Source code for stable_datasets.images.pacs

import zipfile
from pathlib import Path

from stable_datasets.schema import BuilderConfig, DatasetInfo, DatasetSource, DownloadInfo, Version
from stable_datasets.splits import Split, SplitGenerator
from stable_datasets.utils import BaseDatasetBuilder, _default_dest_folder, bulk_download

from ._multi_domain import (
    ensure_rgb,
    google_drive_url,
    image_members,
    leave_one_domain_out_configs,
    multi_domain_features,
)


_DOMAINS = ("photo", "art_painting", "cartoon", "sketch")
_ALL_DOMAINS = "all"

# Google Drive IDs of the original release (https://dali-dl.github.io/project_iccv2017.html).
_IMAGES_ID = ("0B6x7gtvErXgfbF9CSk53UkRxVzg", "0-aQEyqs-mKSsoWHvclYvgjw")  # Raw images/PACS.zip
_SPLIT_IDS = {
    ("art_painting", "train"): "1cSo_5_cQLKoURsWwsKqFmw9neWFvDIeV",
    ("art_painting", "crossval"): "1g7haGuvxmZA1SxPyGFZneDVi9wOm3nK4",
    ("art_painting", "test"): "1KVe_S73jECKG9MsIujF-yqZAjPrf0y_O",
    ("cartoon", "train"): "1EhlIyRoeZ5UPiUJs_UXGH-Ql1PS5qtYW",
    ("cartoon", "crossval"): "1suUUrxyPOmN_dAVdhVSaG-NZDMOsl8JR",
    ("cartoon", "test"): "14fpMKzcherr1onzp8jcyhmjJMjZSZ72h",
    ("photo", "train"): "1RyCVv67zVITU4JYYr1XxLnG5q165yu8s",
    ("photo", "crossval"): "1lXtOcPL-yWli_MuDZ0mQwLfq0XbzDu--",
    ("photo", "test"): "1opW_IbwDQQmuylquX5UvRFxbj3muP5sz",
    ("sketch", "train"): "13rWm64rW_dcdcyTFugu4fJcSCTkCB8YA",
    ("sketch", "crossval"): "1Rs0dUTduQ320bXwHBLQT1s82gyr92J9J",
    ("sketch", "test"): "1dFnnsWxYHvdCXmWdhPVwuJ8zgkb_wtbQ",
}


def _split_file(domain: str, part: str) -> str:
    return f"{domain}_{part}_kfold"


[docs] class PACS(BaseDatasetBuilder): """PACS: Photo, Art painting, Cartoon and Sketch. A domain generalization benchmark with 9,991 images of 7 object classes in 4 visual domains: photo (1,670), art painting (2,048), cartoon (2,344) and sketch (3,929). The original release comes with an official train/validation split for every domain (``<domain>_train_kfold.txt`` / ``<domain>_crossval_kfold.txt``), which the authors ask to be used for fair comparisons. The configs follow it: ``"all"`` (default) ``train`` and ``validation`` are the official splits of all four domains. ``"photo"``, ``"art_painting"``, ``"cartoon"``, ``"sketch"`` Leave-one-domain-out: ``train`` and ``validation`` are the official splits of the other three domains, and ``test`` is every image of the named domain (``<domain>_test_kfold.txt``). Every example has ``image`` (227x227 RGB), ``label`` (7 classes) and ``domain`` (4 domains). Usage: train = PACS(split="train") source_train = PACS(config_name="sketch", split="train") target_test = PACS(config_name="sketch", split="test") """ VERSION = Version("1.0.0") DOMAINS = _DOMAINS CLASS_NAMES = ("dog", "elephant", "giraffe", "guitar", "horse", "house", "person") BUILDER_CONFIGS = [ BuilderConfig(name=_ALL_DOMAINS, description="Official train/validation splits of all four domains."), *leave_one_domain_out_configs( "PACS", _DOMAINS, "Leave-one-domain-out: train/validation are the official splits of the other domains, " "test is every image of '{domain}'.", ), ] DEFAULT_CONFIG_NAME = _ALL_DOMAINS SOURCE = DatasetSource( homepage="https://dali-dl.github.io/project_iccv2017.html", assets={ "images": DownloadInfo(url=google_drive_url(*_IMAGES_ID), filename="PACS.zip"), **{ _split_file(domain, part): DownloadInfo( url=google_drive_url(file_id), filename=f"pacs_{_split_file(domain, part)}.txt" ) for (domain, part), file_id in _SPLIT_IDS.items() }, }, license="Unknown (research use; see the original release)", citation="""@inproceedings{li2017deeper, title={Deeper, Broader and Artier Domain Generalization}, author={Li, Da and Yang, Yongxin and Song, Yi-Zhe and Hospedales, Timothy M.}, booktitle={Proceedings of the IEEE International Conference on Computer Vision (ICCV)}, pages={5542--5550}, year={2017}}""", ) def _info(self): return DatasetInfo( description=( "PACS contains 9,991 images of 7 classes (dog, elephant, giraffe, guitar, horse, house, person) " "in 4 domains (photo, art painting, cartoon, sketch), with the official per-domain " f"train/validation splits. Config '{self.config.name}': {self.config.description}" ), features=multi_domain_features(self.CLASS_NAMES, self.DOMAINS), supervised_keys=("image", "label"), homepage=self.SOURCE["homepage"], license=self.SOURCE["license"], citation=self.SOURCE["citation"], ) def _split_files(self) -> dict[str, list[str]]: """Asset names of the official split files that make up each split of this config.""" held_out = self.config.name sources = [d for d in self.DOMAINS if d != held_out] splits = { Split.TRAIN: [_split_file(d, "train") for d in sources], Split.VALIDATION: [_split_file(d, "crossval") for d in sources], } if held_out != _ALL_DOMAINS: splits[Split.TEST] = [_split_file(held_out, "test")] return splits def _candidate_splits(self) -> list[str]: return list(self._split_files()) def _split_generators(self): source = self._source() names = list(source["assets"]) infos = [self._normalize_download_info(source["assets"][n], asset_name=n) for n in names] download_dir = getattr(self, "_raw_download_dir", None) or _default_dest_folder() paths = dict(zip(names, (Path(p) for p in bulk_download(infos, dest_folder=download_dir)))) return [ SplitGenerator( name=split, gen_kwargs={"archive_path": paths["images"], "split_paths": [paths[f] for f in files]}, ) for split, files in self._split_files().items() ] def _generate_examples(self, archive_path, split_paths): label_feature = self.info.features["label"] domain_feature = self.info.features["domain"] with zipfile.ZipFile(archive_path) as archive: members = image_members(archive, self.DOMAINS) for split_path in split_paths: for line in Path(split_path).read_text().splitlines(): if not line.strip(): continue rel_path, label = line.rsplit(maxsplit=1) domain, class_name = rel_path.split("/")[:2] label = int(label) - 1 # the split files number classes from 1 if label_feature.int2str(label) != class_name: raise ValueError(f"PACS: {split_path.name} labels {rel_path} as class {label + 1}.") if rel_path not in members: raise ValueError( f"PACS: {rel_path} from {split_path.name} is missing from {archive_path.name}." ) yield ( rel_path, { "image": ensure_rgb(archive.read(members[rel_path])), "label": label, "domain": domain_feature.str2int(domain), }, )