Resize instead of crop

Hi, is there a data aug function in fastai v1 which will resize, altering the aspect ratio if necessary, the input images to the size passed into the ImageDataBunch, without applying any cropping? I have had a look and I haven’t been able find one.
In fastai 0.7 I used

crop_type=CropType.NO

to achieve this.
Thank you.

3 Likes

I’m actually not sure about any build it method.

But I use ImageMagick to do something similar. Here’s how I resize/crop them in different ways: https://arunoda.me/blog/getting-started-with-fastai

2 Likes

I’m interested in this as well.

@arunoda, thanks for the pointer. I looked at your notebook and it looks like you are using “get_transforms()” which looks to me to include crop by default. Are you doing anything special to prevent crop?

@keijik get_transforms() does not crop AFAIK. Or at least with default params.
It does tilt and some similar transformations.

The cropping is done by the size param. Since I’m using a square image, no cropping happens with size param.

Hey,

I too am interested in figuring out if there is any specified method to do so. I ended up changing the code in vision/image.py in the apply_tfms function from default cropping when size given to the following

if size:
    x.resize(size)
    x.refresh()

I am not sure if that is the correct way to do so.

Looking at the code, it seems like the DataBunch constructor will pass through a do_crop parameter to apply_tfms which might also do the trick.

I’ll try that.

Just pushed a new version of the lib where @sgugger has added a resize_method param, which you can pass ResizeMethod.SQUISH to resize by squishing.

10 Likes

Great - thank you! Amazing how quickly fastai reponds to feedback!

I was in the middle of creating an isolated notebook to show the issue I was having when I saw this :D. I no longer get the exception using resize_method (and no transforms).

Now, with both ResizeMethod.PAD and ResizeMethod.SQUISH I get higher error rates (about 15%) in training compared to manually resize/padding the images with imagemagick (about 4%). I will report if I find what is causing this.

Thank you again!

It does crop by default if you specify a square (or int) size and your source images are not square.

3 Likes

@arunoda, the result of get_transforms() is below. Based on this and the transformed images, I assumed it does Crop but thanks @jeremy for confirming

([RandTransform(tfm=TfmCrop (crop_pad), kwargs={'row_pct': (0, 1), 'col_pct': (0, 1)}, p=1.0, resolved={}, do_run=True, is_random=True),
RandTransform(tfm=TfmAffine (flip_affine), kwargs={}, p=0.5, resolved={}, do_run=True, is_random=True),
RandTransform(tfm=TfmCoord (symmetric_warp), kwargs={'magnitude': (-0.2, 0.2)}, p=0.75, resolved={}, do_run=True, is_random=True),
RandTransform(tfm=TfmAffine (rotate), kwargs={'degrees': (-10.0, 10.0)}, p=0.75, resolved={}, do_run=True, is_random=True),
RandTransform(tfm=TfmAffine (zoom), kwargs={'row_pct': (0, 1), 'col_pct': (0, 1), 'scale': (1.0, 1.1)}, p=0.75, resolved={}, do_run=True, is_random=True),
RandTransform(tfm=TfmLighting (brightness), kwargs={'change': (0.4, 0.6)}, p=0.75, resolved={}, do_run=True, is_random=True),
RandTransform(tfm=TfmLighting (contrast), kwargs={'scale': (0.8, 1.25)}, p=0.75, resolved={}, do_run=True, is_random=True)],
[RandTransform(tfm=TfmCrop (crop_pad), kwargs={}, p=1.0, resolved={}, do_run=True, is_random=True)])

Thank you for the quick addition of resize_method param. I am looking at the new documentation

For the following image I was expecting pad to place black bars above and below the image and squish to resize ignoring the original aspect ratio.

However all the images look identical to me, am I missing something?

1 Like

The parameter is what Jeremy said - resize_method. I checked the code before trying it. Ditto for the enum (ResizeMethod, not ResizeMtd).

Perfect, thank you.

Yes, Jeremy changed the name without updating the docs, you can address your complains to him ;);

1 Like

I’m really new to python so I would appreciate it if someone could walk me though the logic behind the following taken from data.py

def _prep_tfm_kwargs(tfms, kwargs):
    default_rsz = ResizeMethod.SQUISH if ('size' in kwargs and is_listy(kwargs['size'])) else ResizeMethod.CROP
    resize_method = getattr(kwargs, 'resize_method', default_rsz)
    if resize_method <= 2: tfms = [crop_pad()] + tfms
    kwargs['resize_method'] = resize_method
    return tfms, kwargs

Specifically the first two statements. Is it possible for the below to return anything but default_rsz?

getattr(kwargs, 'resize_method', default_rsz)

If not what is the reason that the size argument to _prep_tfm_kwargs have to be a list or a tuple to use the ResizeMethod.SQUISH?

Thank you.

It’s assuming that if it’s a list [u, v], that u != v - i.e., if it’s square, people should not bother to make a list [u,u] but instead just pass u.

And that sets the default - which can be overridden by what people pass in explicitly as resize_method.

Hope that helps!

Thank you, just to confirm, if size is not a list will the below default to ResizeMethod.CROP?

default_rsz = ResizeMethod.SQUISH if ('size' in kwargs and is_listy(kwargs['size'])) else ResizeMethod.CROP

And you are saying that

getattr(kwargs, 'resize_method', default_rsz)

can get the value for the ‘resize_method’ keyword from kwargs even though kwargs it is not an object?
I ask because in my testing this is not the case.

getattr works fine with dictionaries, and kwargs is one. What this does is, it tries to get kwargs['resize_method'] but it if doesn’t exists, it uses default_rsz. A longer way to rewrite it would be

resize_method = kwargs['resize_method'] if 'resize_method' in kwargs else default_rsz

Thank you for clarifying. I think I must have something wrong with either my config or my understanding, because for me getattr does not appear to be working on dictionaries, the output from the following

testDict = {'key1':1,'key2':2}
print(type(testDict))
print(testDict.get('key1'))
print(getattr(testDict,'key1','notakey'))

is

< class 'dict'>
1 
notakey

and as you have pointed out the last 2 lines should be the same.

See! You know more python than I do :wink:
I wanted to do kwargs.get('resize_method', default_rsz) and got mixed up, sorry about that. Will fix.