Cropping using transforms.Compose(transform_list)

mhusseinsh: <p>Hello</p>
<p>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.</p>
<p>Currently, I was using random cropping by providing<br>
<code>transform_list = [transforms.RandomCrop((height, width))] + transform_list if crop else transform_list</code><br>
I want to change the random cropping to a defined normal cropping for all images starting from one <code>x,y</code> coordinate to a certain width and height (basically I want to take the bottom half of the image and remove the upper one)</p>
<p>I searched through the functions provided by the transforms in torch and I found this <code>transforms.functional.crop()</code>, 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 ?</p>
<p>PS: My image size is 720x1280</p>
<p>Here is how I do it:</p>
<pre><code class="lang-auto">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
</code></pre>

vabh: <p>The solution that comes quickly to mind is that you use the functional crop in the <code>__getitem__</code> function of your dataset class.</p>

mhusseinsh: <p>hi<br>
yes for sure this is an option I already thought of</p>
<p>But the problem that I want to do the cropping before the resizing and after the normalization, so exactly in this place<br>
but if I do it in the<code>__getitem__</code>it will be like this</p>
<pre><code class="lang-auto">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)
</code></pre>
<p>which will be done then either as the first transformation or the last one</p>

vabh: <p>I see. In that case I would modify the <code>RandomCrop</code> implementation and enable it to take the crop location. Then you can use it along with the <code>Compose</code> construct. The signature would be something like:<br>
<code>MyCrop(x,y,h,w)</code></p>
<p><code>RandomCrop</code> is defined here <a href="https://github.com/pytorch/vision/blob/master/torchvision/transforms/transforms.py#L452" rel="nofollow noopener">https://github.com/pytorch/vision/blob/master/torchvision/transforms/transforms.py#L452</a></p>

mhusseinsh: <p>hey <a class="mention" href="/u/vabh">@vabh</a><br>
Thanks for your reply, as testing matter, I did it in the <strong>getitem</strong>, but there is something weird<br>
so here is my code</p>
<pre><code class="lang-auto"> 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)
</code></pre>
<p>here is the output</p>
<pre><code class="lang-auto">image before crop: (1280, 720)
image after crop: (1280, 720)
</code></pre>
<p>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<br>
so my image looks like upper half black, bottom half what I want</p>
<p>Do you have any idea why is this ?</p>

mhusseinsh: <p>Okay I knew my mistake<br>
I should do it like this</p>
<p><code>img = transforms.functional.crop(img, 360, 0, 360, 1280)</code><br>
not<br>
<code>img = transforms.functional.crop(img, 360, 0, 720, 1280)</code></p>

Sunshine352: <p>Another solution is to use PIL-image-resize function<br>
<strong>img = self.loader(path)</strong><br>
img=img.resize(size, box=None), box is (w0, h0, w, h)</p>

Maryam: <p>Hi Mostafa,<br>
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.</p>

Rebeka: <p>I used the implementation of CenterCrop in transforms as a template and defined following class:</p>
<pre><code class="lang-auto">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)
</code></pre>
<p>You can add this to your tranform_list by Crop(height, width)</p>