Repository: pytorch/vision

Issue:
Imagenet 10 or Imagenette
### 🚀 The feature

I think it would be cool to have a readily-downloading dataset of 10 categories and imagenet shape (i.e. to be fed into 224x224). Currently the closes is likely STL10, but I think one of Imagenet-10 ( https://paperswithcode.com/sota/image-clustering-on-imagenet-10 ) or Imagenette ( https://github.com/fastai/imagenette ) might be worthwhile to add.

### Motivation, pitch

With vision transformers, it seems even more interesting to get a reasonably-sized dataset with images of similar type (size) of ImageNet.

### Alternatives

Manually downloading (torch.hub.download?), extracting and using ImageFolder.

### Additional context

_No response_

cc @pmeier @YosuaMichael


Discussion:
@t-vi Thanks for the suggestion.

I think that's reasonable. Another option could be [Tiny Imagenet](https://paperswithcode.com/dataset/tiny-imagenet). Worth exploring as an addition on the new Datasets API.
Note that tiny imagenet is 64x64 , I was hoping to have a smallish dataset with usual imagenet sizes.
Is "ImageNet10" available as independent dataset? paperswithcode.com has it listed in the clustering benchmark as you linked, but it is not listed in their dataset section. Is it just a subset of the original ImageNet dataset?

As for "imagenette" we could easily provide that. I'll add it to my backlog when the new dataset API is final.


I think it is a subset of imagenet. I don't remember which was the first paper that I saw used it, but e.g. here is the list of classes:
https://github.com/Yunfan-Li/Contrastive-Clustering/tree/main/datasets
> I think it is a subset of imagenet.

Is that the same for imagenette? From looking at the file structure that seems to be the case. If so I'm a little confused how they can provide public download links whereas the regular imagenet doesn't do that due to licensing issues.

In case they are actually subsets, I wouldn't put much priority on adding them to our API for now. With the new datasets (available under `torchvision.prototype.datasets` in the nightly releases), we can easily achieve this with

```py
import functools

from torchvision.prototype import datasets, features


def filter_wnid(sample, *, wnids):
    return sample["wnid"] in wnids


def fix_label(sample, *, wnids, categories):
    sample["label"] = features.Label(wnids.index(sample["wnid"]), categories=categories)
    return sample


def load_imagenet_subset(*wnids):
    info = datasets.info("imagenet")
    wnids_to_categories = dict(zip(info["wnids"], info["categories"]))
    categories = [wnids_to_categories[wnid] for wnid in wnids]

    return (
        datasets.load("imagenet")
        .filter(functools.partial(filter_wnid, wnids=wnids))
        .map(functools.partial(fix_label, wnids=wnids, categories=categories))
    )


imagenet_10 = load_imagenet_subset(
    "n02056570",
    "n02085936",
    "n02128757",
    "n02690373",
    "n02692877",
    "n03095699",
    "n04254680",
    "n04285008",
    "n04467665",
    "n07747607",
)

for sample in imagenet_10:
    print(sample["wnid"])
    print(sample["label"])
    print(sample["label"].to_categories())
    break
```

```
n02056570
Label(0)
king penguin
```

It is not really efficient since we still are going to load (not decode!) all other images and throw them away, but it should be sufficient for prototyping.
> Is that the same for imagenette? From looking at the file structure that seems to be the case. If so I'm a little confused how they can provide public download links whereas the regular imagenet doesn't do that due to licensing issues.

I have no insight into it, except that maybe the difference of giving it out to your buddies or giving it out to everyone might not be that big in terms of copyright.

My key interest would be to not have to manually code downloading the thing. Alternatively, maybe having a dataset that does the downloading of a given URL + checksum would also work for me, if the link to imagenette is that scary.

Also, the subset (tench, English springer, cassette player, chain saw, church, French horn, garbage truck, gas pump, golf ball, parachute) of imagenette would seem to be able to sidestep some of the issues surrounding the imagenet redaction of 2019/20(?).
> I have no insight into it, except that maybe the difference of giving it out to your buddies or giving it out to everyone might not be that big in terms of copyright.
> 
> My key interest would be to not have to manually code downloading the thing. Alternatively, maybe having a dataset that does the downloading of a given URL + checksum would also work for me, if the link to imagenette is that scary.

I don't think it has any implications for us if we provide a wrapper around a publicly available URL. In case the usage was not authorized, the people that published it will be at fault. Since the repository is licensed under [Apache License 2.0](https://github.com/fastai/imagenette/blob/master/LICENSE), I think it is fair to assume for us and our users that the data is fine to use under this license. There is nothing in the README that would contradict this.

My reason for bringing this up was that we should be extra careful when checking if imagenette is actually a subset of imagenet, since the images for the latter are not publicly available and thus I find it odd that the others are.

@yassineAlouini Could you also put this in the "new datasets lists" that you are compiling?


> I don't think it has any implications for us if we provide a wrapper around a publicly available URL.

I am not too sure about this. I'm not an expert to copyrighting so before starting any work on this we would have to ask advice of people within Meta who have this expertise. It's OK to record this as a request from the community, just make sure to ping me prior kicking off work on it so that I can try to clarify these points. This will help us avoid throwaway work.
@datumbox @pmeier @yassineAlouini @t-vi I would like to open up this issue once again as my organization has interest in this dataset as well to be readily available, what is the current status surrounding this dataset to be in Torchvision dataset? From my understanding of their license in their repo, it shows Apache 2.0 which should be fine but would like to check if this is a showstopper for Meta or anything else. If this is not a showstopper, could we proceed in creating a PR for upstreaming Dataset with Imagenette capability into torchvision?

Resolving patch:
diff --git a/torchvision/datasets/__init__.py b/torchvision/datasets/__init__.py
--- a/torchvision/datasets/__init__.py
+++ b/torchvision/datasets/__init__.py
@@ -30,6 +30,7 @@
 from .gtsrb import GTSRB
 from .hmdb51 import HMDB51
 from .imagenet import ImageNet
+from .imagenette import Imagenette
 from .inaturalist import INaturalist
 from .kinetics import Kinetics
 from .kitti import Kitti
@@ -128,6 +129,7 @@
     "InStereo2k",
     "ETH3DStereo",
     "wrap_dataset_for_transforms_v2",
+    "Imagenette",
 )
 
 
diff --git a/torchvision/datasets/imagenette.py b/torchvision/datasets/imagenette.py
new file mode 100644
--- /dev/null
+++ b/torchvision/datasets/imagenette.py
@@ -0,0 +1,104 @@
+from pathlib import Path
+from typing import Any, Callable, Optional, Tuple
+
+from PIL import Image
+
+from .folder import find_classes, make_dataset
+from .utils import download_and_extract_archive, verify_str_arg
+from .vision import VisionDataset
+
+
+class Imagenette(VisionDataset):
+    """`Imagenette <https://github.com/fastai/imagenette#imagenette-1>`_ image classification dataset.
+
+    Args:
+        root (string): Root directory of the Imagenette dataset.
+        split (string, optional): The dataset split. Supports ``"train"`` (default), and ``"val"``.
+        size (string, optional): The image size. Supports ``"full"`` (default), ``"320px"``, and ``"160px"``.
+        download (bool, optional): If ``True``, downloads the dataset components and places them in ``root``. Already
+            downloaded archives are not downloaded again.
+        transform (callable, optional): A function/transform that  takes in an PIL image and returns a transformed
+            version, e.g. ``transforms.RandomCrop``.
+        target_transform (callable, optional): A function/transform that takes in the target and transforms it.
+
+     Attributes:
+        classes (list): List of the class name tuples.
+        class_to_idx (dict): Dict with items (class name, class index).
+        wnids (list): List of the WordNet IDs.
+        wnid_to_idx (dict): Dict with items (WordNet ID, class index).
+    """
+
+    _ARCHIVES = {
+        "full": ("https://s3.amazonaws.com/fast-ai-imageclas/imagenette2.tgz", "fe2fc210e6bb7c5664d602c3cd71e612"),
+        "320px": ("https://s3.amazonaws.com/fast-ai-imageclas/imagenette2-320.tgz", "3df6f0d01a2c9592104656642f5e78a3"),
+        "160px": ("https://s3.amazonaws.com/fast-ai-imageclas/imagenette2-160.tgz", "e793b78cc4c9e9a4ccc0c1155377a412"),
+    }
+    _WNID_TO_CLASS = {
+        "n01440764": ("tench", "Tinca tinca"),
+        "n02102040": ("English springer", "English springer spaniel"),
+        "n02979186": ("cassette player",),
+        "n03000684": ("chain saw", "chainsaw"),
+        "n03028079": ("church", "church building"),
+        "n03394916": ("French horn", "horn"),
+        "n03417042": ("garbage truck", "dustcart"),
+        "n03425413": ("gas pump", "gasoline pump", "petrol pump", "island dispenser"),
+        "n03445777": ("golf ball",),
+        "n03888257": ("parachute", "chute"),
+    }
+
+    def __init__(
+        self,
+        root: str,
+        split: str = "train",
+        size: str = "full",
+        download=False,
+        transform: Optional[Callable] = None,
+        target_transform: Optional[Callable] = None,
+    ) -> None:
+        super().__init__(root, transform=transform, target_transform=target_transform)
+
+        self._split = verify_str_arg(split, "split", ["train", "val"])
+        self._size = verify_str_arg(size, "size", ["full", "320px", "160px"])
+
+        self._url, self._md5 = self._ARCHIVES[self._size]
+        self._size_root = Path(self.root) / Path(self._url).stem
+        self._image_root = str(self._size_root / self._split)
+
+        if download:
+            self._download()
+        elif not self._check_exists():
+            raise RuntimeError("Dataset not found. You can use download=True to download it.")
+
+        self.wnids, self.wnid_to_idx = find_classes(self._image_root)
+        self.classes = [self._WNID_TO_CLASS[wnid] for wnid in self.wnids]
+        self.class_to_idx = {
+            class_name: idx for wnid, idx in self.wnid_to_idx.items() for class_name in self._WNID_TO_CLASS[wnid]
+        }
+        self._samples = make_dataset(self._image_root, self.wnid_to_idx, extensions=".jpeg")
+
+    def _check_exists(self) -> bool:
+        return self._size_root.exists()
+
+    def _download(self):
+        if self._check_exists():
+            raise RuntimeError(
+                f"The directory {self._size_root} already exists. "
+                f"If you want to re-download or re-extract the images, delete the directory."
+            )
+
+        download_and_extract_archive(self._url, self.root, md5=self._md5)
+
+    def __getitem__(self, idx: int) -> Tuple[Any, Any]:
+        path, label = self._samples[idx]
+        image = Image.open(path).convert("RGB")
+
+        if self.transform is not None:
+            image = self.transform(image)
+
+        if self.target_transform is not None:
+            label = self.target_transform(label)
+
+        return image, label
+
+    def __len__(self) -> int:
+        return len(self._samples)
diff --git a/torchvision/tv_tensors/_dataset_wrapper.py b/torchvision/tv_tensors/_dataset_wrapper.py
--- a/torchvision/tv_tensors/_dataset_wrapper.py
+++ b/torchvision/tv_tensors/_dataset_wrapper.py
@@ -284,6 +284,7 @@ def classification_wrapper_factory(dataset, target_keys):
     datasets.GTSRB,
     datasets.DatasetFolder,
     datasets.ImageFolder,
+    datasets.Imagenette,
 ]:
     WRAPPER_FACTORIES.register(dataset_cls)(classification_wrapper_factory)
 


Test patch:
diff --git a/test/test_datasets.py b/test/test_datasets.py
--- a/test/test_datasets.py
+++ b/test/test_datasets.py
@@ -3377,6 +3377,41 @@ def test_bad_input(self):
                 pass
 
 
+class ImagenetteTestCase(datasets_utils.ImageDatasetTestCase):
+    DATASET_CLASS = datasets.Imagenette
+    ADDITIONAL_CONFIGS = combinations_grid(split=["train", "val"], size=["full", "320px", "160px"])
+
+    _WNIDS = [
+        "n01440764",
+        "n02102040",
+        "n02979186",
+        "n03000684",
+        "n03028079",
+        "n03394916",
+        "n03417042",
+        "n03425413",
+        "n03445777",
+        "n03888257",
+    ]
+
+    def inject_fake_data(self, tmpdir, config):
+        archive_root = "imagenette2"
+        if config["size"] != "full":
+            archive_root += f"-{config['size'].replace('px', '')}"
+        image_root = pathlib.Path(tmpdir) / archive_root / config["split"]
+
+        num_images_per_class = 3
+        for wnid in self._WNIDS:
+            datasets_utils.create_image_folder(
+                root=image_root,
+                name=wnid,
+                file_name_fn=lambda idx: f"{wnid}_{idx}.JPEG",
+                num_examples=num_images_per_class,
+            )
+
+        return num_images_per_class * len(self._WNIDS)
+
+
 class TestDatasetWrapper:
     def test_unknown_type(self):
         unknown_object = object()