Download dataloader2.py from bctnry/sanime2x: direct link, hf CLI and curl.
- Browser
- Download file 5.51 kB
-
https://huggingface.co/bctnry/sanime2x/resolve/main/dataloader2.py
- Command line
-
hf download hf://bctnry/sanime2x/dataloader2.py
-
curl -L -o dataloader2.py https://huggingface.co/bctnry/sanime2x/resolve/main/dataloader2.py
5.51 kB
| import pathlib | |
| import time | |
| import torch | |
| import numpy as np | |
| from PIL import Image | |
| SPLIT_SEED = 20260921 # fixed train/val split; same 100 val images every run | |
| CACHE_FILE = 'image_cache.pt' | |
| class SAnime2XDataset: | |
| # loads every image into RAM once (uint8), then serves crops by slicing. | |
| # ~12 GB for all 2000 images; pass cache_limit to cap how many get cached | |
| # (uncached images fall back to jpeg decode on the fly). | |
| # the cache is persisted to image_cache.pt (~12 GB on disk) so subsequent | |
| # runs skip the ~90s decode pass. | |
| def __init__(self, p, patch_size=96, cache_limit=2000, data_augmentation=True): | |
| self.path = p | |
| self.patch_size = patch_size | |
| gen = torch.Generator().manual_seed(SPLIT_SEED) | |
| perm = torch.randperm(2000, generator=gen) | |
| self.train_idx = perm[:1900] | |
| self.val_idx = perm[1900:] | |
| self.train_size = 1900 | |
| self.val_size = 100 | |
| self.data_augmentation = data_augmentation | |
| cache = pathlib.Path(__file__).parent / CACHE_FILE | |
| n_avail = len(list(pathlib.Path(p).rglob('*.jpeg'))) | |
| if cache.exists() and cache_limit >= n_avail: | |
| t0 = time.time() | |
| blob = torch.load(cache, weights_only=True) | |
| # only trust the cache if the dataset on disk hasn't changed | |
| if blob.get('source') == str(p) and len(blob['images']) == n_avail: | |
| self.images = blob['images'] | |
| print(f'loaded {len(self.images)} cached images ' | |
| f'in {time.time()-t0:.1f}s') | |
| return | |
| print('cache stale/short -> rebuilding') | |
| self.images = {} | |
| files = sorted(pathlib.Path(p).rglob('*.jpeg')) | |
| assert len(files) == 2000, f'expected 2000 images, found {len(files)}' | |
| t0 = time.time() | |
| for k, f in enumerate(files): | |
| if k >= cache_limit: | |
| break | |
| img = np.asarray(Image.open(f)).copy() | |
| # store as (C, H, W) uint8 for fast slicing; lazy conversion to float | |
| self.images[k] = torch.from_numpy(img).permute(2, 0, 1) | |
| print(f'cached {len(self.images)} images in {time.time()-t0:.1f}s') | |
| if len(self.images) == n_avail: | |
| torch.save({'source': str(p), 'images': self.images}, cache) | |
| def __get_image(self, i): | |
| if i in self.images: | |
| return self.images[i] | |
| img = Image.open(self.__path_of(i)) | |
| return torch.from_numpy(np.asarray(img).copy()).permute(2, 0, 1) | |
| def __path_of(self, i): | |
| return pathlib.Path(self.path) / f'part_{(i//200)+1:02}' / f'00{i:04}.jpeg' | |
| def __crop_pair(self, img, ps, zoom=2): | |
| _, h, w = img.shape | |
| x = torch.randint(w - ps - 1, (1,)).item() | |
| y = torch.randint(h - ps - 1, (1,)).item() | |
| cropped = img[:, y:y+ps, x:x+ps] | |
| c_img = Image.fromarray(cropped.permute(1, 2, 0).numpy()) | |
| shrinked = c_img.resize((ps//zoom, ps//zoom), resample=Image.Resampling.BILINEAR) | |
| enlarged = shrinked.resize((ps, ps), resample=Image.Resampling.BILINEAR) | |
| cropped_t = cropped.to(torch.float).div_(255) | |
| enlarged_t = torch.from_numpy(np.asarray(enlarged).copy()) \ | |
| .permute(2, 0, 1).to(torch.float).div_(255) | |
| # data augmentation | |
| if self.data_augmentation: | |
| k = torch.randint(4, (1,)).item() | |
| cropped_t = torch.rot90(cropped_t, k, dims=(1, 2)) | |
| enlarged_t = torch.rot90(enlarged_t, k, dims=(1, 2)) | |
| k2 = torch.randint(4, (1,)).item() | |
| if k2 % 2 == 1: | |
| cropped_t = torch.flip(cropped_t, (1,)) | |
| enlarged_t = torch.flip(enlarged_t, (1,)) | |
| if k2 // 2 == 1: | |
| cropped_t = torch.flip(cropped_t, (2,)) | |
| enlarged_t = torch.flip(enlarged_t, (2,)) | |
| return enlarged_t, cropped_t | |
| def get_train_random_crop(self, n=1, zoom=2): | |
| # return float [0,1] | |
| ii = self.train_idx[torch.randint(self.train_size, (n,))] | |
| es, cs = [], [] | |
| for i in ii: | |
| img = self.__get_image(int(i)) | |
| e, c = self.__crop_pair(img, self.patch_size, zoom=zoom) | |
| es.append(e) | |
| cs.append(c) | |
| return torch.stack(es), torch.stack(cs) | |
| def get_val_images(self, idx): | |
| # returns int [0,255] | |
| return [self.__get_image(int(self.val_idx[k])) for k in idx] | |
| def get_val_shrink_enlarged_images(self, idx, zoom=2): | |
| # returns int [0,255] | |
| out = [] | |
| for k in idx: | |
| img = self.__get_image(int(self.val_idx[k])) | |
| pil = Image.fromarray( | |
| img.permute(1, 2, 0).numpy()) | |
| shrinked = pil.resize((pil.size[0]//zoom, pil.size[1]//zoom), | |
| resample=Image.Resampling.BILINEAR) | |
| enlarged = shrinked.resize(pil.size, | |
| resample=Image.Resampling.BILINEAR) | |
| out.append(torch.from_numpy(np.asarray(enlarged).copy()) | |
| .permute(2, 0, 1)) | |
| return out | |
| def get_val_random_image(self, n=1): | |
| return self.get_val_images(torch.randint(self.val_size, (n,))) | |
| def get_train_images(self, idx): | |
| # returns int [0, 255] | |
| return [self.__get_image(int(self.train_idx[k])) for k in idx] | |
| def get_train_random_image(self, n=1): | |
| # returns int [0, 255] | |
| return self.get_train_images(torch.randint(self.train_size, (n,))) | |