{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":126777,"databundleVersionId":15314950,"sourceType":"competition"},{"sourceId":297813564,"sourceType":"kernelVersion"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **DINOv2 Jaguar Patch Similarity Search**","metadata":{}},{"cell_type":"markdown","source":"https://www.kaggle.com/code/stpeteishii/dinov2-pixar-pins-face-similarity-search","metadata":{}},{"cell_type":"markdown","source":"This script performs **similar image search** using DINOv2, a modern computer vision model.\n\n**Key steps:**\n\n1. **Loads DINOv2 model** - pretrained vision transformer that understands image content\n2. **Processes images** - resizes, normalizes, and converts them to tensors\n3. **Creates embeddings** - converts each image into a numerical vector (512-dimension) that represents its content\n4. **Searches similar images** - uses cosine similarity to find images with the closest vector representations\n\n**How it works:**\n- Takes a folder of celebrity images\n- Converts all images to numerical embeddings\n- When you provide a query image, it finds the most similar images by comparing their vectors\n- Returns top matches with similarity scores\n\n**Use case:** Finding duplicate/near-duplicate images or similar-looking photos in a collection.","metadata":{}},{"cell_type":"code","source":"from PIL import Image\nimport torch\nimport torch.nn.functional as F\nimport timm\nimport numpy as np\nimport pandas as pd\nimport os\nimport matplotlib.pyplot as plt\nfrom matplotlib.patches import Rectangle\nimport random\n\n## -----------------------------\n## 1. Load Model\n## -----------------------------\nmodel = timm.create_model('vit_small_patch14_dinov2.lvd142m', pretrained=True)\nmodel.eval()\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nmodel.to(device)\n\n## -----------------------------\n## 2. Image Preprocessing\n## -----------------------------\nfrom torchvision import transforms\n\n# DINOv2 models typically use 518x518 input size\ntarget_size = 518\n\npreprocess = transforms.Compose([\n    transforms.Resize(target_size),\n    transforms.CenterCrop(target_size),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\ndef image_to_embedding(img_path):\n    img = Image.open(img_path).convert('RGB')\n    x = preprocess(img).unsqueeze(0).to(device)\n    with torch.no_grad():\n        emb = model(x)\n        \n        # Extract features if output is a dictionary\n        if isinstance(emb, dict):\n            if 'last_hidden_state' in emb:\n                emb = emb['last_hidden_state'][:, 0]  # CLS token\n            elif 'pooler_output' in emb:\n                emb = emb['pooler_output']\n            else:\n                emb = list(emb.values())[0]\n                if len(emb.shape) > 2: emb = emb[:, 0]\n        \n        # Handle sequence dimension [batch, seq, dim] -> [batch, dim]\n        if len(emb.shape) == 3:\n            emb = emb[:, 0]\n            \n        if len(emb.shape) == 1:\n            emb = emb.unsqueeze(0)\n            \n        emb = F.normalize(emb, dim=-1) # L2 normalization for Cosine Similarity\n        \n    return emb.cpu().numpy()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T16:58:25.0398Z","iopub.execute_input":"2026-02-09T16:58:25.040399Z","iopub.status.idle":"2026-02-09T16:58:25.515113Z","shell.execute_reply.started":"2026-02-09T16:58:25.040375Z","shell.execute_reply":"2026-02-09T16:58:25.514185Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## -----------------------------\n## 3. Compute Embeddings for Folder\n## -----------------------------\nimport os\nimport random\nimport numpy as np\nfrom typing import List, Optional\n\n# Initialize lists\nimage_paths: List[str] = []\nembeddings: List[np.ndarray] = []\nimage_folder = '/kaggle/input/notebooks/stpeteishii/extracting-square-patches-of-jaguar-pattern/test'\n\n# 1. Use more efficient image file filtering\ndef get_image_files(folder: str) -> List[str]:\n    \"\"\"Get all image files in a folder\"\"\"\n    image_extensions = {'.png', '.jpg', '.jpeg', '.PNG', '.JPG', '.JPEG'}\n    image_files = []\n    \n    for file in os.listdir(folder):\n        if any(file.lower().endswith(ext) for ext in image_extensions):\n            image_files.append(os.path.join(folder, file))\n    \n    return image_files\n\n# Get all image files\nall_image_files = get_image_files(image_folder)\n\n# 2. Safer sampling method\ndef select_files(files: List[str], max_samples: int = 1000) -> List[str]:\n    \"\"\"Select files, random sampling if exceeding max_samples\"\"\"\n    if len(files) > max_samples:\n        print(f\"Total images: {len(files)} - randomly selecting {max_samples} samples\")\n        return random.sample(files, max_samples)\n    else:\n        print(f\"Total images: {len(files)} - using all images\")\n        return files\n\n# Select files\nselected_files = select_files(all_image_files, max_samples=1000)\n\n# 3. Process images with better error handling\nembeddings = []\nfileids = []        \nimage_paths = []\nprocessed_count = 0\n\nfor file_path in selected_files:\n    try:\n        emb = image_to_embedding(file_path)\n        emb = emb.squeeze(0) \n        embeddings.append(emb)\n        image_paths.append(file_path)\n        fileids.append(os.path.basename(file_path))\n        processed_count += 1\n        if processed_count % 100 == 0:\n            print(f\"Processed {processed_count} images...\")\n\n    except Exception as e:\n        print(f\"Error processing {os.path.basename(file_path)}: {str(e)}\")\n        continue\n\n# 4. Handle results\nif embeddings:\n    embeddings_array = np.vstack(embeddings)\n    print(f\"\\nProcessing complete!\")\n    print(f\"Successfully processed: {len(image_paths)} images\")\n    print(f\"Embedding shape: {embeddings_array.shape}\")\nelse:\n    raise ValueError(\"No valid images found or processed successfully.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T16:58:25.516525Z","iopub.execute_input":"2026-02-09T16:58:25.516781Z","iopub.status.idle":"2026-02-09T17:00:41.140196Z","shell.execute_reply.started":"2026-02-09T16:58:25.51676Z","shell.execute_reply":"2026-02-09T17:00:41.139594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## -----------------------------\n## 4. Similarity Search (Top 11)\n## -----------------------------\ndef find_most_similar(query_path, embeddings, image_paths, topk=11):\n    \"\"\"\n    Search for the top 11 most similar images.\n    \"\"\"\n    query_emb = image_to_embedding(query_path)\n    \n    # Calculate Cosine Similarity\n    sims = (embeddings @ query_emb.T).squeeze()\n    \n    # Sort indices by similarity descending\n    idxs = np.argsort(-sims)[:topk]\n    \n    results = []\n    for i in idxs:\n        results.append((image_paths[i], float(sims[i])))\n    \n    return results\n\n## -----------------------------\n## 5. Display 4x3 Grid\n## -----------------------------\ndef display_4x3_grid(query_path, results):\n    \"\"\"\n    Display Query + Top 11 results in a 4x3 grid.\n    Layout:\n    Row 1: Query, Rank 1, Rank 2\n    Row 2: Rank 3, Rank 4, Rank 5\n    ...and so on.\n    \"\"\"\n    all_images = [query_path] + [path for path, _ in results]\n    \n    fig, axes = plt.subplots(4, 3, figsize=(15, 20))\n    \n    for idx, ax in enumerate(axes.flat):\n        if idx < len(all_images):\n            img_path = all_images[idx]\n            img = Image.open(img_path).convert('RGB')\n            ax.imshow(img)\n            ax.axis('off')\n            \n            if idx == 0:\n                title = f\"QUERY\\n{os.path.basename(img_path)}\"\n                color, border = 'red', 'red'\n                weight = 'bold'\n            else:\n                rank = idx\n                sim_val = results[idx-1][1]\n                title = f\"#{rank}: {sim_val:.4f}\\n{os.path.basename(img_path)}\"\n                # Color coding based on similarity\n                if sim_val >= 0.8: color, border = 'green', 'green'\n                elif sim_val >= 0.6: color, border = 'orange', 'orange'\n                else: color, border = 'gray', 'gray'\n                weight = 'normal'\n            \n            ax.set_title(title, fontsize=10, color=color, fontweight=weight)\n            \n            # Add colored border\n            rect = Rectangle((0, 0), img.width-1, img.height-1,\n                             linewidth=4, edgecolor=border, facecolor='none')\n            ax.add_patch(rect)\n        else:\n            ax.axis('off')\n    \n    plt.suptitle('Similarity Search Results (Top 11)', fontsize=18, fontweight='bold', y=0.98)\n    plt.tight_layout()\n    plt.show()\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T17:00:41.140981Z","iopub.execute_input":"2026-02-09T17:00:41.141221Z","iopub.status.idle":"2026-02-09T17:00:41.149683Z","shell.execute_reply.started":"2026-02-09T17:00:41.141203Z","shell.execute_reply":"2026-02-09T17:00:41.149156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## -----------------------------\n## 6. Execution\n## -----------------------------\nif __name__ == \"__main__\":\n    # Change this to your specific query image\n    query_img = '/kaggle/input/notebooks/stpeteishii/extracting-square-patches-of-jaguar-pattern/test/test_0001.png'\n    \n    print(\"\\n\" + \"=\"*50)\n    print(\"STARTING SEARCH...\")\n    results = find_most_similar(query_img, embeddings, image_paths, topk=11)\n    \n    print(f\"Query: {os.path.basename(query_img)}\")\n    for i, (path, sim) in enumerate(results, 1):\n        print(f\"Rank {i:02d} | Similarity: {sim:.4f} | File: {os.path.basename(path)}\")\n    \n    display_4x3_grid(query_img, results)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T17:00:41.150372Z","iopub.execute_input":"2026-02-09T17:00:41.150598Z","iopub.status.idle":"2026-02-09T17:00:46.421264Z","shell.execute_reply.started":"2026-02-09T17:00:41.150583Z","shell.execute_reply":"2026-02-09T17:00:46.420293Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test=pd.read_csv('/kaggle/input/jaguar-re-id/test.csv')\npairs=test.iloc[:,1:3].to_numpy()\ndisplay(pairs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T17:00:46.423158Z","iopub.execute_input":"2026-02-09T17:00:46.423457Z","iopub.status.idle":"2026-02-09T17:00:46.494804Z","shell.execute_reply.started":"2026-02-09T17:00:46.423433Z","shell.execute_reply":"2026-02-09T17:00:46.494229Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport os\n\ndef compute_similarity(\n    query_id,\n    gallery_id,\n    embeddings,\n    fileids,\n    folderpath,\n):\n    \"\"\"\n    query_id   : str (filename)\n    gallery_id : str (filename)\n    embeddings : np.ndarray (N, D)  # L2-normalized\n    fileids    : list[str]          # length N\n    folderpath : str\n    \"\"\"\n\n    # filename -> index\n    name_to_idx = {name: i for i, name in enumerate(fileids)}\n\n    qi = name_to_idx[query_id]\n    gi = name_to_idx[gallery_id]\n\n    query_emb = embeddings[qi]\n    gallery_emb = embeddings[gi]\n\n    similarity = float(np.dot(query_emb, gallery_emb))\n\n    return {\n        \"query_path\": os.path.join(folderpath, query_id),\n        \"gallery_path\": os.path.join(folderpath, gallery_id),\n        \"similarity\": similarity,\n    }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T17:00:46.495484Z","iopub.execute_input":"2026-02-09T17:00:46.495699Z","iopub.status.idle":"2026-02-09T17:00:46.500744Z","shell.execute_reply.started":"2026-02-09T17:00:46.495679Z","shell.execute_reply":"2026-02-09T17:00:46.500196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submit=pd.read_csv('/kaggle/input/jaguar-re-id/sample_submission.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T17:00:46.501441Z","iopub.execute_input":"2026-02-09T17:00:46.501718Z","iopub.status.idle":"2026-02-09T17:00:46.549632Z","shell.execute_reply.started":"2026-02-09T17:00:46.501696Z","shell.execute_reply":"2026-02-09T17:00:46.549147Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for i, p in enumerate(pairs):\n    qid, gid = p\n\n    if (qid not in fileids) or (gid not in fileids):\n        submit.iloc[i, 1] = 0.5\n        continue\n\n    res = compute_similarity(\n        query_id=qid,\n        gallery_id=gid,\n        embeddings=embeddings,\n        fileids=fileids,\n        folderpath=image_folder,\n    )\n\n    submit.iloc[i, 1] = np.clip(res['similarity'], 0, 1)\n\ndisplay(submit)\nsubmit.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-09T17:00:46.57728Z","iopub.status.idle":"2026-02-09T17:00:46.577537Z","shell.execute_reply.started":"2026-02-09T17:00:46.577423Z","shell.execute_reply":"2026-02-09T17:00:46.577436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}