Image-to-Image
sanime2x / dataloader2.py
bctnry's picture
Upload 13 files
c07d84c verified
Raw History Blame Contribute Delete
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,)))