Source code for stable_datasets.images.office_home

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,
    normalize_name,
    stratified_test_keys,
)


_DOMAINS = ("art", "clipart", "product", "real_world")
_RANDOM = "random"
_TEST_FRACTION = 0.2


[docs] class OfficeHome(BaseDatasetBuilder): """Office-Home. A domain adaptation / generalization benchmark with 15,588 images of 65 everyday office and home objects in 4 domains: art (2,427), clipart (4,365), product (4,439) and real world (4,357). The class set is shared across domains. The release has no official split, so the configs define one: ``"random"`` (default) Stratified 80/20 train/test split inside every (domain, class) pair. Images are assigned by a SHA-256 of their bytes, so the split is reproducible. ``"art"``, ``"clipart"``, ``"product"``, ``"real_world"`` Leave-one-domain-out: ``train`` is every image of the other three domains and ``test`` is every image of the named domain. Every example has ``image`` (RGB, variable size), ``label`` (65 classes) and ``domain`` (4 domains). Usage: train = OfficeHome(split="train") target_test = OfficeHome(config_name="clipart", split="test") """ VERSION = Version("1.0.0") DOMAINS = _DOMAINS CLASS_NAMES = ( "alarm_clock", "backpack", "batteries", "bed", "bike", "bottle", "bucket", "calculator", "calendar", "candles", "chair", "clipboards", "computer", "couch", "curtains", "desk_lamp", "drill", "eraser", "exit_sign", "fan", "file_cabinet", "flipflops", "flowers", "folder", "fork", "glasses", "hammer", "helmet", "kettle", "keyboard", "knives", "lamp_shade", "laptop", "marker", "monitor", "mop", "mouse", "mug", "notebook", "oven", "pan", "paper_clip", "pen", "pencil", "postit_notes", "printer", "push_pin", "radio", "refrigerator", "ruler", "scissors", "screwdriver", "shelf", "sink", "sneakers", "soda", "speaker", "spoon", "tv", "table", "telephone", "toothbrush", "toys", "trash_can", "webcam", ) # fmt: skip BUILDER_CONFIGS = [ BuilderConfig( name=_RANDOM, description="Stratified 80/20 train/test split within every (domain, class) pair." ), *leave_one_domain_out_configs( "Office-Home", _DOMAINS, "Leave-one-domain-out: train on the other domains, test on every image of '{domain}'.", ), ] DEFAULT_CONFIG_NAME = _RANDOM SOURCE = DatasetSource( homepage="https://www.hemanthdv.org/officeHomeDataset.html", assets={ "archive": DownloadInfo( url=google_drive_url("0B81rNlvomiwed0V1YUxQdC1uOTg", "0-2SNWq0CDAuWOBRRBL7ZZsw"), filename="OfficeHomeDataset_10072016.zip", ), }, license="Custom fair-use notice, non-commercial research and educational use only", citation="""@inproceedings{venkateswara2017deep, title={Deep Hashing Network for Unsupervised Domain Adaptation}, author={Venkateswara, Hemanth and Eusebio, Jose and Chakraborty, Shayok and Panchanathan, Sethuraman}, booktitle={Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition (CVPR)}, pages={5018--5027}, year={2017}}""", ) def _info(self): return DatasetInfo( description=( "Office-Home contains 15,588 images of 65 office and home object classes in 4 domains: " f"art, clipart, product and real world. 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 _candidate_splits(self) -> list[str]: return [Split.TRAIN, Split.TEST] def _split_generators(self): info = self._normalize_download_info(self._source()["assets"]["archive"], asset_name="archive") download_dir = getattr(self, "_raw_download_dir", None) or _default_dest_folder() archive_path = Path(bulk_download([info], dest_folder=download_dir)[0]) return [ SplitGenerator(name=split, gen_kwargs={"archive_path": archive_path, "split": split}) for split in self._candidate_splits() ] def _iter_images(self, archive): """Yield ``(key, member, label, domain)`` for every image, in archive order.""" label_feature = self.info.features["label"] domain_feature = self.info.features["domain"] for key, member in image_members(archive, self.DOMAINS).items(): domain, class_name = key.split("/")[:2] class_name = normalize_name(class_name) if class_name not in label_feature.names: raise ValueError(f"OfficeHome: unexpected class folder {class_name!r} in {member}.") yield key, member, label_feature.str2int(class_name), domain_feature.str2int(domain) def _generate_examples(self, archive_path, split): with zipfile.ZipFile(archive_path) as archive: if self.config.name == _RANDOM: # Both splits need the same test set, so hash the images once per builder. if getattr(self, "_test_keys", None) is None: self._test_keys = stratified_test_keys( ( (key, archive.read(member), (domain, label)) for key, member, label, domain in self._iter_images(archive) ), _TEST_FRACTION, ) test_keys = self._test_keys in_test = lambda key, domain: key in test_keys # noqa: E731 else: held_out = self.info.features["domain"].str2int(self.config.name) in_test = lambda key, domain: domain == held_out # noqa: E731 for key, member, label, domain in self._iter_images(archive): if in_test(key, domain) == (split == Split.TEST): yield key, {"image": ensure_rgb(archive.read(member)), "label": label, "domain": domain}