{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":91448,"databundleVersionId":11249847,"sourceType":"competition"}],"dockerImageVersionId":31011,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"###Install required libraries:\n!pip install git+https://github.com/mlfoundations/open_clip.git -q\n!pip install faiss-gpu -qq ","metadata":{"execution":{"iopub.status.busy":"2025-04-30T17:15:51.517281Z","iopub.execute_input":"2025-04-30T17:15:51.517549Z","iopub.status.idle":"2025-04-30T17:18:24.504140Z","shell.execute_reply.started":"2025-04-30T17:15:51.517530Z","shell.execute_reply":"2025-04-30T17:18:24.503114Z"},"tags":["formatted"],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nimport yaml\nfrom pathlib import Path\nfrom types import SimpleNamespace\nimport argparse\n\nimport numpy as np\nimport pandas as pd\nimport torch\nimport faiss\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, TensorDataset, Dataset\nfrom torchvision import transforms\nfrom torchvision.datasets import ImageFolder\n\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom torchvision import transforms as tfms\nimport torchvision.transforms as T\nimport open_clip\n\nfrom typing import Sequence, Tuple, Any, Dict, List, Optional, Union\nfrom collections import defaultdict\nimport importlib","metadata":{"execution":{"iopub.status.busy":"2025-04-28T08:11:56.616647Z","iopub.execute_input":"2025-04-28T08:11:56.616975Z","iopub.status.idle":"2025-04-28T08:11:56.623236Z","shell.execute_reply.started":"2025-04-28T08:11:56.616942Z","shell.execute_reply":"2025-04-28T08:11:56.622480Z"},"tags":["formatted"],"trusted":true,"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"data_path = '/kaggle/input/fungi-clef-2025/'","metadata":{"execution":{"iopub.status.busy":"2025-04-28T08:11:56.625046Z","iopub.execute_input":"2025-04-28T08:11:56.625362Z","iopub.status.idle":"2025-04-28T08:11:56.643617Z","shell.execute_reply.started":"2025-04-28T08:11:56.625333Z","shell.execute_reply":"2025-04-28T08:11:56.642885Z"},"tags":["formatted"],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_ds_raw = FungiTastic(\n#     root=data_path,\n#     split='train',\n#     transform=transforms.ToTensor()          # yields torch.Tensor C×H×W in [0,1]\n# )\n\n# # 2) Accumulate sums and squared sums per channel\n# sum_c  = torch.zeros(3)\n# sum_sq = torch.zeros(3)\n# pix_cnt = 0\n\n# for img, *_ in tqdm(train_ds_raw, desc=\"Computing mean/std\"):  \n#     # img has shape [C, H, W], values in [0,1]\n#     C, H, W = img.shape\n#     sum_c  += img.sum(dim=[1,2])          # sum over H and W → [3]\n#     sum_sq += (img**2).sum(dim=[1,2])     # sum of squares → [3]\n#     pix_cnt += H * W\n\n# # 3) Compute mean and std\n# mean = sum_c  / pix_cnt\n# var  = (sum_sq / pix_cnt) - mean**2\n# std  = torch.sqrt(var)\n\n# print(\"Computed means:\", mean.tolist())\n# print(\"Computed stds: \", std.tolist())\nMEAN = [0.44550442695617676, 0.42286601662635803, 0.3496400713920593] \nSTD = [0.2475634068250656, 0.2405535727739334, 0.23902718722820282] ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:11:56.644633Z","iopub.execute_input":"2025-04-28T08:11:56.644937Z","iopub.status.idle":"2025-04-28T08:11:56.658668Z","shell.execute_reply.started":"2025-04-28T08:11:56.644908Z","shell.execute_reply":"2025-04-28T08:11:56.657953Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class FungiTastic(torch.nn.Module):\n    \n    SPLIT2STR = {'train': 'Train', 'val': 'Val', 'test': 'Test'}\n\n    def __init__(self, root: str, split: str = 'val', transform=None):\n        super().__init__()\n        self.split = split\n        self.transform = transform\n        self.df = self._get_df(root, split)\n\n        assert \"image_path\" in self.df\n        if self.split != 'test':\n            assert \"category_id\" in self.df\n            self.n_classes = len(self.df['category_id'].unique())\n            self.category_id2label = {\n                k: v[0] for k, v in self.df.groupby('category_id')['species'].unique().to_dict().items()\n            }\n            self.label2category_id = {\n                v: k for k, v in self.category_id2label.items()\n            }\n\n    def add_embeddings(self, embeddings: pd.DataFrame):\n        assert isinstance(embeddings, pd.DataFrame), \"Embeddings must be a pandas DataFrame.\"\n        assert \"embedding\" in embeddings.columns, \"Embeddings DataFrame must have an 'embedding' column.\"\n        assert len(embeddings) == len(self.df), \"Embeddings must match dataset length.\"\n\n        self.df = pd.merge(self.df, embeddings, on=\"filename\", how=\"inner\")\n\n    def get_embeddings_for_class(self, id):\n        # return the embeddings for class class_idx\n        class_idxs = self.df[self.df['category_id'] == id].index\n        return self.df.iloc[class_idxs]['embedding']\n    \n    @staticmethod\n    def _get_df(data_path: str, split: str) -> pd.DataFrame:\n        df_path = os.path.join(\n            data_path,\n            \"metadata\",\n            \"FungiTastic-FewShot\",\n            f\"FungiTastic-FewShot-{FungiTastic.SPLIT2STR[split]}.csv\"\n        )\n        df = pd.read_csv(df_path)\n        df[\"image_path\"] = df.filename.apply(\n            lambda x: os.path.join(data_path, \"FungiTastic-FewShot\", split, '300p', x)\n        )\n        return df\n\n    def __getitem__(self, idx: int):\n        file_path = self.df[\"image_path\"].iloc[idx].replace('FungiTastic-FewShot', 'images/FungiTastic-FewShot')\n    \n        if self.split != 'test':\n            category_id = self.df[\"category_id\"].iloc[idx]\n        else:\n            category_id = None\n    \n        image = Image.open(file_path)\n    \n        if self.transform:\n            image = self.transform(image)\n    \n        # Check if embeddings exist\n        if \"embedding\" in self.df.columns:\n            emb = torch.tensor(self.df.iloc[idx]['embedding'], dtype=torch.float32).squeeze()\n        else:\n            emb = None  # No embeddings available\n\n        return image, category_id, file_path, emb\n\n\n    def __len__(self):\n        return len(self.df)\n\n    def get_class_id(self, idx: int) -> int:\n        return self.df[\"category_id\"].iloc[idx]\n\n    def show_sample(self, idx: int) -> None:\n        image, category_id, _, _ = self.__getitem__(idx)\n        class_name = self.category_id2label[category_id]\n\n        plt.imshow(image)\n        plt.title(f\"Class: {class_name}; id: {idx}\")\n        plt.axis('off')\n        plt.show()\n\n    def get_category_idxs(self, category_id: int) -> List[int]:\n        return self.df[self.df.category_id == category_id].index.tolist()","metadata":{"execution":{"iopub.status.busy":"2025-04-28T08:11:56.672810Z","iopub.execute_input":"2025-04-28T08:11:56.673038Z","iopub.status.idle":"2025-04-28T08:11:56.690617Z","shell.execute_reply.started":"2025-04-28T08:11:56.673018Z","shell.execute_reply":"2025-04-28T08:11:56.689745Z"},"tags":["formatted"],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_obs_embeddings(df: pd.DataFrame) -> pd.DataFrame:\n    rows = []\n    for obs_id, grp in df.groupby('observationID'):\n        # each grp.embedding is a 1‑D numpy array\n        embs = np.vstack(grp['embedding'].values)   # (V, D)\n        rows.append({\n            'observationID': obs_id,\n            'category_id'  : grp['category_id'].iloc[0],\n            'embedding'    : embs.mean(axis=0)\n        })\n    return pd.DataFrame(rows)\n\n\ndef make_obs_embeddings_test(df):\n    \"\"\"\n    Only aggregates embeddings per observation,\n    doesn’t require category_id.\n    \"\"\"\n    rows = []\n    for obs_id, grp in df.groupby('observationID'):\n        emb = np.vstack(grp['embedding'].values).mean(axis=0)\n        rows.append({\n            'observationID': obs_id,\n            'embedding'    : emb\n        })\n    return pd.DataFrame(rows)\n\ndef generate_ensemble_embeddings_raw(dataset):\n    rows = []\n    for _, row in tqdm(dataset.df.iterrows(),\n                      total=len(dataset.df),\n                      desc=\"Ensemble Embeds\"):\n        fp = row.image_path.replace(\n            'FungiTastic-FewShot',\n            'images/FungiTastic-FewShot'\n        )\n        img = Image.open(fp).convert(\"RGB\")\n\n        feats = []\n        with torch.no_grad():\n            for m, proc in zip(ensembleModels, ensembleProcessors):\n                tensor = proc(img).unsqueeze(0).to(device)  # (1,3,H,W)\n                f      = m.encode_image(tensor)            # (1, D_i)\n                f      = F.normalize(f, dim=-1)            # L2 norm\n                feats.append(f)\n\n        fused = torch.cat(feats, dim=1)           # shape [1, 512+768+…]\n        fused = F.normalize(fused, dim=-1)        # re‑normalize length\n        rows.append({\n            'filename' : Path(fp).name,\n            'embedding': fused.cpu().numpy().squeeze()\n        })\n\n    return pd.DataFrame(rows)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:12:09.522436Z","iopub.execute_input":"2025-04-28T08:12:09.522746Z","iopub.status.idle":"2025-04-28T08:12:09.528394Z","shell.execute_reply.started":"2025-04-28T08:12:09.522721Z","shell.execute_reply":"2025-04-28T08:12:09.527413Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ensembleModels = []\nensembleProcessors = []","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nensembleSpecs = [\n    ('hf-hub:imageomics/bioclip',     None),               # BioCLIP\n    ('ViT-L-14',                       'openai'),           # official OpenAI CLIP\n    ('ViT-L-14',                       'laion2b_s32b_b82k') # LAION 2B variant\n]\n\nfor modelName, pretrainedTag in ensembleSpecs:\n    if pretrainedTag:\n        m, _, proc = open_clip.create_model_and_transforms(\n            modelName,\n            pretrained=pretrainedTag\n        )\n    else:\n        m, _, proc = open_clip.create_model_and_transforms(modelName)\n    m.to(device).eval()\n    ensembleModels.append(m)\n    ensembleProcessors.append(proc)\n\nprint(f\"Loaded {len(ensembleModels)} models\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:14:54.335864Z","iopub.execute_input":"2025-04-28T08:14:54.336183Z","iopub.status.idle":"2025-04-28T08:15:18.321140Z","shell.execute_reply.started":"2025-04-28T08:14:54.336159Z","shell.execute_reply":"2025-04-28T08:15:18.320222Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trainDataset = FungiTastic(root=data_path, split='train', )\nvalDataset = FungiTastic(root = data_path, split = \"val\")\ntestDataset = FungiTastic(root = data_path, split = 'test')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:19:15.129332Z","iopub.execute_input":"2025-04-28T08:19:15.129645Z","iopub.status.idle":"2025-04-28T08:19:15.357179Z","shell.execute_reply.started":"2025-04-28T08:19:15.129623Z","shell.execute_reply":"2025-04-28T08:19:15.356225Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_emb_df = generate_ensemble_embeddings_raw(trainDataset)\ntrainDataset.add_embeddings(train_emb_df) \n\nval_emb_df   = generate_ensemble_embeddings_raw(valDataset)\nvalDataset.add_embeddings(val_emb_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:19:17.212233Z","iopub.execute_input":"2025-04-28T08:19:17.212573Z","iopub.status.idle":"2025-04-28T08:35:18.240076Z","shell.execute_reply.started":"2025-04-28T08:19:17.212546Z","shell.execute_reply":"2025-04-28T08:35:18.239151Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"trainObs = make_obs_embeddings(trainDataset.df)\nvalObs   = make_obs_embeddings(valDataset.df) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:36:06.728666Z","iopub.execute_input":"2025-04-28T08:36:06.728968Z","iopub.status.idle":"2025-04-28T08:36:07.407347Z","shell.execute_reply.started":"2025-04-28T08:36:06.728942Z","shell.execute_reply":"2025-04-28T08:36:07.406667Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"proto_df    = (\n    trainObs\n      .groupby('category_id')['embedding']\n      .apply(lambda arrs: np.stack(arrs.values).mean(axis=0))\n      .reset_index()\n)\nproto_labels = proto_df['category_id'].values                        # (C,)\nproto_embs   = np.stack(proto_df['embedding'].values).astype('float32')  # (C, D_ensemble)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:36:12.131028Z","iopub.execute_input":"2025-04-28T08:36:12.131467Z","iopub.status.idle":"2025-04-28T08:36:12.286001Z","shell.execute_reply.started":"2025-04-28T08:36:12.131430Z","shell.execute_reply":"2025-04-28T08:36:12.285347Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"faiss.normalize_L2(proto_embs)\nindex = faiss.IndexFlatIP(proto_embs.shape[1])\nindex.add(proto_embs) ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:36:19.709086Z","iopub.execute_input":"2025-04-28T08:36:19.709441Z","iopub.status.idle":"2025-04-28T08:36:19.723835Z","shell.execute_reply.started":"2025-04-28T08:36:19.709413Z","shell.execute_reply":"2025-04-28T08:36:19.723042Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_embs = np.stack(valObs['embedding'].values).astype('float32')\nfaiss.normalize_L2(val_embs)\nK = 5\nD, I = index.search(val_embs, K)              # top‑K\npreds = proto_labels[I]                      # shape (N_val, 5)\ntrue  = valObs['category_id'].values\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:36:25.655922Z","iopub.execute_input":"2025-04-28T08:36:25.656273Z","iopub.status.idle":"2025-04-28T08:36:25.720857Z","shell.execute_reply.started":"2025-04-28T08:36:25.656243Z","shell.execute_reply":"2025-04-28T08:36:25.720108Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"recall5 = np.mean([ true[i] in preds[i] for i in range(len(true)) ])\nprint(f\"Ensemble Val Recall@5 = {recall5:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:36:30.892643Z","iopub.execute_input":"2025-04-28T08:36:30.892967Z","iopub.status.idle":"2025-04-28T08:36:30.901790Z","shell.execute_reply.started":"2025-04-28T08:36:30.892940Z","shell.execute_reply":"2025-04-28T08:36:30.900923Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# test_emb_df = generate_ensemble_embeddings_raw(trainDataset)\n# testDataset.add_embeddings(train_emb_df) \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:56:23.588283Z","iopub.execute_input":"2025-04-28T08:56:23.588624Z","iopub.status.idle":"2025-04-28T08:56:23.591956Z","shell.execute_reply.started":"2025-04-28T08:56:23.588595Z","shell.execute_reply":"2025-04-28T08:56:23.591111Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# testObs = make_obs_embeddings_test(testDataset.df)\n# test_embs = np.stack(testObs['embedding'].values).astype('float32')\n# faiss.normalize_L2(test_embs)\n# _, I_test = index.search(test_embs, 10)  # top‑10 for submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:56:24.544661Z","iopub.execute_input":"2025-04-28T08:56:24.544994Z","iopub.status.idle":"2025-04-28T08:56:24.548686Z","shell.execute_reply.started":"2025-04-28T08:56:24.544965Z","shell.execute_reply":"2025-04-28T08:56:24.547709Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# pred_strs = [\" \".join(map(str, proto_labels[idxs])) for idxs in I_test]\n# submission = pd.DataFrame({\n#     'observationId': testObs['observationID'].values,\n#     'predictions':   pred_strs\n# })\n# submission.to_csv('submission_ensemble.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-28T08:56:24.951148Z","iopub.execute_input":"2025-04-28T08:56:24.951526Z","iopub.status.idle":"2025-04-28T08:56:24.954910Z","shell.execute_reply.started":"2025-04-28T08:56:24.951496Z","shell.execute_reply":"2025-04-28T08:56:24.954035Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}