Resize instead of crop


#1

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.


Resize but no crop (or flip)
(Arunoda Susiripala) #2

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


#3

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?


(Arunoda Susiripala) #4

@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.


#5

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.


#6

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.


(Jeremy Howard (Admin)) #7

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.


Resize but no crop (or flip)
#8

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!


(Jeremy Howard (Admin)) #9

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


#10

@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)])

#11

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?


#12

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


#13

Perfect, thank you.


#14

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


#15

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.


#16

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!


#17

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.


#18

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

#19

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.


#20

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.