Cropping using transforms.Compose(transform_list)

Hello

I am using a dataloader and I am creating a transform list to do all the transformations on the tensors once I read them before passing to the network.

Currently, I was using random cropping by providing
transform_list = [transforms.RandomCrop((height, width))] + transform_list if crop else transform_list
I want to change the random cropping to a defined normal cropping for all images starting from one x,y coordinate to a certain width and height (basically I want to take the bottom half of the image and remove the upper one)

I searched through the functions provided by the transforms in torch and I found this transforms.functional.crop(), the problem that this function has to take the image as an argument, however, I still don’t have the image since I am still creating my transform list, is there a way to do so ?

PS: My image size is 720x1280

Here is how I do it:

def get_all_data_loaders(conf):
    batch_size = conf['batch_size']
    num_workers = conf['num_workers']
    if 'new_size' in conf:
        new_size_a = new_size_b = conf['new_size']
    else:
        new_size_a = conf['new_size_a']
        new_size_b = conf['new_size_b']
    height = conf['crop_image_height']
    width = conf['crop_image_width']

    train_loader_a = get_data_loader_folder(os.path.join(conf['data_root'], 'trainA'), batch_size, True,
                                         new_size_a, height, width, num_workers, True)
    test_loader_a = get_data_loader_folder(os.path.join(conf['data_root'], 'testA'), batch_size, False,
                                         new_size_a, new_size_a, new_size_a, num_workers, True)
    train_loader_b = get_data_loader_folder(os.path.join(conf['data_root'], 'trainB'), batch_size, True,
                                          new_size_b, height, width, num_workers, True)
    test_loader_b = get_data_loader_folder(os.path.join(conf['data_root'], 'testB'), batch_size, False,
                                          new_size_b, new_size_b, new_size_b, num_workers, True)

def get_data_loader_folder(input_folder, batch_size, train, new_size=None,
                           height=256, width=256, num_workers=4, crop=True):
    transform_list = [transforms.ToTensor(),
                      transforms.Normalize((0.5, 0.5, 0.5),
                                           (0.5, 0.5, 0.5))]
    # Only for R2NR implementation - crop half of the image
    transform_list = [transforms.functional.crop(360, 640, 360, 640)] + transform_list if crop else transform_list
    #transform_list = [transforms.RandomCrop((height, width))] + transform_list if crop else transform_list
    transform_list = [transforms.Resize(new_size)] + transform_list if new_size is not None else transform_list
    transform_list = [transforms.RandomHorizontalFlip()] + transform_list if train else transform_list
    transform = transforms.Compose(transform_list)
    dataset = ImageFolder(input_folder, transform=transform)
    loader = DataLoader(dataset=dataset, batch_size=batch_size, shuffle=train, drop_last=True, num_workers=num_workers)
    return loader
1 Like

The solution that comes quickly to mind is that you use the functional crop in the __getitem__ function of your dataset class.

hi
yes for sure this is an option I already thought of

But the problem that I want to do the cropping before the resizing and after the normalization, so exactly in this place
but if I do it in the__getitem__it will be like this

class ImageFolder(data.Dataset):

    def __init__(self, root, transform=None, return_paths=False,
                 loader=default_loader):
        imgs = sorted(make_dataset(root))
        if len(imgs) == 0:
            raise(RuntimeError("Found 0 images in: " + root + "\n"
                               "Supported image extensions are: " +
                               ",".join(IMG_EXTENSIONS)))

        self.root = root
        self.imgs = imgs
        self.transform = transform
        self.return_paths = return_paths
        self.loader = loader

    def __getitem__(self, index):
        path = self.imgs[index]
        img = self.loader(path)
        if self.transform is not None:
            img = self.transform(img)
        if self.return_paths:
            return img, path
        else:
            return img

    def __len__(self):
        return len(self.imgs)

which will be done then either as the first transformation or the last one

I see. In that case I would modify the RandomCrop implementation and enable it to take the crop location. Then you can use it along with the Compose construct. The signature would be something like:
MyCrop(x,y,h,w)

RandomCrop is defined here https://github.com/pytorch/vision/blob/master/torchvision/transforms/transforms.py#L452

hey @vabh
Thanks for your reply, as testing matter, I did it in the getitem, but there is something weird
so here is my code

 class ImageFolder(data.Dataset):

    def __init__(self, root, transform=None, return_paths=False,
                 loader=default_loader):
        imgs = sorted(make_dataset(root))
        if len(imgs) == 0:
            raise(RuntimeError("Found 0 images in: " + root + "\n"
                               "Supported image extensions are: " +
                               ",".join(IMG_EXTENSIONS)))

        self.root = root
        self.imgs = imgs
        self.transform = transform
        self.return_paths = return_paths
        self.loader = loader

    def __getitem__(self, index):
        path = self.imgs[index]
        img = self.loader(path)
        if self.transform is not None:
            if "train" in self.root:
                print('image before crop: ', img.size)
                img = transforms.functional.crop(img, 360, 0, 720, 1280)
                print('image after crop: ', img.size)
            img = self.transform(img)
        if self.return_paths:
            return img, path
        else:
            return img

    def __len__(self):
        return len(self.imgs)

here is the output

image before crop: (1280, 720)
image after crop: (1280, 720)

So yes the image is cropped like I wanted (bottom half), but the size remained the same, and instead, the rest of the image is black
so my image looks like upper half black, bottom half what I want

Do you have any idea why is this ?

Okay I knew my mistake
I should do it like this

img = transforms.functional.crop(img, 360, 0, 360, 1280)
not
img = transforms.functional.crop(img, 360, 0, 720, 1280)

Another solution is to use PIL-image-resize function
img = self.loader(path)
img=img.resize(size, box=None), box is (w0, h0, w, h)

Hi Mostafa,
I have the same issue you had for customizing crop function, is it possible to kindly to send me your code that includes ImageFolder class and also when you call it. I’m very new to pytorch and I have no idea how to apply customized ImageFolder class.Thank you very much for your help.

I used the implementation of CenterCrop in transforms as a template and defined following class:

class Crop(object):

   def __init__(self, height, width):
       if isinstance(width, numbers.Number) and isinstance(height, numbers.Number):
           self.width = int(width)
           self.height = int(height)
       else:
           self.width = width
           self.height = height

   def __call__(self, img):
       width, _ = img.size
       return transforms.functional.crop(img, 0, width - self.width, self.height, self.width)

You can add this to your tranform_list by Crop(height, width)