diff --git a/ChangeLog.txt b/ChangeLog.txt index e7c3b7d..f0e2500 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -4,4 +4,5 @@ Oct 14, 2022 3. Refactored ImageGroupReader 4. generate_patches normalizes the reader data once. 5. window_size -> patch_size -6. step_size -> stride \ No newline at end of file +6. step_size -> stride +7. 2d mode added. \ No newline at end of file diff --git a/README.md b/README.md index a66cc4e..00a9b43 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ # MapDatasetGenerator Generate and load dataset of road network maps. -# Quick start + # Installation from pip @@ -62,23 +62,32 @@ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(messag handler.setFormatter(formatter) root.addHandler(handler) -from mapdataset import ImageGroupReader, single_layer_converter, MapsDataset, MapReader +from mapdataset import ImageGroupReader, single_layer_converter, MapsDataset, MapReader, ImageUtils +dillFolder = "./data/output/SF_Layered/32x32/group-1280-stride-10" + +mapsDataset = MapsDataset( + patch_size=(32, 32), + stride=10, + sample_group_size=1280, + converter=single_layer_converter, + outputDir="./data/output" + ) + +mapsDataset.loadPatches("./data/output/SF_Layered/32x32/group-1280-stride-10") +patchNo = randint(0, len(mapsDataset)) +logging.info(f"reading patch {patchNo}") +patch = mapsDataset[patchNo] + + +im = ImageUtils.TorchNpPatchToPILImgGray(patch) +path = os.path.join(dillFolder, f"{patchNo}.png") +im.save(path) + +``` -dillFolder = "./data/output/SF_Layered/32x32/stride-10" -nGroups = 0 -# Iterate directory -for path in os.listdir(dillFolder): - # check if current path is a file - if os.path.isfile(os.path.join(dillFolder, path)) and path.endswith(".dill"): - nGroups += 1 +# Using for training -for i in range(nGroups): - reader = ImageGroupReader(dillFolder) - data = reader.load_group(groupNo=i) - patchImgArray = reader.asImg(data[0]) - im = Image.fromarray(patchImgArray) - path = os.path.join(dillFolder, f"{i}-0.png") - im.save(path) -``` \ No newline at end of file +1. Create patches if you already do not have them +2. Create a MapsDataset object and load patches. Now you can use the dataset object as a regular Pytorch dataset or use it with a Dataloader. diff --git a/test.py b/generate_patches.py similarity index 60% rename from test.py rename to generate_patches.py index a921e44..2dd0e8c 100644 --- a/test.py +++ b/generate_patches.py @@ -29,18 +29,18 @@ mapsDataset.generate_patches(sfMap) #This will generate dill files which contain the saved sample lists. -dillFolder = "./data/output/SF_Layered/32x32/stride-10" -nGroups = 0 -# Iterate directory -for path in os.listdir(dillFolder): - # check if current path is a file - if os.path.isfile(os.path.join(dillFolder, path)) and path.endswith(".dill"): - nGroups += 1 - -for i in range(nGroups): - reader = ImageGroupReader(dillFolder) - data = reader.load_group(groupNo=i) - patchImgArray = reader.asImg(data[0]) - im = Image.fromarray(patchImgArray) - path = os.path.join(dillFolder, f"{i}-0.png") - im.save(path) +# dillFolder = "./data/output/SF_Layered/32x32/stride-10" +# nGroups = 0 +# # Iterate directory +# for path in os.listdir(dillFolder): +# # check if current path is a file +# if os.path.isfile(os.path.join(dillFolder, path)) and path.endswith(".dill"): +# nGroups += 1 + +# for i in range(nGroups): +# reader = ImageGroupReader(dillFolder) +# data = reader.load_group(groupNo=i) +# patchImgArray = reader.asImg(data[0]) +# im = Image.fromarray(patchImgArray) +# path = os.path.join(dillFolder, f"{i}-0.png") +# im.save(path) diff --git a/pyproject.toml b/pyproject.toml index ae80e6f..0ecdd8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "MapDatasetGenerator" -version = "0.0.1" +version = "0.0.2" description = "Map dataset generator for learning map representations and generation" authors = ["Ishaan ", "Golam Md Muktadir "] readme = "README.md" diff --git a/read.py b/read.py deleted file mode 100644 index 329434c..0000000 --- a/read.py +++ /dev/null @@ -1,24 +0,0 @@ -# Script to read dill data objects as numpy arrays. -import dill -import numpy as np -from PIL import Image -import os - -from mapdataset import ImageGroupReader - - -dillFolder = "./data/output/SF_Layered/32x32/stride-10" -nGroups = 0 -# Iterate directory -for path in os.listdir(dillFolder): - # check if current path is a file - if os.path.isfile(os.path.join(dillFolder, path)) and path.endswith(".dill"): - nGroups += 1 - -for i in range(nGroups): - reader = ImageGroupReader(dillFolder) - data = reader.load_group(groupNo=i) - patchImgArray = reader.asImg(data[0]) - im = Image.fromarray(patchImgArray) - path = os.path.join(dillFolder, f"{i}-0.png") - im.save(path) diff --git a/read_patches.py b/read_patches.py new file mode 100644 index 0000000..25cf672 --- /dev/null +++ b/read_patches.py @@ -0,0 +1,56 @@ +# Script to read dill data objects as numpy arrays. +from PIL import Image +import os +import sys +import logging +from random import randint + +root = logging.getLogger() +root.setLevel(logging.DEBUG) + +handler = logging.StreamHandler(sys.stdout) +handler.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +handler.setFormatter(formatter) +root.addHandler(handler) + +from mapdataset import ImageGroupReader, single_layer_converter, MapsDataset, MapReader, ImageUtils + + + + +dillFolder = "./data/output/SF_Layered/32x32/group-1280-stride-10" +# nGroups = 0 +# # Iterate directory +# for path in os.listdir(dillFolder): +# # check if current path is a file +# if os.path.isfile(os.path.join(dillFolder, path)) and path.endswith(".dill"): +# nGroups += 1 + +# for i in range(nGroups): +# reader = ImageGroupReader(dillFolder) +# data = reader.load_group(groupNo=i) +# patchImgArray = reader.asImg(data[0]) +# im = Image.fromarray(patchImgArray) +# path = os.path.join(dillFolder, f"{i}-0.png") +# im.save(path) + +mapsDataset = MapsDataset( + patch_size=(32, 32), + stride=10, + sample_group_size=1280, + converter=single_layer_converter, + outputDir="./data/output" + ) + +mapsDataset.loadPatches("./data/output/SF_Layered/32x32/group-1280-stride-10") +patchNo = randint(0, len(mapsDataset)) +logging.info(f"reading patch {patchNo}") +patch = mapsDataset[patchNo] + +# im = ImageUtils.PILPatchToPILImg(patch) +# im = ImageUtils.PILPatchToPILImg(patch) +im = ImageUtils.TorchNpPatchToPILImgGray(patch) +path = os.path.join(dillFolder, f"{patchNo}.png") +im.save(path) + \ No newline at end of file diff --git a/src/mapdataset/lib/ImageUtils.py b/src/mapdataset/lib/ImageUtils.py new file mode 100644 index 0000000..cc2c341 --- /dev/null +++ b/src/mapdataset/lib/ImageUtils.py @@ -0,0 +1,40 @@ +import numpy as np +from PIL import Image +import torch + +class ImageUtils: + + @staticmethod + def PILPatchToPILImg(patch): + data = np.clip((patch + 1) / 2, 0, 1) * 255 + data = data.astype(np.uint8) + return Image.fromarray(data) + + @staticmethod + def TorchPatchToPILImg(patch): + # TODO fix dimensions + data = np.clip((patch + 1) / 2, 0, 1) * 255 + data = data.astype(np.uint8) + return Image.fromarray(data) + + @staticmethod + def TorchPatchToPILImg(t): + return Image.fromarray(np.array(((t.squeeze(0).permute(1, 2, 0)+1)/2).clip(0, 1)*255).astype(np.uint8)) + + + @staticmethod + def TorchPatchToPILImgGray(t): + return Image.fromarray(np.array(((t.squeeze()+1)/2).clip(0, 1)*255).astype(np.uint8), "L") + + @staticmethod + def TorchNpPatchToPILImgGray(t): + # both tensor and numpy arrays have squeeze method + return ImageUtils.TorchPatchToPILImgGray(t) + + @staticmethod + def PILImgToTorch(im): # batch with 1. Standardized into (-1, 1). (h, w, c) - > (c, h, w) + return torch.tensor(np.array(im.convert('RGB'))/255).permute(2, 0, 1).unsqueeze(0) * 2 - 1 + + @staticmethod + def PILGrayToTorch(im): # batch with 1. Standardized into (-1, 1). (h, w, c) - > (c, h, w) gray has no extra color channel + return torch.tensor(np.array(im.convert('L'))/255).unsqueeze(0) * 2 - 1 \ No newline at end of file diff --git a/src/mapdataset/lib/__init__.py b/src/mapdataset/lib/__init__.py index 780224c..1f28ab0 100644 --- a/src/mapdataset/lib/__init__.py +++ b/src/mapdataset/lib/__init__.py @@ -1,2 +1,3 @@ from .ImageGroupReader import ImageGroupReader +from .ImageUtils import ImageUtils from .maps import * \ No newline at end of file diff --git a/src/mapdataset/lib/maps.py b/src/mapdataset/lib/maps.py index 2ef8096..956f09c 100644 --- a/src/mapdataset/lib/maps.py +++ b/src/mapdataset/lib/maps.py @@ -53,7 +53,7 @@ def get_char(self, layer): # Modifications - Ishaan, Muktadir class MapsDataset(Dataset): - def __init__(self, patch_size, stride, sample_group_size, converter, outputDir="../data/output"): + def __init__(self, patch_size, stride, sample_group_size, converter, outputDir="../data/output", mode='gray'): self.outputDir = outputDir self.char_size = converter.char_size self.converter = converter @@ -61,34 +61,85 @@ def __init__(self, patch_size, stride, sample_group_size, converter, outputDir=" self.stride = stride self.sample_group_size = sample_group_size self.samples = [] + self.sampleGroupFiles = [] + self.currentGroup = None + self.currentGroupNo = None self.block_size = self.patch_size[0] * self.patch_size[1] - 1 - os.makedirs(self.outputDir, exist_ok=True) + self.mode = mode - + os.makedirs(self.outputDir, exist_ok=True) + self.__precomputedPatches = False def __len__(self): + if self.__precomputedPatches: + return len(self.sampleGroupFiles * self.sample_group_size) return len(self.samples) def __getitem__(self, idx): - sample = (self.samples[idx], self.samples[idx], self.samples[idx]) - return torch.from_numpy(np.array(sample)).unsqueeze(0) - #flat = torch.from_numpy(np.array(sample)).view(-1) - #flat = flat[self.perm].float() - #return flat + + if self.__precomputedPatches: + return torch.from_numpy(self.__getPreComputedSample__(idx)) + + if self.mode =='gray': + sample = (self.samples[idx]) # 1 channel + return torch.from_numpy(np.array(sample)).unsqueeze(0).unsqueeze(0) + else: + sample = (self.samples[idx], self.samples[idx], self.samples[idx]) # 3 channels + return torch.from_numpy(sample).unsqueeze(0) def add(self, mapReader): + mapReader.standardize(self.converter) for i in range(0, mapReader.size[0] - self.patch_size[0] + 1, self.stride): for j in range(0, mapReader.size[1] - self.patch_size[1] + 1, self.stride): - self.samples.append([[ - (self.converter.get_char(mapReader.data[i + x][j + y]) / (len(self.converter.char_groups) - 1)) * -2 + 1 - for y in range(self.patch_size[1])] - for x in range(self.patch_size[0])]) + self.samples.append(self.extractSample(mapReader, topLeft=(i, j))) + + + def loadPatches(self, patchDirectory): + #TODO + + groupFiles = [os.path.join(patchDirectory, f) + for f in os.listdir(patchDirectory) + if os.path.isfile(os.path.join(patchDirectory, f)) and + f.endswith(".dill")] + self.sampleGroupFiles = sorted(groupFiles, key=lambda f: self.filenameComparator(f)) + logging.info(f"Loading {len(self.sampleGroupFiles) * self.sample_group_size} patches from {patchDirectory}") + self.__precomputedPatches = True + + pass + - #Generate image patches and write to data/output directory - def generate_patches(self, mapReader, image_groups=3, outDirectory=None): - """_summary_ + def filenameComparator(self, f): + _, tail = os.path.split(f) + return int(tail.split(".")[0]) + + #region Generate image patches and write to data/output directory + + def __getPreComputedSample__(self, idx): + # file = self.sampleFiles[idx] + # find group + groupNo = idx // self.sample_group_size + if self.currentGroupNo != groupNo: + # we don't have the group in memory + logging.debug(f"Opening group file {self.sampleGroupFiles[groupNo]}") + with open(self.sampleGroupFiles[groupNo], "rb") as f: + self.currentGroup = dill.load(f) + + relativeIdx = idx % self.sample_group_size + logging.debug(f"patch {idx} is in group {groupNo} with relativeIdx {relativeIdx}") + return self.currentGroup[relativeIdx] + + + def __createDirectoryForPatches(self, mapReader, outDirectory=None): + + if outDirectory is None: + outDirectory = os.path.join(self.outputDir, mapReader.mapName, f"{self.patch_size[0]}x{self.patch_size[1]}", f"group-{self.sample_group_size}-stride-{self.stride}") + os.makedirs(outDirectory, exist_ok=True) + return outDirectory + + def generate_patches(self, mapReader, outDirectory=None): + """patches already have tensor like shape ready to be trained. No need to unsqueeze them. Args: mapReader (MapReader): reader for a single big map! @@ -97,9 +148,7 @@ def generate_patches(self, mapReader, image_groups=3, outDirectory=None): mapReader.standardize(converter=self.converter) - if outDirectory is None: - outDirectory = os.path.join(self.outputDir, mapReader.mapName, f"{self.patch_size[0]}x{self.patch_size[1]}", f"stride-{self.stride}") - os.makedirs(outDirectory, exist_ok=True) + outDirectory = self.__createDirectoryForPatches(mapReader, outDirectory) img_group_number = 0 for i in range(0, mapReader.size[0] - self.patch_size[0] + 1, self.stride): @@ -122,13 +171,7 @@ def generate_patches(self, mapReader, image_groups=3, outDirectory=None): def extractSample(self, mapReader, topLeft): i = topLeft[0] j = topLeft[1] - # sample = [ - # [ - # (self.converter.get_char(mapReader.data[i + x][j + y]) / (len(self.converter.char_groups) - 1)) * -2 + 1 # TODO this conversion should be done once in the original data instead of patches. - # for y in range(self.patch_size[1]) - # ] - # for x in range(self.patch_size[0]) - # ] + sample = [ [ mapReader.data[i + x][j + y] @@ -136,7 +179,11 @@ def extractSample(self, mapReader, topLeft): ] for x in range(self.patch_size[0]) ] - return np.asarray(sample) + imArr = np.asarray(sample) + if self.mode == "gray": + return np.expand_dims(np.expand_dims(imArr, axis=0), axis=0) + else: + return np.expand_dims(imArr, axis=0) def shuffle(self): diff --git a/src/mapdataset/read.py b/src/read.py similarity index 100% rename from src/mapdataset/read.py rename to src/read.py diff --git a/src/mapdataset/run.py b/src/run.py similarity index 100% rename from src/mapdataset/run.py rename to src/run.py