Source code for stable_datasets.images.semeion

import numpy as np
from PIL import Image as PILImage

from stable_datasets.schema import (
    ClassLabel,
    DatasetInfo,
    DatasetSource,
    DownloadInfo,
    Features,
    Version,
)
from stable_datasets.schema import Image as ImageFeature
from stable_datasets.utils import BaseDatasetBuilder


[docs] class Semeion(BaseDatasetBuilder): """Semeion handwritten digit classification dataset. Contains 1,593 grayscale images of handwritten digits (0-9). Each image is 16x16 pixels, represented by 256 binary pixel values in the original dataset. The original dataset has no predefined train/test split. This implementation uses NumPy's random generator with seed 42 to create a reproducible 80/20 split: - train: 1,274 images - test: 319 images Each example returns a PIL grayscale image and an integer label between 0 and 9. """ VERSION = Version("1.0.0") SOURCE = DatasetSource( homepage="https://archive.ics.uci.edu/dataset/178/semeion+handwritten+digit", license="CC BY 4.0", citation="""@misc{semeion_handwritten_digit_178, title = {{Semeion Handwritten Digit}}, year = {1998}, howpublished = {UCI Machine Learning Repository}, note = {{DOI}: https://doi.org/10.24432/C5SC8V} }""", assets={ "train": DownloadInfo( url="https://archive.ics.uci.edu/ml/machine-learning-databases/semeion/semeion.data" ), "test": DownloadInfo(url="https://archive.ics.uci.edu/ml/machine-learning-databases/semeion/semeion.data"), }, ) def _info(self): return DatasetInfo( description=("Semeion handwritten digits dataset containing 1,593 grayscale 16x16 images of digits 0-9."), features=Features( { "image": ImageFeature(), "label": ClassLabel(names=[str(i) for i in range(10)]), } ), supervised_keys=("image", "label"), homepage=self.SOURCE["homepage"], citation=self.SOURCE["citation"], license=self.SOURCE["license"], ) def _generate_examples(self, data_path, split): """Generate images and labels from the Semeion text file.""" data = np.loadtxt(data_path, dtype=np.float32) if data.shape != (1593, 266): raise ValueError(f"Unexpected Semeion shape: {data.shape}") pixels = data[:, :256] targets = data[:, 256:] if not np.all((pixels == 0) | (pixels == 1)): raise ValueError("Pixel values must be binary") if not np.all((targets == 0) | (targets == 1)): raise ValueError("Class indicators must be binary") if not np.all(targets.sum(axis=1) == 1): raise ValueError("Each example must have exactly one class") # Deterministic 80/20 split indices = np.random.default_rng(42).permutation(len(data)) train_size = int(0.8 * len(data)) if split == "train": selected_indices = indices[:train_size] elif split == "test": selected_indices = indices[train_size:] else: raise ValueError(f"Unknown split: {split}") for idx in selected_indices: pixel_row = pixels[idx] target_row = targets[idx] image_array = (pixel_row.reshape(16, 16) * 255).astype(np.uint8) image = PILImage.fromarray(image_array, mode="L") label = int(np.argmax(target_row)) yield ( int(idx), { "image": image, "label": label, }, )