diff --git a/.gitignore b/.gitignore
index cf2a90d..e30bcef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,4 +7,5 @@ pretrained_weights/
data/images/
visualization/
data/annotations/
-=*
\ No newline at end of file
+=*
+*.tgz
\ No newline at end of file
diff --git a/README.md b/README.md
index 66ae672..f419bda 100644
--- a/README.md
+++ b/README.md
@@ -77,21 +77,44 @@ cub/
## CUB
-- Download prepared dataset
- - From [](https://drive.google.com/drive/folders/1X3ikQEk_D7cKcyCnxbF3kJTsZ0LZfvVO?usp=sharing)
-- `Or` Prepare the dataset by yourself
- - You can download the CUB dataset from [the original website](https://www.vision.caltech.edu/datasets/cub_200_2011/) and put it in the `data/images/` folder.
- - You can use the dataset's provided train/val split to create the train/val splits and have their class numbers as the `prefix` of the respective image folder names(starting from 1).
- - The code will automatically create train and val annotation files in the `data/annotations/` folder for each dataset if not provided.
+1. Download `CUB_200_2011.tgz` from [the official website](https://www.vision.caltech.edu/datasets/cub_200_2011/) and extract it:
+ ```bash
+ tar -xzf CUB_200_2011.tgz
+ ```
+ You will get a `CUB_200_2011/` folder containing `images/`, `images.txt`, and `train_test_split.txt`.
+
+2. Run the preparation script to arrange the images into the required structure:
+ ```bash
+ python data/prepare_cub.py \
+ --cub_dir /path/to/CUB_200_2011 \
+ --out_dir /path/to/data/images/cub
+ ```
+
+The script uses the official train/test split and organises images into `cub/train/` and `cub/val/` with class folders named `NNN.ClassName` (e.g. `001.Black_footed_Albatross`).
Prepare Oxford Pet dataset
## Pet Dataset
-- Download prepared dataset
- - From [](https://drive.google.com/drive/folders/1X3ikQEk_D7cKcyCnxbF3kJTsZ0LZfvVO?usp=sharing)
+
+1. Download both archives from [the official website](https://www.robots.ox.ac.uk/~vgg/data/pets/) and extract them:
+ ```bash
+ tar -xzf images.tar.gz
+ tar -xzf annotations.tar.gz
+ ```
+ You will get an `images/` folder with all `.jpg` files and an `annotations/` folder containing `trainval.txt` and `test.txt`.
+
+2. Run the preparation script to arrange the images into the required structure:
+ ```bash
+ python data/prepare_pet.py \
+ --images_dir /path/to/images \
+ --annotations_dir /path/to/annotations \
+ --out_dir /path/to/data/images/pet
+ ```
+
+The script maps `trainval.txt` → `pet/train/` and `test.txt` → `pet/val/`, with class folders named `NNN.BreedName` (e.g. `001.Abyssinian`).
+
**To add new dataset, see [Extensions](#extensions)**
diff --git a/data/prepare_cub.py b/data/prepare_cub.py
new file mode 100644
index 0000000..111b936
--- /dev/null
+++ b/data/prepare_cub.py
@@ -0,0 +1,90 @@
+"""
+Prepare CUB-200-2011 dataset into the Prompt-CAM directory structure.
+
+Download the dataset from:
+ https://www.vision.caltech.edu/datasets/cub_200_2011/
+
+After extracting the tarball you should have:
+ CUB_200_2011/
+ ├── images/
+ ├── images.txt
+ └── train_test_split.txt
+
+Run:
+ python data/prepare_cub.py --cub_dir /path/to/CUB_200_2011 --out_dir /path/to/data/images/cub
+"""
+
+import argparse
+import os
+import shutil
+
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--cub_dir",
+ required=True,
+ help="Path to the extracted CUB_200_2011 folder.",
+ )
+ parser.add_argument(
+ "--out_dir",
+ required=True,
+ help="Destination folder (e.g. data/images/cub). Created if absent.",
+ )
+ return parser.parse_args()
+
+
+def read_split(cub_dir):
+ """Return dict {image_id: 'train' or 'val'}."""
+ split_file = os.path.join(cub_dir, "train_test_split.txt")
+ split = {}
+ with open(split_file) as f:
+ for line in f:
+ img_id, is_train = line.strip().split()
+ split[img_id] = "train" if is_train == "1" else "val"
+ return split
+
+
+def read_images(cub_dir):
+ """Return dict {image_id: relative_path_from_images_dir}."""
+ images_file = os.path.join(cub_dir, "images.txt")
+ images = {}
+ with open(images_file) as f:
+ for line in f:
+ img_id, img_path = line.strip().split(maxsplit=1)
+ images[img_id] = img_path
+ return images
+
+
+def main():
+ args = parse_args()
+ cub_dir = args.cub_dir
+ out_dir = args.out_dir
+
+ split = read_split(cub_dir)
+ images = read_images(cub_dir)
+
+ images_src = os.path.join(cub_dir, "images")
+
+ for img_id, img_rel_path in images.items():
+ subset = split[img_id]
+ # img_rel_path is like "001.Black_footed_Albatross/Black_Footed_Albatross_0001_796111.jpg"
+ class_folder = img_rel_path.split("/")[0]
+ filename = img_rel_path.split("/")[1]
+
+ dst_dir = os.path.join(out_dir, subset, class_folder)
+ os.makedirs(dst_dir, exist_ok=True)
+
+ src = os.path.join(images_src, img_rel_path)
+ dst = os.path.join(dst_dir, filename)
+ if not os.path.exists(dst):
+ shutil.copy2(src, dst)
+
+ train_classes = len(os.listdir(os.path.join(out_dir, "train")))
+ val_classes = len(os.listdir(os.path.join(out_dir, "val")))
+ print(f"Done. train: {train_classes} classes | val: {val_classes} classes")
+ print(f"Output saved to: {out_dir}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/data/prepare_pet.py b/data/prepare_pet.py
new file mode 100644
index 0000000..121f7c9
--- /dev/null
+++ b/data/prepare_pet.py
@@ -0,0 +1,122 @@
+"""
+Prepare Oxford-IIIT Pet dataset into the Prompt-CAM directory structure.
+
+Download the dataset from:
+ https://www.robots.ox.ac.uk/~vgg/data/pets/
+
+You need two archives:
+ images.tar.gz -> extract to get a folder of .jpg images
+ annotations.tar.gz -> extract to get trainval.txt and test.txt
+
+After extracting you should have:
+ /
+ ├── images/ (all .jpg images, e.g. Abyssinian_1.jpg)
+ └── annotations/
+ ├── trainval.txt
+ └── test.txt
+
+Each annotation line has the format:
+
+where class_id is 1-indexed and corresponds to alphabetical order of breed names.
+
+Run:
+ python data/prepare_pet.py \
+ --images_dir /path/to/images \
+ --annotations_dir /path/to/annotations \
+ --out_dir /path/to/data/images/pet
+"""
+
+import argparse
+import os
+import re
+import shutil
+
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ "--images_dir",
+ required=True,
+ help="Path to the extracted images/ folder containing all .jpg files.",
+ )
+ parser.add_argument(
+ "--annotations_dir",
+ required=True,
+ help="Path to the extracted annotations/ folder containing trainval.txt and test.txt.",
+ )
+ parser.add_argument(
+ "--out_dir",
+ required=True,
+ help="Destination folder (e.g. data/images/pet). Created if absent.",
+ )
+ return parser.parse_args()
+
+
+def class_name_from_image(image_name):
+ """'Abyssinian_1' -> 'Abyssinian', 'English_Cocker_Spaniel_12' -> 'English_Cocker_Spaniel'."""
+ return re.sub(r"_\d+$", "", image_name)
+
+
+def parse_annotation_file(ann_file):
+ """Return list of (image_name, class_id) skipping comment lines."""
+ entries = []
+ with open(ann_file) as f:
+ for line in f:
+ line = line.strip()
+ if not line or line.startswith("#"):
+ continue
+ parts = line.split()
+ image_name = parts[0]
+ class_id = int(parts[1])
+ entries.append((image_name, class_id))
+ return entries
+
+
+def main():
+ args = parse_args()
+ images_dir = args.images_dir
+ annotations_dir = args.annotations_dir
+ out_dir = args.out_dir
+
+ trainval_file = os.path.join(annotations_dir, "trainval.txt")
+ test_file = os.path.join(annotations_dir, "test.txt")
+
+ splits = [
+ (trainval_file, "train"),
+ (test_file, "val"),
+ ]
+
+ for ann_file, subset in splits:
+ if not os.path.exists(ann_file):
+ print(f"Warning: {ann_file} not found, skipping {subset} split.")
+ continue
+
+ entries = parse_annotation_file(ann_file)
+ for image_name, class_id in entries:
+ breed = class_name_from_image(image_name)
+ # Zero-pad class_id to three digits to match the NNN.ClassName convention
+ class_folder = f"{class_id:03d}.{breed}"
+
+ src = os.path.join(images_dir, image_name + ".jpg")
+ if not os.path.exists(src):
+ print(f"Warning: image not found: {src}")
+ continue
+
+ dst_dir = os.path.join(out_dir, subset, class_folder)
+ os.makedirs(dst_dir, exist_ok=True)
+
+ dst = os.path.join(dst_dir, image_name + ".jpg")
+ if not os.path.exists(dst):
+ shutil.copy2(src, dst)
+
+ for subset in ("train", "val"):
+ subset_path = os.path.join(out_dir, subset)
+ if os.path.exists(subset_path):
+ n = len(os.listdir(subset_path))
+ print(f"{subset}: {n} classes")
+
+ print(f"Output saved to: {out_dir}")
+
+
+if __name__ == "__main__":
+ main()