Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request] Augementation config file #415

Open
bkanuka opened this issue Sep 12, 2019 · 8 comments
Open

[Feature Request] Augementation config file #415

bkanuka opened this issue Sep 12, 2019 · 8 comments
Labels
TODO Planned to be added to the library or changed in the future

Comments

@bkanuka
Copy link

bkanuka commented Sep 12, 2019

It would be great to be able to define augmentation using some kind of config file. I imagine YAML would for well. For example a config file could look like:

sequential:
    fliplr:
        arg: 0.5
    flipud:
        arg: 0.2
    sometimes:
        cropandpad:
            percent: (-0.05, 0.1)
            pad_mode: all
            pad_cval: (0, 255)
...
@aleju aleju added the TODO Planned to be added to the library or changed in the future label Sep 12, 2019
@aleju
Copy link
Owner

aleju commented Sep 12, 2019

Yes, I intended to add that probably in the version after 0.3.0 (i.e. after the next release).

@lamhoangtung
Copy link

@aleju I actually did pretty much like this with my text generator. Here is my code (which is definitely very messy :P), hope this could help

def process_list_augment(input_list, cval, random_state, is_binary):
    """Convert dictionary augmentation configs into augmentation object"""
    current_pipeline = []
    for _, each in enumerate(input_list):
        function_name = next(iter(each))
        augment_param = each[function_name]
        if isinstance(augment_param, list):
            prob = 1
            for each_element in augment_param:
                if isinstance(each_element, dict) and 'prob' in each_element.keys():
                    prob = each_element['prob']
                    augment_param.remove(each_element)
                    break
            big_object = None
            if function_name == 'Sequential':
                for each_element in augment_param:
                    if isinstance(each_element, dict) and 'random_order' in each_element.keys():
                        random_order = each_element['random_order']
                        augment_param.remove(each_element)
                        big_object = iaa.Sequential(process_list_augment(
                            input_list=augment_param, cval=cval,
                            random_state=random_state, is_binary=is_binary), random_order=random_order, random_state=random_state)
            elif function_name == 'SomeOf':
                for each_element in augment_param:
                    if isinstance(each_element, dict) and 'n' in each_element.keys():
                        n = each_element['n']
                        augment_param.remove(each_element)
                        big_object = iaa.SomeOf(literal_eval(n), process_list_augment(
                            input_list=augment_param, cval=cval, random_state=random_state, is_binary=is_binary), random_state=random_state)
            elif function_name == 'OneOf':
                big_object = iaa.OneOf(process_list_augment(
                    input_list=augment_param, cval=cval, random_state=random_state, is_binary=is_binary), random_state=random_state)
            if prob:
                big_object = iaa.Sometimes(
                    prob, big_object, random_state=random_state)
            current_pipeline.append(big_object)
        elif isinstance(augment_param, dict):
            prob = augment_param.pop('prob', None)
            for key, value in augment_param.items():
                if isinstance(value, str) and value[0] == '(' and value[-1] == ')' and len(value) > 2:
                    augment_param[key] = literal_eval(value)
            try:
                augment_function = getattr(iaa, function_name)
            except AttributeError:
                print(function_name, 'not found in imgaug lib, using custom')
                augment_function = globals()[function_name]
            required_param = list(inspect.getargspec(augment_function))[0]
            if 'cval' in required_param:
                augment_param['cval'] = cval
            if 'is_binary' in required_param:
                augment_param['is_binary'] = is_binary
            augment_param['random_state'] = random_state
            augment_object = augment_function(**augment_param)
            if prob is not None:
                augment_object = iaa.Sometimes(
                    prob, augment_object, random_state=random_state)
            current_pipeline.append(augment_object)
    return current_pipeline

@lamhoangtung
Copy link

Also, here is what my yaml config file looks like:

augmentation_pipeline:
    - Sequential:
        - random_order: False
        - ItalicizeLine:
            prob: 0.3
        - RotateLine:
            prob: 0.3
        - OneOf:
            - Pad:
                percent: ((0.05, 0.2),(0.05, 0.2),(0.05, 0.2),(0.05, 0.2))
                pad_mode: constant
                pad_cval: 1
            - Pad:
                px: ((0, 50), (0, 300), (0, 50), (0, 300))
                pad_mode: constant
                pad_cval: 1
        - TransformPerspective:
            prob: 0.3
            scale: (0.1, 0.25)
            keep_size: False
        - ElasticTransformation:
            prob: 0.3
            alpha: (0, 1.5)
            sigma: (0.4, 0.6) 
        - Skeletonize:
            prob: 0.02
        - Invert:
            p: 0.1
            max_value: 1

I can submit a PR, but I don't know where to start injecting my function yet @aleju :P

@aleju
Copy link
Owner

aleju commented Oct 26, 2019

Writing a proper config parsing method (or a few of them, one per augmenter) is not that straight forward and has quite some requirements, mainly:

  • The method should require as few unittests as possible. If its requires one method per augmenter then that will require implementing many new unittests, which costs a lot of time.
  • The method should not require adding an abstract method to imgaug.augmenters.meta.Augmenter as that would break all augmenters implemented by users.
  • The method should automatically be able to parse configs for new augmenters without any changes. Requiring changes would (a) create frequent bugs by forgetting to change these things when adding new augmenters, (b) make it harder for users to implement their own augmenters and (c) create frequent conflicts between different PRs.
  • The method should be able to handle new augmenters by users, even when their name is unknown.
  • The method must be able to deal with augmenters having children (possibly in multiple different lists). These augmenters again must be allowed to have their own children.
  • The method should be able to handle parameters from imgaug.parameters.
  • The method should be able to handle the various shortcuts for parameters, such as (0.0, 1.0) for Uniform(0.0, 1.0).
  • The method should be able to handle non-augmenter classes. E.g. Voronoi accepts classes for point sampling that aren't augmenters.
  • The method should be able to handle renaming of augmenters and parameters between versions of the library, as well as removals/additions of parameters without crashing.
  • The method shouldn't only work with yaml as some people might use other config file formats.
  • The method shouldn't have many augmenter-specific parsing rules as nobody could memorize these.
  • If at any point the library adds GPU support, the method should allow to add this to the parsing without breaking previous config files.

It's not that hard to quickly come up with a parsing method. But it is equally easy to violate one or more of the above points and then e.g. suddenly spend ages of time writing unittests for dozens of augmenters.

@lamhoangtung
Copy link

Damn :P. Sound kind da overwhelming for me lul

@caterpillars
Copy link

You could break your code down into 2 or more modules, one of them being your configuration data (ie, the constructed sequence), and then import it into your main script.

@jspaezp
Copy link

jspaezp commented Jul 12, 2020

I think I did something that fulfills the requirements (proof of concept, not PR right now) ...

Right now it is able to generate the example labelled "Heavy Augmentations" in the documentation (https://imgaug.readthedocs.io/en/latest/source/examples_basics.html) by using an equivalent yaml file: https://gist.github.com/jspaezp/6152733a644b8f6fe9b985b58c856997#file-complex_yaml_001-yaml only requiring to define the sometimes lambda function in the python call environment.

It more accurately parses the yaml and given a series of locations (similar to a PATH on the system level), looks for the names that match callable elements and greedily does the call.

https://gist.github.com/jspaezp/6152733a644b8f6fe9b985b58c856997#file-yaml_parser-py
(right now it has a lot of print statements for debugging purposes... but they should be easy to remove or make conditional on a debug/verbose argument).

right now I'm testing it with pytest and works fine, there is definitely some more work to do regarding unit tests, since I don't really know how to test if two augmenters are equivalent

let me know what you think.
Best,
Sebastian

@copaah
Copy link

copaah commented Nov 23, 2020

Any updates on this issue?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
TODO Planned to be added to the library or changed in the future
Projects
None yet
Development

No branches or pull requests

6 participants