A gist of affine transforms in pytorch
Category: vision

ncullen93: Hi all, Right now it’s not maximally efficient because i cast to and from numpy… Eventually I’ll implement this all in torch and then it can be performed efficiently on the gpu. I’ve spot-checked these transforms so I’m confident they aren’t blatantly wrong, but if you come across a bug then add a comment in the gist. Short example of the easiest use case <SCODE>import torch
affine_transform = Affine(rotation_range=30, translation_range=(0.2,0.2), zoom_range=(0.8,1.2))
x = torch.ones(10,3,50,50)
y = torch.ones(10,3,50,50)
data  = TensorDataset(x, y, co_transform=affine_transform)

x_sample, y_sample = data[0] # the transforms should be applied to both x and y
<ECODE>

apaszke: Nice! We’ll think how could that be integrated into torchvision. Note that numpy conversions are nearly free, because the array/tensor you get after the conversion shares the data with the original object.

kindlychung: I made something simpler: <SCODE>from PIL import Image
from scipy import misc
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage
from skimage.transform import warp, AffineTransform

f = misc.face(gray=True)
plt.hist(f.flatten())

class RandomAffineTransform(object):
    def __init__(self,
                 scale_range,
                 rotation_range,
                 shear_range,
                 translation_range
                 ):
        self.scale_range = scale_range
        self.rotation_range = rotation_range
        self.shear_range = shear_range
        self.translation_range = translation_range

    def __call__(self, img):
        img_data = np.array(img)
        scale_x = np.random.uniform(*self.scale_range)
        scale_y = np.random.uniform(*self.scale_range)
        scale = (scale_x, scale_y)
        rotation = np.random.uniform(*self.rotation_range)
        shear = np.random.uniform(*self.shear_range)
        translation = (
            np.random.uniform(*self.translation_range),
            np.random.uniform(*self.translation_range)
        )
        af = AffineTransform(scale=scale, shear=shear, rotation=rotation, translation=translation)
        img_data1 = warp(img_data, af.inverse)
        return Image.fromarray(img_data1 * 255)
<ECODE> I ended up adding some randomness into it: <SCODE>from PIL import Image
from scipy import misc
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage
from skimage.transform import warp, AffineTransform


class RandomAffineTransform(object):
    def __init__(self,
                 scale_range,
                 rotation_range,
                 shear_range,
                 translation_range
                 ):
        self.scale_range = scale_range
        self.rotation_range = rotation_range
        self.shear_range = shear_range
        self.translation_range = translation_range

    def __call__(self, img):
        img_data = np.array(img)
        h, w, n_chan = img_data.shape
        scale_x = np.random.uniform(*self.scale_range)
        scale_y = np.random.uniform(*self.scale_range)
        scale = (scale_x, scale_y)
        rotation = np.random.uniform(*self.rotation_range)
        shear = np.random.uniform(*self.shear_range)
        translation = (
            np.random.uniform(*self.translation_range) * w,
            np.random.uniform(*self.translation_range) * h
        )
        af = AffineTransform(scale=scale, shear=shear, rotation=rotation, translation=translation)
        img_data1 = warp(img_data, af.inverse)
        img1 = Image.fromarray(np.uint8(img_data1 * 255))
        return img1
<ECODE>

ncullen93: Cool! That’s very simple! The gist above is very old… If you see now the affine transforms in my torchsample package are now completely written in pytorch and thus can be included in nn.Module’s and/or run on gpu. Whatever works for you though!

arogozhnikov: 

Royi: Is there a built in way to do it in PyTorch?

Royi: imageRotate = lambda mI: mI.rotate((2 * imageRotAngle * np.random.rand(1)) - imageRotAngle) trainSetTransform = transforms.Compose([transforms.RandomCrop(28, padding = imageCropPad), transforms.Lambda(imageRotate), transforms.ToTensor()])

will_soon: <SCODE>transforms.Compose([
transforms.RandomSizedCrop(224),
transforms.RandomHorizontalFlip(),
transform.Affine(rotation_range=10,translation_range =0.1),
transforms.ToTensor()]
<ECODE> Do you know why? And the version of my pytorch is 0.3.