pixel_gen / src /data /dataset /image_txt.py
linxin02's picture
Upload lx_gan project
cef8b68 verified
Raw
History Blame Contribute Delete
3.98 kB
import hashlib
import os
import pickle
import torch
from torch.utils.data import Dataset
from torchvision.transforms import CenterCrop, Normalize, Resize
from torchvision.transforms.functional import to_tensor
from PIL import Image
EXTs = ['.png', '.jpg', '.jpeg', ".JPEG"]
def is_image_file(filename):
return any(filename.endswith(ext) for ext in EXTs)
def _index_root(root: str, cache_path: str = None, require_text: bool = True):
"""Walk ``root`` once and return a list of (image_path, text_path) pairs.
The result is cached on disk so subsequent epochs / DDP workers do not
repeat the walk. Caching is keyed by ``root`` + mtime of ``root`` so the
cache invalidates when the top-level directory changes.
"""
if cache_path is None:
key = hashlib.md5(root.encode("utf-8")).hexdigest()[:16]
cache_path = os.path.join("/tmp", f"imagetext_index_{key}.pkl")
try:
st = os.stat(root)
mtime = st.st_mtime
except FileNotFoundError:
raise FileNotFoundError(f"dataset root does not exist: {root}")
if os.path.exists(cache_path):
try:
with open(cache_path, "rb") as f:
cached = pickle.load(f)
if cached.get("root") == root and cached.get("mtime") == mtime:
return cached["pairs"]
except Exception:
pass
pairs = []
for dir_, _subdirs, files in os.walk(root):
# split images from txts using a set so we don't stat per file
files_set = set(files)
for file in files:
if not is_image_file(file):
continue
base, _ = os.path.splitext(file)
txt_name = base + ".txt"
if require_text and txt_name not in files_set:
continue
image_path = os.path.join(dir_, file)
text_path = os.path.join(dir_, txt_name) if txt_name in files_set else None
pairs.append((image_path, text_path))
try:
with open(cache_path, "wb") as f:
pickle.dump({"root": root, "mtime": mtime, "pairs": pairs}, f,
protocol=pickle.HIGHEST_PROTOCOL)
except Exception:
pass
return pairs
class ImageText(Dataset):
"""Image-text dataset.
Behavior change vs. the original implementation: the ``__init__`` no longer
opens and reads every ``.txt`` file -- it only indexes file *paths*. The
actual text is read on demand in ``__getitem__``. An index of (image,
text) path pairs is also cached on disk under ``/tmp`` so that subsequent
runs / DDP worker processes start instantly.
"""
def __init__(self, root: str, resolution: int, cache_path: str = None,
require_text: bool = True):
super().__init__()
self.root = root
self.pairs = _index_root(root, cache_path=cache_path, require_text=require_text)
self.resize = Resize(resolution)
self.center_crop = CenterCrop(resolution)
self.normalize = Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
@property
def image_paths(self): # backward-compat for any external user
return [p[0] for p in self.pairs]
def __getitem__(self, idx: int):
image_path, text_path = self.pairs[idx]
if text_path is not None:
try:
with open(text_path, "r") as f:
text = f.read()
except Exception:
text = ""
else:
text = ""
pil_image = Image.open(image_path).convert('RGB')
pil_image = self.resize(pil_image)
pil_image = self.center_crop(pil_image)
raw_image = to_tensor(pil_image)
normalized_image = self.normalize(raw_image)
metadata = {
"image_path": image_path,
"prompt": text,
"raw_image": raw_image,
}
return normalized_image, text, metadata
def __len__(self):
return len(self.pairs)