{"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":108394,"databundleVersionId":13172641,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport torch\nimport pandas as pd\nimport numpy as np\nfrom torchvision import models, transforms\nfrom PIL import Image\nfrom tqdm import tqdm\nfrom sklearn.preprocessing import normalize\nfrom sklearn.metrics.pairwise import cosine_similarity\n\n# =========================================================\n# CONFIG\n# =========================================================\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nNUM_WORKERS = 2\nBATCH_SIZE = 64\nDATA_ROOT = \"/kaggle/input/h690\"   # dataset root\nOUTPUT_PATH = \"submission.csv\"\n\n# =========================================================\n# LOAD IMAGE PATHS\n# =========================================================\n# Recursively collect all image files (jpg, png, jpeg)\nimage_paths = []\nfor root, dirs, files in os.walk(DATA_ROOT):\n    for f in files:\n        if f.lower().endswith((\".jpg\", \".jpeg\", \".png\")):\n            image_paths.append(os.path.join(root, f))\n\nprint(f\"✅ Found {len(image_paths)} images.\")\n\n# Create dataframe\nmetadata = pd.DataFrame({\n    \"image\": [os.path.basename(p) for p in image_paths],\n    \"path\": image_paths\n})\n\n# =========================================================\n# MODEL + FEATURE EXTRACTOR\n# =========================================================\nmodel = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)\nmodel.fc = torch.nn.Identity()  # remove classification layer\nmodel = model.to(DEVICE)\nmodel.eval()\n\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                         std=[0.229, 0.224, 0.225])\n])\n\n@torch.no_grad()\ndef extract_features(paths):\n    features = []\n    for i in tqdm(range(0, len(paths), BATCH_SIZE), desc=\"Extracting features\"):\n        batch_paths = paths[i:i+BATCH_SIZE]\n        imgs = [transform(Image.open(p).convert(\"RGB\")) for p in batch_paths]\n        imgs = torch.stack(imgs).to(DEVICE)\n        feats = model(imgs).cpu().numpy()\n        features.append(feats)\n    return np.vstack(features)\n\n# =========================================================\n# FEATURE EXTRACTION\n# =========================================================\nfeatures = extract_features(metadata[\"path\"].values)\nfeatures = normalize(features)\n\n# =========================================================\n# SIMPLE CLUSTERING (cosine similarity)\n# =========================================================\nsim = cosine_similarity(features)\nthreshold = 0.75  # tweakable\ngroups = []\nvisited = set()\n\nfor i in tqdm(range(len(metadata)), desc=\"Clustering\"):\n    if i in visited:\n        continue\n    neighbors = np.where(sim[i] > threshold)[0]\n    cluster = list(neighbors)\n    for n in neighbors:\n        visited.add(n)\n    groups.append(cluster)\n\n# Map each image to a group/component ID\nimage_to_group = {}\nfor group_id, cluster in enumerate(groups):\n    for idx in cluster:\n        image_to_group[metadata.iloc[idx][\"image\"]] = group_id\n\n# =========================================================\n# SAVE SUBMISSION\n# =========================================================\nsubmission = pd.DataFrame({\n    \"image\": metadata[\"image\"],\n    \"component\": metadata[\"image\"].map(image_to_group)\n})\nsubmission.to_csv(OUTPUT_PATH, index=False)\nprint(f\"🎉 Submission saved to {OUTPUT_PATH}\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-16T07:44:43.469728Z","iopub.execute_input":"2025-08-16T07:44:43.470418Z","iopub.status.idle":"2025-08-16T08:03:03.713500Z","shell.execute_reply.started":"2025-08-16T07:44:43.470378Z","shell.execute_reply":"2025-08-16T08:03:03.712856Z"}},"outputs":[],"execution_count":null}]}