Repository: aleju/imgaug
Branch: master
Commit: 0101108d4fed
Files: 290
Total size: 6.1 MB
Directory structure:
gitextract_v3n8p6ds/
├── .codacy.yml
├── .github/
│ └── workflows/
│ ├── build_wheels.yml
│ ├── test_master.yml
│ └── test_pull_requests.yml
├── .gitignore
├── .pylintrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── MANIFEST.in
├── README.md
├── bandit.yml
├── changelogs/
│ ├── 0.3.0/
│ │ ├── v0.3.0.cleaned.md
│ │ └── v0.3.0.uncleaned.md
│ ├── 0.4.0/
│ │ ├── 20191003_reworked_aug_methods.md
│ │ ├── 20191016_pooling_affects_maps.md
│ │ ├── 20191026_reworked_quantization.md
│ │ ├── 20191027_improve_invert.md
│ │ ├── 20191111_pickleable.md
│ │ ├── 20191113_iterable_augmentables.md
│ │ ├── 20191610_crop_and_pad.md
│ │ ├── 20191610_perspective_transform.md
│ │ ├── 20200107_improved_blending.md
│ │ ├── 20200126_python38.md
│ │ ├── added/
│ │ │ ├── 20190927_unwrapped_bb_aug.md
│ │ │ ├── 20191002_unwrapped_ls_aug.md
│ │ │ ├── 20191013_change_color_temperature.md
│ │ │ ├── 20191014_brightness_augmenters.md
│ │ │ ├── 20191016_dropout2d.md
│ │ │ ├── 20191019_colorwise_grayscaling.md
│ │ │ ├── 20191020_cartoon.md
│ │ │ ├── 20191023_mean_shift_blur.md
│ │ │ ├── 20191027_jigsaw.md
│ │ │ ├── 20191101_deterministic_list.md
│ │ │ ├── 20191102_autocontrast.md
│ │ │ ├── 20191103_affine_shear_y.md
│ │ │ ├── 20191103_equalize.md
│ │ │ ├── 20191103_identity.md
│ │ │ ├── 20191105_affine_wrappers.md
│ │ │ ├── 20191106_ooi_removal.md
│ │ │ ├── 20191110_bb_polygon_conversion.md
│ │ │ ├── 20191110_polygon_subdivision.md
│ │ │ ├── 20191110_withpolarwarping.md
│ │ │ ├── 20191117_debug_images.md
│ │ │ ├── 20191117_pad_multi_cval.md
│ │ │ ├── 20191218_imagecorruptions.md
│ │ │ ├── 20191220_cutout.md
│ │ │ ├── 20191221_inplace_cba_methods.md
│ │ │ ├── 20191224_pil_module.md
│ │ │ ├── 20191230_standardized_lut.md
│ │ │ ├── 20200101_bb_label_drawing.md
│ │ │ ├── 20200102_cbasoi_getitem.md
│ │ │ ├── 20200105_discretize_round.md
│ │ │ ├── 20200106_rain.md
│ │ │ ├── 20200106_randaugment.md
│ │ │ └── 20200125_image_warnings.md
│ │ ├── changed/
│ │ │ ├── 20190929_rngs_polygon_recoverer.md
│ │ │ ├── 20191110_affine_translation_precision.md
│ │ │ ├── 20191128_affine_translate.md
│ │ │ ├── 20191230_dont_import_msgs.md
│ │ │ ├── 20200103_standardized_shift_interfaces.md
│ │ │ ├── 20200112_simplified_augmenter_args.md
│ │ │ ├── 20200115_changed_defaults.md
│ │ │ └── 20200125_any_opencv_accepted.md
│ │ ├── deprecated/
│ │ │ ├── 20190926_rename_inplace.md
│ │ │ └── 20191230_deprecate_affinecv2.md
│ │ ├── fixed/
│ │ │ ├── 20190926_fixed_resize_dtype.md
│ │ │ ├── 20190928_fixed_affine_coords_aug.md
│ │ │ ├── 20190928_fixed_pwa_empty_kps_unaligned.md
│ │ │ ├── 20190928_fixed_type_val.md
│ │ │ ├── 20190929_fixed_assert_is_iterable_of.md
│ │ │ ├── 20191003_fixed_image_normalization.md
│ │ │ ├── 20191003_fixed_typo.md
│ │ │ ├── 20191006_fixed_withchannels_alignment.md
│ │ │ ├── 20191106_fixed_random_state_funcs_missing.md
│ │ │ ├── 20191110_fixed_affine_map_aug.md
│ │ │ ├── 20191111_fixed_snowflakeslayer_crash.md
│ │ │ ├── 20191111_multiplyhueandsaturation_rng.md
│ │ │ ├── 20191124_fixed_cloud_layer_float.md
│ │ │ ├── 20191128_fixed_affine_translate.md
│ │ │ ├── 20191128_fixed_hanging_nixos.md
│ │ │ ├── 20191217_collections_abc.md
│ │ │ ├── 20191218_fixed_fromfunction_deprecated.md
│ │ │ ├── 20191222_fixed_numpy_1_18.md
│ │ │ ├── 20191223_fixed_opencv_multicore_aug_hanging.md
│ │ │ ├── 20200110_fixed_seed.md
│ │ │ ├── 20200111_fixed_elastic_transformation_cval.md
│ │ │ ├── 20200113_fixed_weather_randomness.md
│ │ │ ├── 20200118_perspt_inaccuracy.md
│ │ │ ├── 20200122_fix_keepsizebyresize.md
│ │ │ └── 20200126_shapely_17a2.md
│ │ ├── refactored/
│ │ │ ├── 20191124_pylint.md
│ │ │ └── 20200111_opencv_normalization.md
│ │ └── renamed/
│ │ └── 20190926_rename_inplace.md
│ ├── master/
│ │ ├── 20200206_data_module.md
│ │ ├── changed/
│ │ │ ├── 20200222_shape_handling.md
│ │ │ └── 20200522_limit_dtype_support_alphablend.md
│ │ ├── fixed/
│ │ │ ├── 20200217_legacy_kp_aug_fallback.md
│ │ │ ├── 20200225_fix_imageio.md
│ │ │ ├── 20200412_fix_change_color_temperature.md
│ │ │ ├── 20200521_fix_skimage_slic_warning.md
│ │ │ ├── 20200522_fix_blend_alpha_f128.md
│ │ │ ├── 20200522_fix_mac_multiprocessing.md
│ │ │ ├── 20200522_fix_pad_f128.md
│ │ │ ├── 20200522_fix_permission_denied.md
│ │ │ ├── 20200525_fix_affine_cval_float.md
│ │ │ └── 20200601_fix_affine_skimage_order_0.md
│ │ ├── improved/
│ │ │ ├── 20200211_improve_performance_add.md
│ │ │ ├── 20200213_improved_blend_performance.md
│ │ │ ├── 20200216_add_elementwise_performance.md
│ │ │ ├── 20200216_improve_multiply_scalar_perf.md
│ │ │ ├── 20200216_improved_mul_elementwise_perf.md
│ │ │ ├── 20200217_vectorize_cropandpad.md
│ │ │ ├── 20200221_reworked_pooling.md
│ │ │ ├── 20200223_blur_avg.md
│ │ │ ├── 20200223_faster_elastic_tf.md
│ │ │ ├── 20200229_convolve.md
│ │ │ ├── 20200229_faster_invert.md
│ │ │ ├── 20200308_prefetching.md
│ │ │ ├── 20200315_segment_replacement.md
│ │ │ ├── 20200413_frequency_noise.md
│ │ │ ├── 20200517_faster_dtype_checks.md
│ │ │ ├── 20200521_improved_cicd_testing.md
│ │ │ ├── 20200522_tests_f128.md
│ │ │ └── 20200530_glass_blur_perf.md
│ │ └── refactored/
│ │ ├── 20200223_blur_gaussian.md
│ │ └── 20200314_affine.md
│ ├── v0.2.8.summary.md
│ ├── v0.2.9.summary.md
│ ├── v0.3.0.summary.md
│ └── v0.4.0.summary.md
├── checks/
│ ├── README.md
│ ├── check_add_to_hue_and_saturation.py
│ ├── check_affine.py
│ ├── check_affinecv2.py
│ ├── check_average_blur.py
│ ├── check_background_augmentation.py
│ ├── check_bb_augmentation.py
│ ├── check_bilateral_blur.py
│ ├── check_blendalphasegmapclassids.py
│ ├── check_blendalphasomecolors.py
│ ├── check_brightness.py
│ ├── check_canny.py
│ ├── check_cartoon.py
│ ├── check_channel_shuffle.py
│ ├── check_clouds.py
│ ├── check_color_temperature.py
│ ├── check_contrast.py
│ ├── check_crop_and_pad.py
│ ├── check_cutout.py
│ ├── check_deprecation_warning.py
│ ├── check_directed_edge_detect.py
│ ├── check_elastic_transformation.py
│ ├── check_fast_snowy_landscape.py
│ ├── check_fixed_size.py
│ ├── check_flip_performance.py
│ ├── check_fog.py
│ ├── check_heatmaps.py
│ ├── check_impulse_noise.py
│ ├── check_imshow.py
│ ├── check_jigsaw.py
│ ├── check_jpeg_compression.py
│ ├── check_kmeans_color_quantization.py
│ ├── check_laplace_noise.py
│ ├── check_mean_shift_blur.py
│ ├── check_median_blur.py
│ ├── check_motion_blur.py
│ ├── check_multicore_pool.py
│ ├── check_multiply_hue_and_saturation.py
│ ├── check_noise.py
│ ├── check_parameters.py
│ ├── check_performance.py
│ ├── check_perspective_transform.py
│ ├── check_piecewise_affine.py
│ ├── check_poisson_noise.py
│ ├── check_polygons_stay_valid_during_augmentation.py
│ ├── check_pooling.py
│ ├── check_quantize_uniform_to_n_bits.py
│ ├── check_rain.py
│ ├── check_randaugment.py
│ ├── check_readme_examples.py
│ ├── check_remove_saturation.py
│ ├── check_resize.py
│ ├── check_rot90.py
│ ├── check_seed.py
│ ├── check_segmentation_maps.py
│ ├── check_single_image_warning.py
│ ├── check_snowflakes.py
│ ├── check_snowflakes_layer.py
│ ├── check_solarize.py
│ ├── check_some_of.py
│ ├── check_superpixels.py
│ ├── check_uniform_color_quantization.py
│ ├── check_visually.py
│ ├── check_voronoi.py
│ ├── check_with_hue_and_saturation.py
│ ├── check_withchannels.py
│ └── check_withcolorspace.py
├── codecov.yml
├── imgaug/
│ ├── __init__.py
│ ├── augmentables/
│ │ ├── __init__.py
│ │ ├── base.py
│ │ ├── batches.py
│ │ ├── bbs.py
│ │ ├── heatmaps.py
│ │ ├── kps.py
│ │ ├── lines.py
│ │ ├── normalization.py
│ │ ├── polys.py
│ │ ├── segmaps.py
│ │ └── utils.py
│ ├── augmenters/
│ │ ├── __init__.py
│ │ ├── arithmetic.py
│ │ ├── artistic.py
│ │ ├── base.py
│ │ ├── blend.py
│ │ ├── blur.py
│ │ ├── collections.py
│ │ ├── color.py
│ │ ├── contrast.py
│ │ ├── convolutional.py
│ │ ├── debug.py
│ │ ├── edges.py
│ │ ├── flip.py
│ │ ├── geometric.py
│ │ ├── imgcorruptlike.py
│ │ ├── meta.py
│ │ ├── overlay.py
│ │ ├── pillike.py
│ │ ├── pooling.py
│ │ ├── segmentation.py
│ │ ├── size.py
│ │ └── weather.py
│ ├── data.py
│ ├── dtypes.py
│ ├── external/
│ │ ├── README.md
│ │ ├── __init__.py
│ │ ├── opensimplex.py
│ │ └── poly_point_isect_py2py3.py
│ ├── imgaug.py
│ ├── multicore.py
│ ├── parameters.py
│ ├── quokka_annotations.json
│ ├── random.py
│ ├── testutils.py
│ └── validation.py
├── pytest.ini
├── readthedocs.yml
├── requirements.txt
├── setup.cfg
├── setup.py
└── test/
├── augmentables/
│ ├── test_batches.py
│ ├── test_bbs.py
│ ├── test_heatmaps.py
│ ├── test_kps.py
│ ├── test_lines.py
│ ├── test_normalization.py
│ ├── test_polys.py
│ ├── test_segmaps.py
│ └── test_utils.py
├── augmenters/
│ ├── test_arithmetic.py
│ ├── test_artistic.py
│ ├── test_blend.py
│ ├── test_blur.py
│ ├── test_collections.py
│ ├── test_color.py
│ ├── test_contrast.py
│ ├── test_convolutional.py
│ ├── test_debug.py
│ ├── test_edges.py
│ ├── test_flip.py
│ ├── test_geometric.py
│ ├── test_imgcorruptlike.py
│ ├── test_meta.py
│ ├── test_mixed_files.py
│ ├── test_overlay.py
│ ├── test_pillike.py
│ ├── test_pooling.py
│ ├── test_segmentation.py
│ ├── test_size.py
│ └── test_weather.py
├── requirements.txt
├── run_doctests.sh
├── run_tests.sh
├── test_data.py
├── test_dtypes.py
├── test_imgaug.py
├── test_multicore.py
├── test_parameters.py
└── test_random.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .codacy.yml
================================================
exclude_paths:
- test/*
- test/augmenters/*
- test/augmentables/*
- checks/*
- imgaug/external/*
- old_version/*
- generate_documentation_images.py
- generate_example_images.py
================================================
FILE: .github/workflows/build_wheels.yml
================================================
# This action generates wheel files for python 2 and 3.
name: build wheels
on:
push:
branches:
- 'master'
jobs:
build:
# There were errors on Mac that would lead to non-stop printing of
# error messages forever instead of the job crashing. To prevent this,
# a timeout is placed here (default value is otherwise 360min).
timeout-minutes: 30
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# see supported versions at
# https://raw.githubusercontent.com/actions/python-versions/master/versions-manifest.json
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
exclude:
- os: windows-latest
python-version: 2.7 # causes a Shapely install error
env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v2
# ----------------
# Install python and base packages
# ----------------
- name: Set up python ${{ matrix.python-version }} on ${{ runner.os }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Display python version
run: |
python -c "import sys; print(sys.version)"
- name: Display system information
run : |
python -c "import sys; print(sys.maxsize);"
python -c "import platform; print(platform.uname());"
python -c "import platform; print(platform.platform());"
python -c "import platform; print(platform.architecture());"
python -c "import platform; print(platform.processor());"
python -c "import platform; print(platform.python_compiler());"
- name: Upgrade basic packages
run: |
python -m pip install --upgrade pip setuptools wheel
# ----------------
# Set up pip cache
# ----------------
- name: Get Date
id: get-date
run: |
echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")"
shell: bash
- uses: actions/cache@v1
if: startsWith(runner.os, 'Linux')
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/cache@v1
if: startsWith(runner.os, 'macOS')
with:
path: ~/Library/Caches/pip
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/cache@v1
if: startsWith(runner.os, 'Windows')
with:
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
# ----------------
# Install dependencies
# ----------------
- name: Install dependencies
run: |
pip install -r requirements.txt
# ----------------
# Generate wheels
# ----------------
- name: Generate wheels
run: |
python setup.py sdist
python setup.py bdist_wheel
# ----------------
# Upload artifacts
# ----------------
- uses: actions/upload-artifact@v2
with:
name: ${{ runner.os }}-py${{ matrix.python-version }}-dist
path: dist/
================================================
FILE: .github/workflows/test_master.yml
================================================
# This is effectively identical to pr_or_push.yml, with the exceptions of:
# (1) This is only executed upon pushes to master
# (2) This executes tests for more different python versions
name: test master
on:
push:
branches:
- 'master'
jobs:
build:
# There were errors on Mac that would lead to non-stop printing of
# error messages forever instead of the job crashing. To prevent this,
# a timeout is placed here (default value is otherwise 360min).
# Usually, jobs currently run through in around 10min.
timeout-minutes: 60
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# see supported versions at
# https://raw.githubusercontent.com/actions/python-versions/master/versions-manifest.json
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
exclude:
- os: windows-latest
python-version: 2.7 # causes a Shapely install error
env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v2
# ----------------
# Install python and base packages
# ----------------
- name: Set up Python ${{ matrix.python-version }} on ${{ runner.os }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Display python version
run: |
python -c "import sys; print(sys.version)"
- name: Display system information
run : |
python -c "import sys; print(sys.maxsize);"
python -c "import platform; print(platform.uname());"
python -c "import platform; print(platform.platform());"
python -c "import platform; print(platform.architecture());"
python -c "import platform; print(platform.processor());"
python -c "import platform; print(platform.python_compiler());"
- name: Upgrade basic packages
run: |
python -m pip install --upgrade pip setuptools wheel
# ----------------
# Set up pip cache
# ----------------
- name: Get Date
id: get-date
run: |
echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")"
shell: bash
- uses: actions/cache@v1
if: startsWith(runner.os, 'Linux')
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/cache@v1
if: startsWith(runner.os, 'macOS')
with:
path: ~/Library/Caches/pip
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/cache@v1
if: startsWith(runner.os, 'Windows')
with:
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
# ----------------
# Install dependencies
# ----------------
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Install test dependencies
run: |
pip install --upgrade -r test/requirements.txt
- name: Install further test tools
run: |
pip install coverage pytest-cov flake8
# ----------------
# Install library
# ----------------
- name: Install library
run: |
pip install .
# ----------------
# Run checks and tests
# ----------------
- name: Run flake8
run: |
flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics --exclude=".svn,CVS,.bzr,.hg,.git,__pycache__,poly_point_isect.py"
- name: Run tests
run: |
python -m pytest --verbose --xdoctest-modules -s --durations=50 -Walways
# ----------------
# Code coverage reports
# ----------------
# Add 'coverage html -d out_foldername' to add html reports
# Dont deactivate -Walways here, otherwise some tests fail as warnings
# are no longer produced.
- name: Generate code coverage report
run: |
coverage run --source imgaug -m pytest --verbose -Walways
coverage xml
coverage report
#- name: Upload coverage report to codacy
# uses: codacy/codacy-coverage-reporter-action@master
# with:
# project-token: ${{ secrets.CODACY_TOKEN }}
# coverage-reports: coverage.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
# right now the env_vars argument causes a warning, see
# https://github.com/codecov/codecov-action/issues/80
#env_vars: OS,PYTHON
name: codecov-umbrella
fail_ci_if_error: false
================================================
FILE: .github/workflows/test_pull_requests.yml
================================================
name: test pull requests
on:
push:
branches:
- '!master'
pull_request:
jobs:
build:
# There were errors on Mac that would lead to non-stop printing of
# error messages forever instead of the job crashing. To prevent this,
# a timeout is placed here (default value is otherwise 360min).
# Usually, jobs currently run through in around 10min.
timeout-minutes: 45
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
# see supported versions at
# https://raw.githubusercontent.com/actions/python-versions/master/versions-manifest.json
python-version: [2.7, 3.5, 3.6, 3.7, 3.8]
# test only 2.7 and the latest 3.x on mac
# test only the latest 3.x on windows
exclude:
- os: macos-latest
python-version: 3.5
- os: macos-latest
python-version: 3.6
- os: macos-latest
python-version: 3.7
- os: windows-latest
python-version: 2.7 # causes a Shapely install error
- os: windows-latest
python-version: 3.5
- os: windows-latest
python-version: 3.6
- os: windows-latest
python-version: 3.7
env:
OS: ${{ matrix.os }}
PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v2
# ----------------
# Install python and base packages
# ----------------
- name: Set up Python ${{ matrix.python-version }} on ${{ runner.os }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Display python version
run: |
python -c "import sys; print(sys.version)"
- name: Display system information
run : |
python -c "import sys; print(sys.maxsize);"
python -c "import platform; print(platform.uname());"
python -c "import platform; print(platform.platform());"
python -c "import platform; print(platform.architecture());"
python -c "import platform; print(platform.processor());"
python -c "import platform; print(platform.python_compiler());"
- name: Upgrade basic packages
run: |
python -m pip install --upgrade pip setuptools wheel
# ----------------
# Set up pip cache
# ----------------
- name: Get Date
id: get-date
run: |
echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")"
shell: bash
- uses: actions/cache@v1
if: startsWith(runner.os, 'Linux')
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/cache@v1
if: startsWith(runner.os, 'macOS')
with:
path: ~/Library/Caches/pip
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- uses: actions/cache@v1
if: startsWith(runner.os, 'Windows')
with:
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-
# ----------------
# Install dependencies
# ----------------
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Install test dependencies
run: |
pip install --upgrade -r test/requirements.txt
- name: Install further test tools
run: |
pip install coverage pytest-cov flake8
# ----------------
# Install library
# ----------------
- name: Install library
run: |
pip install .
# ----------------
# Run checks and tests
# ----------------
- name: Run flake8
run: |
flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics --exclude=".svn,CVS,.bzr,.hg,.git,__pycache__,poly_point_isect.py"
- name: Run tests
run: |
python -m pytest --verbose --xdoctest-modules -s --durations=50 -Walways
# ----------------
# Code coverage reports
# ----------------
# Add 'coverage html -d out_foldername' to add html reports
# Dont deactivate -Walways here, otherwise some tests fail as warnings
# are no longer produced.
- name: Generate code coverage report
run: |
coverage run --source imgaug -m pytest --verbose -Walways
coverage xml
coverage report
#- name: Upload coverage report to codacy
# uses: codacy/codacy-coverage-reporter-action@master
# with:
# project-token: ${{ secrets.CODACY_TOKEN }}
# coverage-reports: coverage.xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
flags: unittests
# right now the env_vars argument causes a warning, see
# https://github.com/codecov/codecov-action/issues/80
#env_vars: OS,PYTHON
name: codecov-umbrella
fail_ci_if_error: false
================================================
FILE: .gitignore
================================================
*.py~
*.rst~
*.md~
*.bak
*.lprof
reinstall.sh
reinstall_conda.sh
todo.txt
pypi-install-guide.txt
checks/bb_aug.jpg
checks/elastic_transformations.jpg
imgaug/parameters-testcode.py
imgaug/bak/*
imgaug/quokka_depth_map.xcf
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# PyCHarm
.idea/
# virtualenv
venv/
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
================================================
FILE: .pylintrc
================================================
[MASTER]
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=cv2,
scipy,
scipy.spatial,
numpy,
numpy.random,
numpy.random.bit_generator,
PIL,
PIL.Image,
PIL.ImageOps,
skimage,
skimage.feature,
skimage.transform,
skimage.segmentation
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Add files or directories matching the regex patterns to the blacklist. The
# regex matches against base names, not paths.
ignore-patterns=opensimplex\.py,
poly_point_isect_py2py3\.py
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use.
jobs=1
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=
# Pickle collected data for later comparisons.
persistent=yes
# Specify a configuration file.
#rcfile=
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED.
confidence=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
disable=print-statement,
parameter-unpacking,
unpacking-in-except,
old-raise-syntax,
backtick,
long-suffix,
old-ne-operator,
old-octal-literal,
import-star-module-level,
non-ascii-bytes-literal,
raw-checker-failed,
bad-inline-option,
locally-disabled,
file-ignored,
suppressed-message,
useless-suppression,
deprecated-pragma,
use-symbolic-message-instead,
apply-builtin,
basestring-builtin,
buffer-builtin,
cmp-builtin,
coerce-builtin,
execfile-builtin,
file-builtin,
long-builtin,
raw_input-builtin,
reduce-builtin,
standarderror-builtin,
unicode-builtin,
xrange-builtin,
coerce-method,
delslice-method,
getslice-method,
setslice-method,
no-absolute-import,
old-division,
dict-iter-method,
dict-view-method,
next-method-called,
metaclass-assignment,
indexing-exception,
raising-string,
reload-builtin,
oct-method,
hex-method,
nonzero-method,
cmp-method,
input-builtin,
round-builtin,
intern-builtin,
unichr-builtin,
map-builtin-not-iterating,
zip-builtin-not-iterating,
range-builtin-not-iterating,
filter-builtin-not-iterating,
using-cmp-argument,
eq-without-hash,
div-method,
idiv-method,
rdiv-method,
exception-message-attribute,
invalid-str-codec,
sys-max-int,
bad-python3-import,
deprecated-string-function,
deprecated-str-translate-call,
deprecated-itertools-function,
deprecated-types-field,
next-method-defined,
dict-items-not-iterating,
dict-keys-not-iterating,
dict-values-not-iterating,
deprecated-operator-function,
deprecated-urllib-function,
xreadlines-attribute,
deprecated-sys-function,
exception-escape,
comprehension-escape,
# ------------- non-standard for imgaug -------------
fixme, # no warnings for TODOs
line-too-long, # required for type definitions in docstrings
too-many-lines, # currently unfulfillable
useless-object-inheritance, # pylint complains that Foo(object) shouldn't be used anymore in py3+, but is required for py2.7
import-outside-toplevel, # necessary e.g. for optional dependencies
too-many-arguments,
too-many-branches,
too-many-locals,
too-many-instance-attributes,
too-few-public-methods,
too-many-public-methods,
too-many-return-statements,
too-many-statements,
too-many-ancestors,
len-as-condition, # more annoying than useful warning, suggestion doesn't even work with np arrays
unused-argument, # without this pylint complains about almost every _augment_batch() implementation not using 'parents' and 'hooks'; due to inheritance we can't do anything about that
no-self-use, # without this pylint complains about every get_parameters() implementation that returns only []; due to inheritance we can't do anything about that
protected-access, # we use plenty of calls of functions that are only marked private to discourage calls of them from outside of the library, but not from within the library
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[REPORTS]
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'error', 'warning', 'refactor', and 'convention'
# which contain the number of messages in each category, as well as 'statement'
# which is the total number of statements analyzed. This score is used by the
# global evaluation report (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
#msg-template=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
output-format=text
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[REFACTORING]
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )??$
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1000
# List of optional constructs for which whitespace checking is disabled. `dict-
# separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}.
# `trailing-comma` allows a space between comma and closing bracket: (a, ).
# `empty-line` allows space-only lines.
no-space-check=trailing-comma,
dict-separator
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[LOGGING]
# Format style used to check logging format string. `old` means using %
# formatting, `new` is for `{}` formatting,and `fstr` is for f-strings.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging
[SIMILARITIES]
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
# Minimum lines number of a similarity.
min-similarity-lines=8
[TYPECHECK]
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# List of decorators that change the signature of a decorated function.
signature-mutators=
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style.
#class-attribute-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style.
#function-rgx=
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_,
# ------- imgaug-specific settings -------
n, # number
h, # height
w, # width
c, # channel index
x, # x-coordinate
y, # y-coordinate
z, # z-coordinate
xy, # xy-coordinate pair
xx, # x-coordinates
yy, # y-coordinates
zz, # z-coordinates
dx, # some difference/shift in x
dy,
dz,
x1, # bounding box top left x-coordinate
x2, # bounding box bottom right x-coordinate
x3,
x4,
y1, # bounding box top left y-coordinate
y2, # bounding box bottom right x-coordinate
y3,
y4,
p, # probability
bb, # bounding box
bbs, # bounding boxes
kp, # keypoint
kps, # keypoints
ls, # line string
lss, # line strings
rs, # random state
ax, # matplotlib axis
f # file handle for 'open(...) as f:'
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style.
#method-rgx=
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=^_
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style.
#variable-rgx=
[VARIABLES]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
# Argument names that match this expression will be ignored. Default to name
# with leading underscore.
ignored-argument-names=_.*|^ignored_|^unused_
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,
XXX,
TODO
[SPELLING]
# Limits count of emitted suggestions for spelling mistakes.
max-spelling-suggestions=4
# Spelling dictionary name. Available dictionaries: none. To make it work,
# install the python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains the private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to the private dictionary (see the
# --spelling-private-dict-file option) instead of raising a message.
spelling-store-unknown-words=no
[STRING]
# This flag controls whether the implicit-str-concat-in-sequence should
# generate a warning on implicit string concatenation in sequences defined over
# several lines.
check-str-concat-over-line-jumps=no
[DESIGN]
# Maximum number of arguments for function / method.
max-args=5
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Maximum number of boolean expressions in an if statement (see R0916).
max-bool-expr=5
# Maximum number of branch for function / method body.
max-branches=12
# Maximum number of locals for function / method body.
max-locals=15
# Maximum number of parents for a class (see R0901).
max-parents=7
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
# Maximum number of return / yield for function / method body.
max-returns=6
# Maximum number of statements in function / method body.
max-statements=50
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=optparse,tkinter.tix
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled).
ext-import-graph=
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled).
import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[CLASSES]
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp,
__post_init__
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,
_fields,
_replace,
_source,
_make
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "BaseException, Exception".
overgeneral-exceptions=BaseException,
Exception
================================================
FILE: .travis.yml
================================================
sudo: required
dist: trusty
language:
- python
- cpp
env:
global:
- CODACY_PROJECT_TOKEN=1370ce38e99e40af842d47a8dd721444
cache:
directories:
- $HOME/.cache/pip
python:
- "2.7"
# - "3.2" # downloads np 1.17 on travis (?!), which doesn't support 3.2
# - "3.3" # downloads np 1.17 on travis (?!), which doesn't support 3.3
- "3.4"
- "3.5"
- "3.6"
# - "3.7" # python version cannot be installed on travis
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq -y python-virtualenv
# otherwise imagecodecs fails to build on py3.6,
# see https://github.com/scikit-image/scikit-image/issues/4673
- pip install --upgrade pip
install:
# TODO why was this deactivated?
# - virtualenv venv
# - . venv/bin/activate
- pip install -r requirements.txt
# Added --upgrade, because at least pytest already came from some other
# install command and so version was never checked
- pip install --upgrade -r test/requirements.txt
- pip install coverage codecov pytest-cov codacy-coverage
- pip install .
before_script:
- pip install flake8
# Stop the build if there are Python syntax errors or undefined names.
#
# We exclude poly_point_isect.py because it is incompatible with python2
# and poly_point_isect_py2py3.py is actually used instead. The incompatible
# file exists in the repo only for comparison. There are some other patterns
# added to --exclude, which are the default values for flake8's exclude
# option.
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics --exclude=".svn,CVS,.bzr,.hg,.git,__pycache__,poly_point_isect.py"
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
# currently deactivated as style guidelines are not yet kept in the project
# TODO change this
#- flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
script:
- python -m pytest --verbose --xdoctest-modules --ignore="test/run_all.py" -s --durations=50 -Walways
- coverage run --source imgaug -m pytest --verbose --xdoctest-modules --ignore="test/run_all.py" -Walways
# some steps are now done in github action
after_success:
# - codecov -t feeff9b2-3750-4246-befb-8cde60dc28aa
- coverage xml
- python-codacy-coverage -r coverage.xml
# - coverage report
================================================
FILE: CHANGELOG.md
================================================
This file is no longer used.
See `changelogs/` for all current and previous changelogs.
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 aleju
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: MANIFEST.in
================================================
include setup.py
include setup.cfg
include LICENSE
include MANIFEST.in
include README.md
include requirements.txt
recursive-include imgaug *.py *.jpg *.ttf *.png *.json
prune test
================================================
FILE: README.md
================================================
# imgaug
This python library helps you with augmenting images for your machine learning projects.
It converts a set of input images into a new, much larger set of slightly altered images.
[](https://travis-ci.org/aleju/imgaug)
[](https://codecov.io/gh/aleju/imgaug)
[](https://www.codacy.com/app/aleju/imgaug?utm_source=github.com&utm_medium=referral&utm_content=aleju/imgaug&utm_campaign=Badge_Grade)
| |
Image |
Heatmaps |
Seg. Maps |
Keypoints |
Bounding Boxes, Polygons |
| Original Input |
 |
 |
 |
 |
 |
Gauss. Noise + Contrast + Sharpen |
 |
 |
 |
 |
 |
| Affine |
 |
 |
 |
 |
 |
Crop + Pad |
 |
 |
 |
 |
 |
Fliplr + Perspective |
 |
 |
 |
 |
 |
**More (strong) example augmentations of one input image:**

## Table of Contents
1. [Features](#features)
2. [Installation](#installation)
3. [Documentation](#documentation)
4. [Recent Changes](#recent_changes)
5. [Example Images](#example_images)
6. [Code Examples](#code_examples)
7. [Citation](#citation)
## Features
* Many augmentation techniques
* E.g. affine transformations, perspective transformations, contrast changes, gaussian noise, dropout of regions, hue/saturation changes, cropping/padding, blurring, ...
* Optimized for high performance
* Easy to apply augmentations only to some images
* Easy to apply augmentations in random order
* Support for
* Images (full support for uint8, for other dtypes see [documentation](https://imgaug.readthedocs.io/en/latest/source/dtype_support.html))
* Heatmaps (float32), Segmentation Maps (int), Masks (bool)
* May be smaller/larger than their corresponding images. *No* extra lines of code needed for e.g. crop.
* Keypoints/Landmarks (int/float coordinates)
* Bounding Boxes (int/float coordinates)
* Polygons (int/float coordinates)
* Line Strings (int/float coordinates)
* Automatic alignment of sampled random values
* Example: Rotate image and segmentation map on it by the same value sampled from `uniform(-10°, 45°)`. (0 extra lines of code.)
* Probability distributions as parameters
* Example: Rotate images by values sampled from `uniform(-10°, 45°)`.
* Example: Rotate images by values sampled from `ABS(N(0, 20.0))*(1+B(1.0, 1.0))`", where `ABS(.)` is the absolute function, `N(.)` the gaussian distribution and `B(.)` the beta distribution.
* Many helper functions
* Example: Draw heatmaps, segmentation maps, keypoints, bounding boxes, ...
* Example: Scale segmentation maps, average/max pool of images/maps, pad images to aspect
ratios (e.g. to square them)
* Example: Convert keypoints to distance maps, extract pixels within bounding boxes from images, clip polygon to the image plane, ...
* Support for augmentation on multiple CPU cores
## Installation
The library supports python 2.7 and 3.4+.
### Installation: Anaconda
To install the library in anaconda, perform the following commands:
```bash
conda config --add channels conda-forge
conda install imgaug
```
You can deinstall the library again via `conda remove imgaug`.
### Installation: pip
Then install imgaug either via pypi (can lag behind the github version):
```bash
pip install imgaug
```
or install the latest version directly from github:
```bash
pip install git+https://github.com/aleju/imgaug.git
```
For more details, see the [install guide](https://imgaug.readthedocs.io/en/latest/source/installation.html)
To deinstall the library, just execute `pip uninstall imgaug`.
## Documentation
Example jupyter notebooks:
* [Load and Augment an Image](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/A01%20-%20Load%20and%20Augment%20an%20Image.ipynb)
* [Multicore Augmentation](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/A03%20-%20Multicore%20Augmentation.ipynb)
* Augment and work with: [Keypoints/Landmarks](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/B01%20-%20Augment%20Keypoints.ipynb),
[Bounding Boxes](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/B02%20-%20Augment%20Bounding%20Boxes.ipynb),
[Polygons](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/B03%20-%20Augment%20Polygons.ipynb),
[Line Strings](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/B06%20-%20Augment%20Line%20Strings.ipynb),
[Heatmaps](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/B04%20-%20Augment%20Heatmaps.ipynb),
[Segmentation Maps](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/B05%20-%20Augment%20Segmentation%20Maps.ipynb)
More notebooks: [imgaug-doc/notebooks](https://github.com/aleju/imgaug-doc/tree/master/notebooks).
Example ReadTheDocs pages:
* [Quick example code on how to use the library](http://imgaug.readthedocs.io/en/latest/source/examples_basics.html)
* [Overview of all Augmenters](https://imgaug.readthedocs.io/en/latest/source/overview_of_augmenters.html)
* [API](http://imgaug.readthedocs.io/en/latest/source/api.html)
More RTD documentation: [imgaug.readthedocs.io](http://imgaug.readthedocs.io/en/latest/source/examples_basics.html).
All documentation related files of this project are hosted in the
repository [imgaug-doc](https://github.com/aleju/imgaug-doc).
## Recent Changes
* **0.4.0**: Added new augmenters, changed backend to batchwise augmentation,
support for numpy 1.18 and python 3.8.
* **0.3.0**: Reworked segmentation map augmentation, adapted to numpy 1.17+
random number sampling API, several new augmenters.
* **0.2.9**: Added polygon augmentation, added line string augmentation,
simplified augmentation interface.
* **0.2.8**: Improved performance, dtype support and multicore augmentation.
See [changelogs/](changelogs/) for more details.
## Example Images
The images below show examples for most augmentation techniques.
Values written in the form `(a, b)` denote a uniform distribution,
i.e. the value is randomly picked from the interval `[a, b]`.
Line strings are supported by (almost) all augmenters, but are not explicitly
visualized here.
| meta |
| Identity |
ChannelShuffle |
|
|
|
 |
 |
|
|
|
| See also: Sequential, SomeOf, OneOf, Sometimes, WithChannels, Lambda, AssertLambda, AssertShape, RemoveCBAsByOutOfImageFraction, ClipCBAsToImagePlanes |
| arithmetic |
| Add |
Add (per_channel=True) |
AdditiveGaussianNoise |
AdditiveGaussianNoise (per_channel=True) |
Multiply |
 |
 |
 |
 |
 |
| Cutout |
Dropout |
CoarseDropout (p=0.2) |
CoarseDropout (p=0.2, per_channel=True) |
Dropout2d |
 |
 |
 |
 |
 |
| SaltAndPepper |
CoarseSaltAndPepper (p=0.2) |
Invert |
Solarize |
JpegCompression |
 |
 |
 |
 |
 |
| See also: AddElementwise, AdditiveLaplaceNoise, AdditivePoissonNoise, MultiplyElementwise, TotalDropout, ReplaceElementwise, ImpulseNoise, Salt, Pepper, CoarseSalt, CoarsePepper, Solarize |
| artistic |
| Cartoon |
|
|
|
|
 |
|
|
|
|
| blend |
BlendAlpha with EdgeDetect(1.0) |
BlendAlphaSimplexNoise with EdgeDetect(1.0) |
BlendAlphaFrequencyNoise with EdgeDetect(1.0) |
BlendAlphaSomeColors with RemoveSaturation(1.0) |
BlendAlphaRegularGrid with Multiply((0.0, 0.5)) |
 |
 |
 |
 |
 |
| See also: BlendAlphaMask, BlendAlphaElementwise, BlendAlphaVerticalLinearGradient, BlendAlphaHorizontalLinearGradient, BlendAlphaSegMapClassIds, BlendAlphaBoundingBoxes, BlendAlphaCheckerboard, SomeColorsMaskGen, HorizontalLinearGradientMaskGen, VerticalLinearGradientMaskGen, RegularGridMaskGen, CheckerboardMaskGen, SegMapClassIdsMaskGen, BoundingBoxesMaskGen, InvertMaskGen |
| blur |
| GaussianBlur |
AverageBlur |
MedianBlur |
BilateralBlur (sigma_color=250, sigma_space=250) |
MotionBlur (angle=0) |
 |
 |
 |
 |
 |
MotionBlur (k=5) |
MeanShiftBlur |
|
|
|
 |
 |
|
|
|
| collections |
| RandAugment |
|
|
|
|
 |
|
|
|
|
| color |
| MultiplyAndAddToBrightness |
MultiplyHueAndSaturation |
MultiplyHue |
MultiplySaturation |
AddToHueAndSaturation |
 |
 |
 |
 |
 |
| Grayscale |
RemoveSaturation |
ChangeColorTemperature |
KMeansColorQuantization (to_colorspace=RGB) |
UniformColorQuantization (to_colorspace=RGB) |
 |
 |
 |
 |
 |
| See also: WithColorspace, WithBrightnessChannels, MultiplyBrightness, AddToBrightness, WithHueAndSaturation, AddToHue, AddToSaturation, ChangeColorspace, Posterize |
| contrast |
| GammaContrast |
GammaContrast (per_channel=True) |
SigmoidContrast (cutoff=0.5) |
SigmoidContrast (gain=10) |
LogContrast |
 |
 |
 |
 |
 |
| LinearContrast |
AllChannels- HistogramEqualization |
HistogramEqualization |
AllChannelsCLAHE |
CLAHE |
 |
 |
 |
 |
 |
| See also: Equalize |
| convolutional |
Sharpen (alpha=1) |
Emboss (alpha=1) |
EdgeDetect |
DirectedEdgeDetect (alpha=1) |
|
 |
 |
 |
 |
|
| See also: Convolve |
| debug |
| See also: SaveDebugImageEveryNBatches |
| edges |
| Canny |
|
|
|
|
 |
|
|
|
|
| flip |
| Fliplr |
Flipud |
|
 |
 |
|
| See also: HorizontalFlip, VerticalFlip |
| geometric |
| Affine |
Affine: Modes |
|
 |
 |
|
| Affine: cval |
PiecewiseAffine |
|
 |
 |
|
| PerspectiveTransform |
ElasticTransformation (sigma=1.0) |
|
 |
 |
|
ElasticTransformation (sigma=4.0) |
Rot90 |
|
 |
 |
|
WithPolarWarping +Affine |
Jigsaw (5x5 grid) |
|
 |
 |
|
| See also: ScaleX, ScaleY, TranslateX, TranslateY, Rotate |
| imgcorruptlike |
| GlassBlur |
DefocusBlur |
ZoomBlur |
Snow |
Spatter |
 |
 |
 |
 |
 |
| See also: GaussianNoise, ShotNoise, ImpulseNoise, SpeckleNoise, GaussianBlur, MotionBlur, Fog, Frost, Contrast, Brightness, Saturate, JpegCompression, Pixelate, ElasticTransform |
| pillike |
| Autocontrast |
EnhanceColor |
EnhanceSharpness |
FilterEdgeEnhanceMore |
FilterContour |
 |
 |
 |
 |
 |
| See also: Solarize, Posterize, Equalize, EnhanceContrast, EnhanceBrightness, FilterBlur, FilterSmooth, FilterSmoothMore, FilterEdgeEnhance, FilterFindEdges, FilterEmboss, FilterSharpen, FilterDetail, Affine |
| pooling |
| AveragePooling |
MaxPooling |
MinPooling |
MedianPooling |
|
 |
 |
 |
 |
|
| segmentation |
Superpixels (p_replace=1) |
Superpixels (n_segments=100) |
UniformVoronoi |
RegularGridVoronoi: rows/cols (p_drop_points=0) |
RegularGridVoronoi: p_drop_points (n_rows=n_cols=30) |
 |
 |
 |
 |
 |
RegularGridVoronoi: p_replace (n_rows=n_cols=16) |
|
|
|
|
 |
|
|
|
|
| See also: Voronoi, RelativeRegularGridVoronoi, RegularGridPointsSampler, RelativeRegularGridPointsSampler, DropoutPointsSampler, UniformPointsSampler, SubsamplingPointsSampler |
| size |
| CropAndPad |
Crop |
|
 |
 |
|
| Pad |
PadToFixedSize (height'=height+32, width'=width+32) |
|
 |
 |
|
CropToFixedSize (height'=height-32, width'=width-32) |
|
|
|
 |
|
|
|
| See also: Resize, CropToMultiplesOf, PadToMultiplesOf, CropToPowersOf, PadToPowersOf, CropToAspectRatio, PadToAspectRatio, CropToSquare, PadToSquare, CenterCropToFixedSize, CenterPadToFixedSize, CenterCropToMultiplesOf, CenterPadToMultiplesOf, CenterCropToPowersOf, CenterPadToPowersOf, CenterCropToAspectRatio, CenterPadToAspectRatio, CenterCropToSquare, CenterPadToSquare, KeepSizeByResize |
| weather |
FastSnowyLandscape (lightness_multiplier=2.0) |
Clouds |
Fog |
Snowflakes |
Rain |
 |
 |
 |
 |
 |
| See also: CloudLayer, SnowflakesLayer, RainLayer |
## Code Examples
### Example: Simple Training Setting
A standard machine learning situation.
Train on batches of images and augment each batch via crop, horizontal
flip ("Fliplr") and gaussian blur:
```python
import numpy as np
import imgaug.augmenters as iaa
def load_batch(batch_idx):
# dummy function, implement this
# Return a numpy array of shape (N, height, width, #channels)
# or a list of (height, width, #channels) arrays (may have different image
# sizes).
# Images should be in RGB for colorspace augmentations.
# (cv2.imread() returns BGR!)
# Images should usually be in uint8 with values from 0-255.
return np.zeros((128, 32, 32, 3), dtype=np.uint8) + (batch_idx % 255)
def train_on_images(images):
# dummy function, implement this
pass
# Pipeline:
# (1) Crop images from each side by 1-16px, do not resize the results
# images back to the input size. Keep them at the cropped size.
# (2) Horizontally flip 50% of the images.
# (3) Blur images using a gaussian kernel with sigma between 0.0 and 3.0.
seq = iaa.Sequential([
iaa.Crop(px=(1, 16), keep_size=False),
iaa.Fliplr(0.5),
iaa.GaussianBlur(sigma=(0, 3.0))
])
for batch_idx in range(100):
images = load_batch(batch_idx)
images_aug = seq(images=images) # done by the library
train_on_images(images_aug)
```
### Example: Very Complex Augmentation Pipeline
Apply a very heavy augmentation pipeline to images (used to create the image
at the very top of this readme):
```python
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
# random example images
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
# Sometimes(0.5, ...) applies the given augmenter in 50% of all cases,
# e.g. Sometimes(0.5, GaussianBlur(0.3)) would blur roughly every second image.
sometimes = lambda aug: iaa.Sometimes(0.5, aug)
# Define our sequence of augmentation steps that will be applied to every image
# All augmenters with per_channel=0.5 will sample one value _per image_
# in 50% of all cases. In all other cases they will sample new values
# _per channel_.
seq = iaa.Sequential(
[
# apply the following augmenters to most images
iaa.Fliplr(0.5), # horizontally flip 50% of all images
iaa.Flipud(0.2), # vertically flip 20% of all images
# crop images by -5% to 10% of their height/width
sometimes(iaa.CropAndPad(
percent=(-0.05, 0.1),
pad_mode=ia.ALL,
pad_cval=(0, 255)
)),
sometimes(iaa.Affine(
scale={"x": (0.8, 1.2), "y": (0.8, 1.2)}, # scale images to 80-120% of their size, individually per axis
translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)}, # translate by -20 to +20 percent (per axis)
rotate=(-45, 45), # rotate by -45 to +45 degrees
shear=(-16, 16), # shear by -16 to +16 degrees
order=[0, 1], # use nearest neighbour or bilinear interpolation (fast)
cval=(0, 255), # if mode is constant, use a cval between 0 and 255
mode=ia.ALL # use any of scikit-image's warping modes (see 2nd image from the top for examples)
)),
# execute 0 to 5 of the following (less important) augmenters per image
# don't execute all of them, as that would often be way too strong
iaa.SomeOf((0, 5),
[
sometimes(iaa.Superpixels(p_replace=(0, 1.0), n_segments=(20, 200))), # convert images into their superpixel representation
iaa.OneOf([
iaa.GaussianBlur((0, 3.0)), # blur images with a sigma between 0 and 3.0
iaa.AverageBlur(k=(2, 7)), # blur image using local means with kernel sizes between 2 and 7
iaa.MedianBlur(k=(3, 11)), # blur image using local medians with kernel sizes between 2 and 7
]),
iaa.Sharpen(alpha=(0, 1.0), lightness=(0.75, 1.5)), # sharpen images
iaa.Emboss(alpha=(0, 1.0), strength=(0, 2.0)), # emboss images
# search either for all edges or for directed edges,
# blend the result with the original image using a blobby mask
iaa.SimplexNoiseAlpha(iaa.OneOf([
iaa.EdgeDetect(alpha=(0.5, 1.0)),
iaa.DirectedEdgeDetect(alpha=(0.5, 1.0), direction=(0.0, 1.0)),
])),
iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.05*255), per_channel=0.5), # add gaussian noise to images
iaa.OneOf([
iaa.Dropout((0.01, 0.1), per_channel=0.5), # randomly remove up to 10% of the pixels
iaa.CoarseDropout((0.03, 0.15), size_percent=(0.02, 0.05), per_channel=0.2),
]),
iaa.Invert(0.05, per_channel=True), # invert color channels
iaa.Add((-10, 10), per_channel=0.5), # change brightness of images (by -10 to 10 of original value)
iaa.AddToHueAndSaturation((-20, 20)), # change hue and saturation
# either change the brightness of the whole image (sometimes
# per channel) or change the brightness of subareas
iaa.OneOf([
iaa.Multiply((0.5, 1.5), per_channel=0.5),
iaa.FrequencyNoiseAlpha(
exponent=(-4, 0),
first=iaa.Multiply((0.5, 1.5), per_channel=True),
second=iaa.LinearContrast((0.5, 2.0))
)
]),
iaa.LinearContrast((0.5, 2.0), per_channel=0.5), # improve or worsen the contrast
iaa.Grayscale(alpha=(0.0, 1.0)),
sometimes(iaa.ElasticTransformation(alpha=(0.5, 3.5), sigma=0.25)), # move pixels locally around (with random strengths)
sometimes(iaa.PiecewiseAffine(scale=(0.01, 0.05))), # sometimes move parts of the image around
sometimes(iaa.PerspectiveTransform(scale=(0.01, 0.1)))
],
random_order=True
)
],
random_order=True
)
images_aug = seq(images=images)
```
### Example: Augment Images and Keypoints
Augment images and keypoints/landmarks on the same images:
```python
import numpy as np
import imgaug.augmenters as iaa
images = np.zeros((2, 128, 128, 3), dtype=np.uint8) # two example images
images[:, 64, 64, :] = 255
points = [
[(10.5, 20.5)], # points on first image
[(50.5, 50.5), (60.5, 60.5), (70.5, 70.5)] # points on second image
]
seq = iaa.Sequential([
iaa.AdditiveGaussianNoise(scale=0.05*255),
iaa.Affine(translate_px={"x": (1, 5)})
])
# augment keypoints and images
images_aug, points_aug = seq(images=images, keypoints=points)
print("Image 1 center", np.argmax(images_aug[0, 64, 64:64+6, 0]))
print("Image 2 center", np.argmax(images_aug[1, 64, 64:64+6, 0]))
print("Points 1", points_aug[0])
print("Points 2", points_aug[1])
```
Note that all coordinates in `imgaug` are subpixel-accurate, which is
why `x=0.5, y=0.5` denotes the center of the top left pixel.
### Example: Augment Images and Bounding Boxes
```python
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
images = np.zeros((2, 128, 128, 3), dtype=np.uint8) # two example images
images[:, 64, 64, :] = 255
bbs = [
[ia.BoundingBox(x1=10.5, y1=15.5, x2=30.5, y2=50.5)],
[ia.BoundingBox(x1=10.5, y1=20.5, x2=50.5, y2=50.5),
ia.BoundingBox(x1=40.5, y1=75.5, x2=70.5, y2=100.5)]
]
seq = iaa.Sequential([
iaa.AdditiveGaussianNoise(scale=0.05*255),
iaa.Affine(translate_px={"x": (1, 5)})
])
images_aug, bbs_aug = seq(images=images, bounding_boxes=bbs)
```
### Example: Augment Images and Polygons
```python
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
images = np.zeros((2, 128, 128, 3), dtype=np.uint8) # two example images
images[:, 64, 64, :] = 255
polygons = [
[ia.Polygon([(10.5, 10.5), (50.5, 10.5), (50.5, 50.5)])],
[ia.Polygon([(0.0, 64.5), (64.5, 0.0), (128.0, 128.0), (64.5, 128.0)])]
]
seq = iaa.Sequential([
iaa.AdditiveGaussianNoise(scale=0.05*255),
iaa.Affine(translate_px={"x": (1, 5)})
])
images_aug, polygons_aug = seq(images=images, polygons=polygons)
```
### Example: Augment Images and LineStrings
LineStrings are similar to polygons, but are not closed, may intersect with
themselves and don't have an inner area.
```python
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
images = np.zeros((2, 128, 128, 3), dtype=np.uint8) # two example images
images[:, 64, 64, :] = 255
ls = [
[ia.LineString([(10.5, 10.5), (50.5, 10.5), (50.5, 50.5)])],
[ia.LineString([(0.0, 64.5), (64.5, 0.0), (128.0, 128.0), (64.5, 128.0),
(128.0, 0.0)])]
]
seq = iaa.Sequential([
iaa.AdditiveGaussianNoise(scale=0.05*255),
iaa.Affine(translate_px={"x": (1, 5)})
])
images_aug, ls_aug = seq(images=images, line_strings=ls)
```
### Example: Augment Images and Heatmaps
Heatmaps are dense float arrays with values between `0.0` and `1.0`.
They can be used e.g. when training models to predict facial landmark
locations. Note that the heatmaps here have lower height and width than the
images. `imgaug` handles that case automatically. The crop pixel amounts will
be halved for the heatmaps.
```python
import numpy as np
import imgaug.augmenters as iaa
# Standard scenario: You have N RGB-images and additionally 21 heatmaps per
# image. You want to augment each image and its heatmaps identically.
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
heatmaps = np.random.random(size=(16, 64, 64, 1)).astype(np.float32)
seq = iaa.Sequential([
iaa.GaussianBlur((0, 3.0)),
iaa.Affine(translate_px={"x": (-40, 40)}),
iaa.Crop(px=(0, 10))
])
images_aug, heatmaps_aug = seq(images=images, heatmaps=heatmaps)
```
### Example: Augment Images and Segmentation Maps
This is similar to heatmaps, but the dense arrays have dtype `int32`.
Operations such as resizing will automatically use nearest neighbour
interpolation.
```python
import numpy as np
import imgaug.augmenters as iaa
# Standard scenario: You have N=16 RGB-images and additionally one segmentation
# map per image. You want to augment each image and its heatmaps identically.
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
segmaps = np.random.randint(0, 10, size=(16, 64, 64, 1), dtype=np.int32)
seq = iaa.Sequential([
iaa.GaussianBlur((0, 3.0)),
iaa.Affine(translate_px={"x": (-40, 40)}),
iaa.Crop(px=(0, 10))
])
images_aug, segmaps_aug = seq(images=images, segmentation_maps=segmaps)
```
### Example: Visualize Augmented Images
Quickly show example results of your augmentation sequence:
```python
import numpy as np
import imgaug.augmenters as iaa
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
seq = iaa.Sequential([iaa.Fliplr(0.5), iaa.GaussianBlur((0, 3.0))])
# Show an image with 8*8 augmented versions of image 0 and 8*8 augmented
# versions of image 1. Identical augmentations will be applied to
# image 0 and 1.
seq.show_grid([images[0], images[1]], cols=8, rows=8)
```
### Example: Visualize Augmented Non-Image Data
`imgaug` contains many helper function, among these functions to quickly
visualize augmented non-image results, such as bounding boxes or heatmaps.
```python
import numpy as np
import imgaug as ia
image = np.zeros((64, 64, 3), dtype=np.uint8)
# points
kps = [ia.Keypoint(x=10.5, y=20.5), ia.Keypoint(x=60.5, y=60.5)]
kpsoi = ia.KeypointsOnImage(kps, shape=image.shape)
image_with_kps = kpsoi.draw_on_image(image, size=7, color=(0, 0, 255))
ia.imshow(image_with_kps)
# bbs
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=10.5, y1=20.5, x2=50.5, y2=30.5)
], shape=image.shape)
image_with_bbs = bbsoi.draw_on_image(image)
image_with_bbs = ia.BoundingBox(
x1=50.5, y1=10.5, x2=100.5, y2=16.5
).draw_on_image(image_with_bbs, color=(255, 0, 0), size=3)
ia.imshow(image_with_bbs)
# polygons
psoi = ia.PolygonsOnImage([
ia.Polygon([(10.5, 20.5), (50.5, 30.5), (10.5, 50.5)])
], shape=image.shape)
image_with_polys = psoi.draw_on_image(
image, alpha_points=0, alpha_face=0.5, color_lines=(255, 0, 0))
ia.imshow(image_with_polys)
# heatmaps
hms = ia.HeatmapsOnImage(np.random.random(size=(32, 32, 1)).astype(np.float32),
shape=image.shape)
image_with_hms = hms.draw_on_image(image)
ia.imshow(image_with_hms)
```
LineStrings and segmentation maps support similar methods as shown above.
### Example: Using Augmenters Only Once
While the interface is adapted towards re-using instances of augmenters
many times, you are also free to use them only once. The overhead to
instantiate the augmenters each time is usually negligible.
```python
from imgaug import augmenters as iaa
import numpy as np
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
# always horizontally flip each input image
images_aug = iaa.Fliplr(1.0)(images=images)
# vertically flip each input image with 90% probability
images_aug = iaa.Flipud(0.9)(images=images)
# blur 50% of all images using a gaussian kernel with a sigma of 3.0
images_aug = iaa.Sometimes(0.5, iaa.GaussianBlur(3.0))(images=images)
```
### Example: Multicore Augmentation
Images can be augmented in **background processes** using the
method `augment_batches(batches, background=True)`, where `batches` is
a list/generator of
[imgaug.augmentables.batches.UnnormalizedBatch](https://imgaug.readthedocs.io/en/latest/_modules/imgaug/augmentables/batches.html#UnnormalizedBatch)
or
[imgaug.augmentables.batches.Batch](https://imgaug.readthedocs.io/en/latest/source/api_augmentables_batches.html#imgaug.augmentables.batches.Batch).
The following example augments a list of image batches in the background:
```python
import skimage.data
import imgaug as ia
import imgaug.augmenters as iaa
from imgaug.augmentables.batches import UnnormalizedBatch
# Number of batches and batch size for this example
nb_batches = 10
batch_size = 32
# Example augmentation sequence to run in the background
augseq = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.CoarseDropout(p=0.1, size_percent=0.1)
])
# For simplicity, we use the same image here many times
astronaut = skimage.data.astronaut()
astronaut = ia.imresize_single_image(astronaut, (64, 64))
# Make batches out of the example image (here: 10 batches, each 32 times
# the example image)
batches = []
for _ in range(nb_batches):
batches.append(UnnormalizedBatch(images=[astronaut] * batch_size))
# Show the augmented images.
# Note that augment_batches() returns a generator.
for images_aug in augseq.augment_batches(batches, background=True):
ia.imshow(ia.draw_grid(images_aug.images_aug, cols=8))
```
If you need more control over the background augmentation, e.g. to set
seeds, control the number of used CPU cores or constraint the memory usage,
see the corresponding
[multicore augmentation notebook](https://nbviewer.jupyter.org/github/aleju/imgaug-doc/blob/master/notebooks/A03%20-%20Multicore%20Augmentation.ipynb)
or the API about
[Augmenter.pool()](https://imgaug.readthedocs.io/en/latest/source/api_augmenters_meta.html#imgaug.augmenters.meta.Augmenter.pool)
and
[imgaug.multicore.Pool](https://imgaug.readthedocs.io/en/latest/source/api_multicore.html#imgaug.multicore.Pool).
### Example: Probability Distributions as Parameters
Most augmenters support using tuples `(a, b)` as a shortcut to denote
`uniform(a, b)` or lists `[a, b, c]` to denote a set of allowed values from
which one will be picked randomly. If you require more complex probability
distributions (e.g. gaussians, truncated gaussians or poisson distributions)
you can use stochastic parameters from `imgaug.parameters`:
```python
import numpy as np
from imgaug import augmenters as iaa
from imgaug import parameters as iap
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
# Blur by a value sigma which is sampled from a uniform distribution
# of range 10.1 <= x < 13.0.
# The convenience shortcut for this is: GaussianBlur((10.1, 13.0))
blurer = iaa.GaussianBlur(10 + iap.Uniform(0.1, 3.0))
images_aug = blurer(images=images)
# Blur by a value sigma which is sampled from a gaussian distribution
# N(1.0, 0.1), i.e. sample a value that is usually around 1.0.
# Clip the resulting value so that it never gets below 0.1 or above 3.0.
blurer = iaa.GaussianBlur(iap.Clip(iap.Normal(1.0, 0.1), 0.1, 3.0))
images_aug = blurer(images=images)
```
There are many more probability distributions in the library, e.g. truncated
gaussian distribution, poisson distribution or beta distribution.
### Example: WithChannels
Apply an augmenter only to specific image channels:
```python
import numpy as np
import imgaug.augmenters as iaa
# fake RGB images
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
# add a random value from the range (-30, 30) to the first two channels of
# input images (e.g. to the R and G channels)
aug = iaa.WithChannels(
channels=[0, 1],
children=iaa.Add((-30, 30))
)
images_aug = aug(images=images)
```
## Citation
If this library has helped you during your research, feel free to cite it:
```latex
@misc{imgaug,
author = {Jung, Alexander B.
and Wada, Kentaro
and Crall, Jon
and Tanaka, Satoshi
and Graving, Jake
and Reinders, Christoph
and Yadav, Sarthak
and Banerjee, Joy
and Vecsei, Gábor
and Kraft, Adam
and Rui, Zheng
and Borovec, Jirka
and Vallentin, Christian
and Zhydenko, Semen
and Pfeiffer, Kilian
and Cook, Ben
and Fernández, Ismael
and De Rainville, François-Michel
and Weng, Chi-Hung
and Ayala-Acevedo, Abner
and Meudec, Raphael
and Laporte, Matias
and others},
title = {{imgaug}},
howpublished = {\url{https://github.com/aleju/imgaug}},
year = {2020},
note = {Online; accessed 01-Feb-2020}
}
```
================================================
FILE: bandit.yml
================================================
skips: [
'B101',
'B301', # "Pickle and modules that wrap it can be unsafe when used to
# deserialize untrusted data, possible security issue."
'B403' # "Consider possible security implications associated with pickle
# module."
]
================================================
FILE: changelogs/0.3.0/v0.3.0.cleaned.md
================================================
# 0.3.0 Cleaned Up Log Of Changes
## Improved Segmentation Map Augmentation #302
Augmentation of Segmentation Maps is now faster and more memory efficient.
This required some breaking changes to `SegmentationMapOnImage`.
To adapt to the new version, the following steps should be sufficient for most
users:
* Rename all calls of `SegmentationMapOnImage` to `SegmentationMapsOnImage`
(Map -> Maps).
* Rename all calls of `SegmentationMapsOnImage.get_arr_int()` to
`SegmentationMapsOnImage.get_arr()`.
* Remove the argument `nb_classes` from all calls of `SegmentationMapsOnImage`.
* Remove the arguments `background_id` and `background_threshold` from all
calls as these are no longer supported.
* Ensure that the input array to `SegmentationMapsOnImage` is always an
int-like (int, uint or bool).
Float arrays are no longer accepted.
* Adapt all calls `SegmentationMapsOnImage.draw()` and
`SegmentationMapsOnImage.draw_on_image()`, as both of these now return a
list of drawn images instead of a single array. (For a segmentation map
array of shape `(H,W,C)` they return `C` drawn images. In most cases `C=1`,
so simply call `draw()[0]` or `draw_on_image()[0]`.)
* Ensure that if `SegmentationMapsOnImage.arr` is accessed anywhere, the
respective code can handle the new `int32` `(H,W,#maps)` array form.
Previously it was `float32` and the channel-axis had the same size as the
max class id (+1) that could appear in the map.
Changes:
- Changes to class `SegmentationMapOnImage`:
- Renamed `SegmentationMapOnImage` to plural `SegmentationMapsOnImage`
and deprecated the old name.
This was changed due to the input array now being allowed to contain
several channels, with each such channel containing one full segmentation
map.
- Changed `SegmentationMapsOnImage.__init__` to produce a deprecation
warning for float arrays as `arr` argument.
- **[breaking]** Changed `SegmentationMapsOnImage.__init__` to no longer
accept `uint32` and larger itemsizes as `arr` argument, only `uint16`
and below is accepted. For `int` the allowed maximum is `int32`.
- Changed `SegmentationMapsOnImage.__init__` to always accept `(H,W,C)`
`arr` arguments.
- **[breaking]** Changed `SegmentationMapsOnImage.arr` to always be
`int32` `(H,W,#maps)` (previously: `float32` `(H,W,#nb_classes)`).
- Deprecated `nb_classes` argument in `SegmentationMapsOnImage.__init__`.
The argument is now ignored.
- Added `SegmentationMapsOnImage.get_arr()`, which always returns a
segmentation map array with similar dtype and number of dimensions as
was originally input when creating a class instance.
- Deprecated `SegmentationMapsOnImage.get_arr_int()`.
The method is now an alias for `get_arr()`.
- `SegmentationMapsOnImage.draw()`:
- **[breaking]** Removed argument `return_foreground_mask` and
corresponding optional output. To generate a foreground mask
for the `c`-th segmentation map on a given image (usually `c=0`),
use `segmentation_map.arr[:, :, c] != 0`, assuming that `0` is
the integer index of your background class.
- **[breaking]** Changed output of drawn image to be a list of arrays
instead of a single array (one per `C` in input array `(H,W,C)`).
- Refactored to be a wrapper around
`SegmentationMapsOnImage.draw_on_image()`.
- The `size` argument may now be any of: A single `None` (keep shape),
a single integer (use as height and width), a single float (relative
change to shape) or a tuple of these values. ("shape" here denotes
the value of the `.shape` attribute.)
- `SegmentationMapsOnImage.draw_on_image()`:
- **[breaking]** The argument `background_threshold` is now deprecated
and ignored. Providing it will lead to a deprecation warning.
- **[breaking]** Changed output of drawn image to be a list of arrays
instead of a single array (one per `C` in input array `(H,W,C)`).
- Changed `SegmentationMapsOnImage.resize()` to use nearest neighbour
interpolation by default.
- **[rarely breaking]** Changed `SegmentationMapsOnImage.copy()` to create
a shallow copy instead of being an alias for `deepcopy()`.
- Added optional arguments `arr` and `shape` to
`SegmentationMapsOnImage.copy()`.
- Added optional arguments `arr` and `shape` to
`SegmentationMapsOnImage.deepcopy()`.
- Refactored `SegmentationMapsOnImage.pad()`,
`SegmentationMapsOnImage.pad_to_aspect_ratio()` and
`SegmentationMapsOnImage.resize()` to generate new object instances via
`SegmentationMapsOnImage.deepcopy()`.
- **[rarely breaking]** Renamed `SegmentationMapsOnImage.input_was` to
`SegmentationMapsOnImage._input_was`.
- **[rarely breaking]** Changed `SegmentationMapsOnImage._input_was` to
always save `(input array dtype, input array ndim)` instead of mixtures
of strings/ints that varied by dtype kind.
- **[rarely breaking]** Restrict `shape` argument in
`SegmentationMapsOnImage.__init__` to tuples instead of accepting all
iterables.
- **[breaking]** Removed `SegmentationMapsOnImage.to_heatmaps()` as the
new segmentation map class is too different to sustain the old heatmap
conversion methods.
- **[breaking]** Removed `SegmentationMapsOnImage.from_heatmaps()` as the
new segmentation map class is too different to sustain the old heatmap
conversion methods.
- Changes to class `Augmenter`:
- **[breaking]** Automatic segmentation map normalization from arrays or
lists of arrays now expects a single `(N,H,W,C)` array (before:
`(N,H,W)`) or a list of `(H,W,C)` arrays (before: `(H,W)`).
This affects valid segmentation map inputs for `Augmenter.augment()`
and its alias `Augmenter.__call__()`,
`imgaug.augmentables.batches.UnnormalizedBatch()` and
`imgaug.augmentables.normalization.normalize_segmentation_maps()`.
- Added `Augmenter._augment_segmentation_maps()`.
- Changed `Augmenter.augment_segmentation_maps()` to no longer be a
wrapper around `Augmenter.augment_heatmaps()` and instead call
`Augmenter._augment_segmentation_maps()`.
- Added special segmentation map handling to all augmenters that modified
segmentation maps
(`Sequential`, `SomeOf`, `Sometimes`, `WithChannels`,
`Lambda`, `AssertLambda`, `AssertShape`,
`Alpha`, `AlphaElementwise`, `WithColorspace`, `Fliplr`, `Flipud`, `Affine`,
`AffineCv2`, `PiecewiseAffine`, `PerspectiveTransform`, `ElasticTransformation`,
`Rot90`, `Resize`, `CropAndPad`, `PadToFixedSize`, `CropToFixedSize`,
`KeepSizeByResize`).
- **[rarely breaking]** This changes the order of arguments in
`Lambda.__init__()`, `AssertLambda.__init__()`, `AssertShape.__init__()`
and hence breaks if one relied on that order.
## New RNG handling #375
* Adapted library to automatically use the new `numpy.random` classes of
numpy 1.17 -- if they are available. If they are not available (i.e. numpy
version is <=1.16), the library automatically falls back to the old
interface (i.e. `numpy.random.RandomState`).
* Added module `imgaug.random`.
* Added class `imgaug.random.RNG`. This is now the preferred way to represent
RNG states (previously: `numpy.random.RandomState`). Instantiate it
via e.g. `RNG(1052912236)`, where `1052912236` is a seed.
* Added `imgaug.random.supports_new_rng_style()`.
* Added `imgaug.random.get_global_rng()`.
* Added `imgaug.random.seed()`.
* Added `imgaug.random.normalize_generator()`.
* Added `imgaug.random.normalize_generator_()`.
* Added `imgaug.random.convert_seed_to_generator()`.
* Added `imgaug.random.convert_seed_sequence_to_generator()`.
* Added `imgaug.random.create_pseudo_random_generator_()`.
* Added `imgaug.random.create_fully_random_generator()`.
* Added `imgaug.random.generate_seed_()`.
* Added `imgaug.random.generate_seeds_()`.
* Added `imgaug.random.copy_generator()`.
* Added `imgaug.random.copy_generator_unless_global_generator()`.
* Added `imgaug.random.reset_generator_cache_()`.
* Added `imgaug.random.derive_generator_()`.
* Added `imgaug.random.derive_generators_()`.
* Added `imgaug.random.get_generator_state()`.
* Added `imgaug.random.set_generator_state_()`.
* Added `imgaug.random.is_generator_equal_to()`.
* Added `imgaug.random.advance_generator_()`.
* Added `imgaug.random.polyfill_integers()`.
* Added `imgaug.random.polyfill_random()`.
* Refactored all arguments related to random state handling to also accept
`imgaug.random.RNG`, as well as the new numpy random classes. This
particularly affects `imgaug.augmenters.meta.Augmenter` and
`imgaug.parameters.StochasticParameter` (argument `random_state` for both).
* Marked old RNG related functions in `imgaug.imgaug` as deprecated.
They will now produce warnings and redirect towards corresponding functions
in `imgaug.random`. This does not yet affect `imgaug.imgaug.seed()`.
It does affect the functions listed below.
* `imgaug.imgaug.normalize_random_state()`.
* `imgaug.imgaug.current_random_state()`.
* `imgaug.imgaug.new_random_state()`.
* `imgaug.imgaug.dummy_random_state()`.
* `imgaug.imgaug.copy_random_state()`.
* `imgaug.imgaug.derive_random_state()`.
* `imgaug.imgaug.derive_random_states()`.
* `imgaug.imgaug.forward_random_state()`.
* [rarely breaking] Removed `imgaug.imgaug.CURRENT_RANDOM_STATE`.
Use `imgaug.random.get_global_rng()` instead.
* [rarely breaking] Removed `imgaug.imgaug.SEED_MIN_VALUE`.
Use `imgaug.random.SEED_MIN_VALUE` instead or sample seeds via
`imgaug.random.generate_seeds_()`.
* [rarely breaking] Removed `imgaug.imgaug.SEED_MAX_VALUE`.
Use `imgaug.random.SEED_MAX_VALUE` instead or sample seeds via
`imgaug.random.generate_seeds_()`.
* Optimized RNG handling throughout all augmenters to minimize the number of
RNG copies. RNGs are now re-used as often as possible. This improves
performance, but has the disadvantage that adding images to a batch will now
often affect the samples of the other images in the same batch. E.g.
previously for a batch of images `A,B,C` and seed `1`, the samples of `A,B,C`
would remain unchanged if the batch was changed to `A,B,C,D` (provided the
seed stayed the same). Now, if `D` is added the samples of `A,B,C` may
change.
* [breaking] The above listed changes will lead to different values being
sampled for the same seeds (compared to past versions of the library).
* [breaking] The seed for `imgaug`'s global random number generator is now
sampled from numpy's default random number generator. That means, that every
run of a program using `imgaug` will by default use a different seed and
hence result in different samples. Previously, a fixed seed was used,
resulting in the same samples for each run (unless the seed was manually
changed to a fixed one). It also means that seeding numpy will automatically
also seed imgaug (not guarantueed that this behaviour will be kept in
future releases). The change from fixed to random seed was done, because the
old (fixed) behaviour didn't match the common practice (and especially not
numpy's standard behaviour) and hence led to confusion. #408
## Adaptations to numpy 1.17
* [rarely breaking] Deactivated support for `int64` in
`imgaug.dtypes.clip_()`. This is due to numpy 1.17 turning `int64` to
`float64` in `numpy.clip()` (possible that this happened in some way
before 1.17 too). #302
* [rarely breaking] Changed `imgaug.dtypes.clip()` to never clip `int32`
in-place, as `numpy.clip()` turns it into `float64` since 1.17 (possible
that this happend in some way before 1.17 too).
* [rarely breaking] Deactivated support for `int64` in
`ReplaceElementwise`. See `clip` issue above. #302
* [rarely breaking] Changed `parameters.DiscreteUniform` to always return
arrays of dtype `int32`. Previously it would automatically return
`int64`. #302
* [rarely breaking] Changed `parameters.Deterministic` to always return
`int32` for integers and always `float32` for floats. #302
* [rarely breaking] Changed `parameters.Choice` to limit integer
dtypes to `int32` or lower, uints to `uint32` or lower and floats
to `float32` or lower. #302
* [rarely breaking] Changed `parameters.Binomial` and `parameters.Poisson`
to always return `int32`. #302
* [rarely breaking] Changed `parameters.Normal`,
`parameters.TruncatedNormal`, `parameters.Laplace`,
`parameters.ChiSquare`, `parameters.Weibull`, `parameters.Uniform` and
`parameters.Beta` to always return `float32`. #302
* [rarely breaking] Changed `augmenters.arithmetic.Add`,
`augmenters.arithmetic.AddElementwise`, `augmenters.arithmetic.Multiply`
and `augmenters.arithmetic.MultiplyElementwise` to no longer internally
increase itemsize of dtypes by a factor of 2 for
dtypes `uint16`, `int8` and `uint16`. For `Multiply*` this also
covers `float16` and `float32`. This protects against crashes due to
clipping `int64` or `uint64` data. In rare cases this can lead to
overflows if `image + random samples` or `image * random samples`
exceeds the value range of `int32` or `uint32`. This change may affect
various other augmenters that are wrappers around the mentioned ones,
e.g. `AdditiveGaussianNoise`. #302
* [rarely breaking] Decreased support of dtypes `uint16`, `int8`,
`int16`, `float16`, `float32` and `bool` in `augmenters.arithmetic.Add`,
`AddElementwise`, `Multiply` and `MultiplyElementwise` from "yes" to
"limited". #302
* [rarely breaking] Decreased support of dtype `int64` in
`augmenters.arithmetic.ReplaceElementwise` from "yes" to "no". This also
affects all `*Noise` augmenters (e.g. `AdditiveGaussianNoise`,
`ImpulseNoise`), all `Dropout` augmenters, all `Salt` augmenters and
all `Pepper` augmenters. #302
* [rarely breaking] Changed `augmenters.contrast.adjust_contrast_log`
and thereby `LogContrast` to no longer support dtypes `uint32`, `uint64`,
`int32` and `int64`. #302
## New Augmenters
* Added `augmenters.edges.Canny`, which applies canny edge detection with alpha
blending and random coloring to images. #316
* Added `augmenters.pooling.AveragePooling`. #317
* Added `augmenters.pooling.MaxPooling`. #317
* Added `augmenters.pooling.MinPooling`. #317
* Added `augmenters.pooling.MedianPooling`. #317
* Added `augmenters.color.AddToHue`, a shortcut for
`AddToHueAndSaturation(value_hue=...)`. #319
* Added `augmenters.color.AddToSaturation`, a shortcut for
`AddToHueAndSaturation(value_saturation=...)`. #319
* Added `augmenters.color.WithHueAndSaturation`. #319
* Added `augmenters.color.MultiplyHueAndSaturation`. #319
* Added `augmenters.color.MultiplyHue`. #319
* Added `augmenters.color.MultiplySaturation`. #319
* Added `augmenters.color.KMeansColorQuantization` and corresponding
`augmenters.color.quantize_colors_kmeans()`. Both deal with quantizing
similar colors using k-Means clustering. #347
* Added a check script for `KMeansColorQuantization` under
`checks/check_kmeans_color_quantization.py`. #347
* Added `augmenters.color.UniformColorQuantization` and corresponding
`augmenters.color.quantize_colors_uniform()`. Both deal with quantizing
similar colors using k-Means clustering. #347
* Added `imgaug.augmenters.segmentation.Voronoi`. An augmenter that converts
an image to a voronoi image. #348
* Added `imgaug.augmenters.segmentation.UniformVoronoi`, a shortcut for
`Voronoi(UniformPointsSamper)`. #348
* Added `imgaug.augmenters.segmentation.RegularGridVoronoi`, a shortcut for
`Voronoi(DropoutPointsSampler(RegularGridPointsSampler))`. #348
* Added `imgaug.augmenters.segmentation.RelativeRegularGridVoronoi`, a shortcut
for `Voronoi(DropoutPointsSampler(RelativeRegularGridPointsSampler))`. #348
## New Modules
* Added module `imgaug.augmenters.edges`. #316
* Added module `imgaug.augmenters.pooling`. #317
* Added module `imgaug.validation`. The module is intended for functions
related to the validation of input arguments. #413
## output_buffer_size
* Added argument `output_buffer_size` to `multicore.Pool.imap_batches()`
and `multicore.Pool.imap_batches_unordered()` to control the maximum number
of batches in the background augmentation pipeline (allows to limit
maximum RAM demands). #305
* Changed default `output_buffer_size` in `Augmenter.augment_batches()` from
"unlimited" to `10*C`, where `C` is the number of logical CPU cores. #417
## Other New Classes
* Added interface `augmenters.edges.BinaryImageColorizerIf`, which
contains the interface for classes used to convert binary images to RGB
images. #316
* Added `augmenters.pooling._AbstractPoolingBase`. #317
* Added `augmenters.edges.RandomColorsBinaryImageColorizer`, which
converts binary images to RGB images by sampling uniformly RGB colors for
`True` and `False` values. #316
* Added `augmenters.color._AbstractColorQuantization`. #347
* Added `imgaug.augmenters.segmentation.PointsSamplerIf`. An interface for
classes used for sampling (usually random) coordinate arrays on images. #348
* Added `imgaug.augmenters.segmentation.RegularGridPointsSampler`. A class
used to generate regular grids of `rows x columns` points on images. #348
* Added `imgaug.augmenters.segmentation.RelativeRegularGridPointsSampler`.
Similar to `RegularGridPointsSampler`, but number of rows/columns is set
as fractions of image sizes, leading to more rows/columns for larger
images. #348
* Added `imgaug.augmenters.segmentation.DropoutPointsSampler`. A class
used to randomly drop `p` percent of all coordinates sampled by another
another points sampler. #348
* Added `imgaug.augmenters.segmentation.UniformPointsSampler`. A class used
to sample `N` points on each image with y-/x-coordinates uniformly sampled
using the corresponding image height/width. #348
* Added `imgaug.augmenters.segmentation.SubsamplingPointsSampler`. A class
that ensures that another points sampler does not produce more than
`N` points by subsampling a random subset of the produced points if `N`
is exceeded. #348
* Added `imgaug.testutils.ArgCopyingMagicMock`. #413
## Other New Functions
* Added `imgaug.is_np_scalar()`, analogous to `imgaug.is_np_array()`. #366
* Added `dtypes.normalize_dtypes()`. #366
* Added `dtypes.normalize_dtype()`. #366
* Added `dtypes.change_dtypes_()`. #366
* Added `dtypes.change_dtype_()`. #366
* Added `dtypes.increase_itemsize_of_dtype()`. #366
* Added `imgaug.warn()` function. #367
* Added `imgaug.min_pool()`. #369
* Added `imgaug.median_pool()`. #369
* Added `imgaug.compute_paddings_to_reach_multiples_of()`. #369
* Added `imgaug.pad_to_multiples_of()`. #369
* Added `imgaug.imgaug.normalize_random_state()`. #348
* Added `imgaug.augmenters.segmentation._ensure_image_max_size()`. #348
* Added `imgaug.augmenters.segmentation._verify_sample_points_images()`. #348
* Added `imgaug.augmenters.segmentation.segment_voronoi()`, a function that
converts an image into a voronoi image, i.e. averages the colors within
voronoi cells placed on the image. #348
* Added `_match_pixels_with_voronoi_cells()`. #348
* Added `_generate_pixel_coords()`. #348
* Added `_compute_avg_segment_colors()`. #348
* Added `_render_segments()`. #348
* Added `augmentables.utils.copy_augmentables`. #410
* Added `augmenters.flip.fliplr()`. #385
* Added `augmenters.flip.flipud()`. #385
* Added `augmenters.color.change_colorspace_()`. #409
* Added `augmenters.color.change_colorspace_batch_()`. #409
* Added `augmenters.arithmetic.add_scalar()`. #411
* Added `augmenters.arithmetic.add_elementwise()`. #411
* Added `augmenters.arithmetic.replace_elementwise_()`. #411
* Added `augmenters.arithmetic.compress_jpg()`. #411
* Added `validation.convert_iterable_to_string_of_types()`. #413
* Added `validation.is_iterable_of()`. #413
* Added `validation.assert_is_iterable_of()`. #413
## Other New Constants
* Added to `imgaug.augmenters.color` the constants `CSPACE_RGB`,
`CSPACE_BGR`, `CSPACE_GRAY`, `CSPACE_CIE`, `CSPACE_YCrCb`, `CSPACE_HSV`,
`CSPACE_HLS`, `CSPACE_Lab`, `CSPACE_Luv`, `CSPACE_YUV`, `CSPACE_ALL`. #409
## Other New Arguments
* [rarely breaking] Added a `pad_mode` argument to `imgaug.pool()`,
`imgaug.avg_pool()`, `imgaug.max_pool()`, `imgaug.min_pool()` and
`imgaug.median_pool()`. This breaks code relying on the order of the
functions arguments. #369
* Added to argument `size` of `Resize` the optional keys `short-side` and
`longer-side`. This adds the ability to resize the shorter and longer sides
of images (instead of only height/width). #349
* [rarely breaking] Added `value_hue` and `value_saturation` arguments,
which allow to set individual parameters for hue and saturation
instead of having to use one parameter for both (they may not be set
if `value` is already set).
This changes the order of arguments of the augmenter and code that relied
on that order will now break.
This also changes the output of
`AddToHueAndSaturation.get_parameters()`. #319
* [rarely breaking] Added argument `polygon_recoverer` to
`augmenters.geometric.PerspectiveTransform`. This changes the order of
arguments of the augmenter and code that relied on that order will now
break. #338
* Added attribute `from_colorspace` to `AddToHueAndSaturation`. #409
## Other Removed Concepts
* [rarely breaking] Removed `dtypes.get_minimal_dtype_for_values()`. The
function was not used anywhere in the library. #366
* [rarely breaking] Removed `dtypes.get_minimal_dtype_by_value_range()`. The
function was not used anywhere in the library. #366
* [rarely breaking] Removed `Affine.VALID_DTYPES_CV2_ORDER_0`. #407
* [rarely breaking] Removed `Affine.VALID_DTYPES_CV2_ORDER_NOT_0`. #407
* [rarely breaking] Removed `Affine.order_map_skimage_cv2`. #407
* [rarely breaking] Removed `Affine.mode_map_skimage_cv2`. #407
* [rarely breaking] Removed attributes `colorspace_changer` and
`colorspace_changer_inv` from `AddToHueAndSaturation`. #409
* [rarely breaking] Removed class constant `ALLOW_DTYPES_CUSTOM_MINMAX`
from `Invert`. #411
* [rarely breaking] Removed attribute `dtype_kind_to_invert_func` from
`Invert`. #411
* [rarely breaking] Removed attribute `maximum_quality` from
`JpegCompression`. #411
* [rarely breaking] Removed attribute `minimum_quality` from
`JpegCompression`. #411
* [rarely breaking] Removed argument `affects` from
`dtypes.promote_array_dtypes_()` as it was unnecessary and not used anywhere
in the library. #366
* [rarely breaking] Removed method
`ElasticTransformation.generate_shift_maps()`. Use
`ElasticTransformation._generate_shift_maps()` instead. #413
* [rarely breaking] Removed method `ElasticTransformation.map_coordinates()`.
Use `ElasticTransformation._map_coordinates()` instead. #413
## Performance
* Replaced all calls of `imgaug.do_assert` with ordinary `assert`
statements. This is a bit less secure, but should overall improve
performance. #387
* Improved performance of `augmenters.flip.Fliplr`. #385
* Improved performance of `augmenters.flip.Flipud`. #385
## Deprecation
* Marked the following functions as deprecated (#398):
* `imgaug.augmenters.meta.clip_augmented_image_`
* `imgaug.augmenters.meta.clip_augmented_image`
* `imgaug.augmenters.meta.clip_augmented_images_`
* `imgaug.augmenters.meta.clip_augmented_images`
* Marked `imgaug.augmenters.arithmetic.ContrastNormalization` as deprecated.
Use `imgaug.augmenters.contrast.LinearContrast` instead. #396
* Marked argument `X` of `imgaug.augmentables.kps.compute_geometric_median()`
as deprecated. Use argument `points` instead. #402
* Marked `cval` in `imgaug.pool()`, `imgaug.avg_pool()` and `imgaug.max_pool()`
as deprecated. Use `pad_cval` instead. #369
## Refactorings
* Refactored `augmenters.arithmetic` to improve code quality and
docstrings. #328
* Refactored `augmenters.segmentation` to improve code quality and
docstrings. #334
* Refactored `augmenters/convolutional.py` to improve code quality and
docstrings. #335
* Refactored `augmenters/weather.py` to improve code quality and
docstrings. #336
* Refactored `multicore` to improve code quality and
docstrings. #367
* Improved error messages in `multicore`.
* Refactored `imgaug.pool()` to use `imgaug.pad()` for image padding. #369
* Refactored `augmenters.pooling.MedianPooling` to use
`imgaug.median_pool()`. #369
* Refactored `augmenters.pooling.MinPooling` to use `imgaug.min_pool()`. #369
* Improved code style and documentation of (#389, #402):
* `imgaug.augmentables.bbs`.
* `imgaug.augmentables.heatmaps`.
* `imgaug.augmentables.kps`.
* `imgaug.augmentables.lines`.
* `imgaug.augmentables.normalization`.
* `imgaug.augmentables.polys`.
* `imgaug.augmentables.segmaps`.
* `imgaug.augmentables.utils`.
* `imgaug.imgaug`.
* `imgaug.parameters`.
* `imgaug.augmenters.weather`.
* `imgaug.augmenters.size`.
* `imgaug.augmenters.segmentation`.
* `imgaug.augmenters.meta`.
* `imgaug.augmenters.geometric`.
* `imgaug.augmenters.flip`.
* `imgaug.augmenters.contrast`.
* `imgaug.augmenters.blur`.
* `imgaug.augmenters.blend`.
* `imgaug.augmenters.weather`.
* Refactored all calls of `warnings.warn()` to use `imgaug.imgaug.warn()
instead. #401
* [rarely breaking] Refactored most of the augmenters from functions to
classes. Previously, some augmenters were functions that returned an
instance of another augmenter (with adjusted hyperparameters) when being
called. Aside from a few corner cases, these have been switched to classes
inheriting from the augmenters that were previously returned. This should
make some outputs less confusing (e.g. `print(A())` does no longer lead to
class `B` being printed). All arguments stayed the same and this is not
expected to affect any user code negatively. The augmenters listed below are
affected by this change. #396
* `imgaug.augmenters.arithmetic.AdditiveGaussianNoise`
* `imgaug.augmenters.arithmetic.AdditiveLaplaceNoise`
* `imgaug.augmenters.arithmetic.AdditivePoissonNoise`
* `imgaug.augmenters.arithmetic.Dropout`
* `imgaug.augmenters.arithmetic.CoarseDropout`
* `imgaug.augmenters.arithmetic.ImpulseNoise`
* `imgaug.augmenters.arithmetic.SaltAndPepper`
* `imgaug.augmenters.arithmetic.CoarseSaltAndPepper`
* `imgaug.augmenters.arithmetic.Salt`
* `imgaug.augmenters.arithmetic.CoarseSalt`
* `imgaug.augmenters.arithmetic.Pepper`
* `imgaug.augmenters.arithmetic.CoarsePepper`
* `imgaug.augmenters.blend.SimplexNoiseAlpha`
* `imgaug.augmenters.blend.FrequencyNoiseAlpha`
* `imgaug.augmenters.blur.MotionBlur`
* `imgaug.augmenters.contrast.MultiplyHueAndSaturation`
* `imgaug.augmenters.contrast.MultiplyHue`
* `imgaug.augmenters.contrast.MultiplySaturation`
* `imgaug.augmenters.contrast.AddToHue`
* `imgaug.augmenters.contrast.AddToSaturation`
* `imgaug.augmenters.contrast.Grayscale`
* `imgaug.augmenters.contrast.GammaContrast`
* `imgaug.augmenters.contrast.SigmoidContrast`
* `imgaug.augmenters.contrast.LogContrast`
* `imgaug.augmenters.contrast.LinearContrast`
* `imgaug.augmenters.convolutional.Sharpen`
* `imgaug.augmenters.convolutional.Emboss`
* `imgaug.augmenters.convolutional.EdgeDetect`
* `imgaug.augmenters.convolutional.DirectedEdgeDetect`
* `imgaug.augmenters.meta.OneOf`
* `imgaug.augmenters.meta.AssertLambda`
* `imgaug.augmenters.meta.AssertShape`
* `imgaug.augmenters.size.Pad`
* `imgaug.augmenters.size.Crop`
* `imgaug.augmenters.weather.Clouds`
* `imgaug.augmenters.weather.Fog`
* `imgaug.augmenters.weather.Snowflakes`
* Refactored `Affine` to improve code quality and minimize code
duplication. #407
* Refactored `CropAndPad` to improve code quality and minimize code
duplication. #407
* Refactored module `size` to decrease code duplication between different
augmenters. #407
* Moved matrix generation logic of augmenters in module `convolutional`
to classes, one per augmenter (i.e. one per category of convolutional
matrix). This should avoid errors related to pickling of functions. #407
* Refactored color augmenters to use `change_colorspace_()` and
`change_colorspace_batch_()`. #409
* Refactored `Alpha` to decrease code duplication. #410
* Refactored `AlphaElementwise` to decrease code duplication. #410
* Refactored `Add` to use `imgaug.augmenters.arithmetic.add_scalar()`. #411
* Refactored `AddElementwise` to use
`imgaug.augmenters.arithmetic.add_elementwise()`. #411
* Refactored `ReplaceElementwise` to use
`imgaug.augmenters.arithmetic.replace_elementwise_()`. #411
* Refactored `JpegCompression` to use
`imgaug.augmenters.arithmetic.compress_jpg()`. #411
* Refactored `AddToHueAndSaturation` to decrease code duplication and improve
code quality. #319
* Refactored `Affine` to improve code quality and decrease code
duplication. #413
* Refactored `PiecewiseAffine` to improve code quality and decrease code
duplication. #413
* Refactored `PerspectiveTransform` to improve code quality and decrease code
duplication. #413
* Refactored `ElasticTransformation` to improve code quality and decrease code
duplication. #413
* Refactored `Rot90` to improve code quality and decrease code
duplication. #413
* Refactored `Augmenter.augment_images()`, `Augmenter.augment_heatmaps()`,
`Augmenter.augment_segmentation_maps()`, `Augmenter.augment_polygons()`,
`Augmenter.augment_line_strings()` and `Augmenter._augment_coord_augables()`
to improve code quality and remove redundancies. #413
* Refactored `imgaug.imgaug.imresize_single_image()`. #413
* Refactored `Sequential` to reduce code duplication. #413
* Refactored `SomeOf` to improve code quality. #413
* Refactored `Sometimes` to reduce code duplication. #413
* Refactored `AssertShape` to reduce code duplication. #413
* Refactored `ChannelShuffle` to improve code quality. #413
* Refactored `dtypes.py` to improve code quality. #366
* Refactored `dtypes.promote_array_dtypes_()` to use
`dtypes.change_dtypes_()`. #366
* Refactored `dtypes.get_minimal_dtype()` to use
`dtypes.increase_itemsize_of_dtype()`. #366
* Renamed `dtypes.restore_dtypes_()` to `dtypes.change_dtypes_()`. (Old name
still exists too and redirects to new name. Not yet marked as
deprecated.) #366
## Other Changes
* [rarely breaking] Changed colorspace transformations throughout the
library to fail if the input image does not have three channels. #409
* Changed colorspace transformations throughout the library to also
support `YUV` colorspace. #409
* [rarely breaking] Changed `AlphaElementwise` to verify for keypoint
and line string augmentation that the number of coordinates before/after
augmentation does not change. Previously this was allowed. This also
affects `SigmoidNoiseAlpha` and `FrequenceNoiseAlpha`. #410
* [rarely breaking] Changed `AlphaElementwise` to use for keypoint,
line string and bounding box augmentation a pointwise approach, where
per coordinate a decision is made whether the new coordinate from the
first branch's (augmented) results or the second branch's (augmented)
results are used. The decision is based on the average alpha mask value
at the xy-location of the coordinate. For polygons, the old mode is
still used where either all coordinates from the first branch's results
or the second branch's results are used. This also affects
`SigmoidNoiseAlpha` and `FrequenceNoiseAlpha`. #410
* Improved error messages of `dtypes.restore_dtypes_()`. #366
## Other Minor Changes
* Increased `max_distance` thresholds for `almost_equals()`,
`exterior_almost_equals()` and `coords_almost_equals()` in `Polygon` and
`LineString` from `1e-6` to `1e-4`. This should fix false-negative problems
related to float inaccuracies.
* Changed `_ConcavePolygonRecoverer` to not search for segment intersection
points in polygons with very large absolute coordinate values.
This prevents rare errors due to floating point inaccuracies. #338
* Changed `_ConcavePolygonRecoverer` to raise warnings instead of throwing
exceptions when the underlying search for segment intersection points
crashes. #338
* Added check to `dtypes.gate_dtypes()` verifying that arguments `allowed`
and `disallowed` have no intersection. #346
* Changed `dtypes.get_value_range_of_dtype()` to return a float as the center
value of `uint` dtypes. #366
* Changed `multicore.Pool` to produce a warning if it cannot find or call the
function `multiprocessing.cpu_count()` instead of silently failing.
(In both cases it falls back to a default value.) #367
* Changed the default `pad_mode` of `avg_pool` from `constant` (`cval=128`)
to `reflect`. #369
* Changed the default `pad_mode` of `max_pool` from `constant` (`cval=0`)
to `edge`. #369
* Changed the default `pad_mode` of `min_pool` from `constant` (`cval=255`)
to `edge`. #369
* Changed the default `pad_mode` of `median_pool` from `constant`
(`cval=128`) to `reflect`. #369
* Changed `imgaug.imgaug.pad` to automatically clip the `cval` argument
to the value range of the array to be padded. #407
* [rarely breaking] Changed `KeypointsOnImage.from_keypoints_image()` to
return `(x+0.5, y+0.5)` instead of `(x, y)` where `(x, y)` denotes the
coordinates of the pixel in which a maximum was found. This change matches
the standard that all pixels are given with subpixel accuracy and therefore
any whole pixel with a maximum should denote the coordinates of that
pixel's center. #413
* Removed image-channel check for cv2-based warp in `Affine`. Images with any
channel number can now be warped using the cv2 backend (previously: only
`<=4`, others would be warped via skimage). #381
* [rarely breaking] The `value` parameter in
`augmenters.color.AddToHueAndSaturation` is now interpreted by the
augmenter to return first the hue and then the saturation value to add,
instead of the other way round. (This isn't expected to affect
anybody.) #319
* Added output `from_colorspace` to
`AddToHueAndSaturation.get_parameters()`. #409
* Added dtype gating to `dtypes.clip_()`.
* Changed all dtype-related functions to accept also dtypes given as
string names, numpy arrays, numpy scalars or dtype functions. #366
* Changed `dtypes.restore_dtypes_()` so that if `images` is a list of length
`N` and `dtypes` is a list of length `M` and `N!=M`, the function now raises
an `AssertionError`. #366
* Changed `dtypes.restore_dtypes_()` to ignore the argument `round` if the
input array does not have a float dtype. #366
* Removed restrictions of `value` parameter in `AddElementwise`.
The value range is now no longer limited to `[-255, 255]` and floats
are now allowed. #411
## New Scripts
* Added a check script for `UniformColorQuantization` in
`checks/check_uniform_color_quantization.py`. #347
* Added a check script for `Voronoi` in `checks/check_voronoi.py`. #348
* Added a check script for flip performance measurments under
`checks/check_flip_performance.py`. #385
## Other
* Added the library to `conda-forge` so it can now be installed via
`conda install imgaug` (provided the conda-forge channel was added
before that). #320 #339
* Changed dependency `opencv-python` to `opencv-python-headless`.
This should improve support for some system without GUIs.
* Added dependency `pytest-subtests` for the library's unittests. #366
* Improved the docstrings of most augmenters and added code examples. #302
* Added error messages to `assert` statements throughout the library. #387
* Removed the requirement to implement `_augment_keypoints()` and
`_augment_heatmaps()` in augmenters. The methods now default to doing
nothing. Also removed all such noop-implementations of these methods from
all augmenters. #380
* Increased minimum version requirement for `scikit-image` to
`0.14.2`. #377, #399
* Renamed `imgaug/external/poly_point_isect.py` to
`imgaug/external/poly_point_isect_py3.py.bak`.
The file is in the library only for completeness and contains python3 syntax.
`poly_point_isect_py2py3.py` is actually used.
## Fixes
* Fixed an issue with `Polygon.clip_out_of_image()`,
which would lead to exceptions if a polygon had overlap with an image,
but not a single one of its points was inside that image plane.
* Fixed `multicore` methods falsely not accepting
`augmentables.batches.UnnormalizedBatch`.
* `Rot90` now uses subpixel-based coordinate remapping.
I.e. any coordinate `(x, y)` will be mapped to `(H-y, x)` for a rotation by
90deg.
Previously, an integer-based remapping to `(H-y-1, x)` was used.
Coordinates are e.g. used by keypoints, bounding boxes or polygons.
* `augmenters.arithmetic.Invert`
* [rarely breaking] If `min_value` and/or `max_value` arguments were
set, `uint64` is no longer a valid input array dtype for `Invert`.
This is due to a conversion to `float64` resulting in loss of resolution.
* Fixed `Invert` in rare cases restoring dtypes improperly.
* Fixed `dtypes.gate_dtypes()` crashing if the input was one or more numpy
scalars instead of numpy arrays or dtypes.
* Fixed `augmenters.geometric.PerspectiveTransform` producing invalid
polygons (more often with higher `scale` values). #338
* Fixed errors caused by `external/poly_point_isect_py2py3.py` related to
floating point inaccuracies (changed an epsilon from `1e-10` to `1e-4`,
rounded some floats). #338
* Fixed `Superpixels` breaking when a sampled `n_segments` was `<=0`.
`n_segments` is now treated as `1` in these cases.
* Fixed `ReplaceElementwise` both allowing and disallowing dtype `int64`. #346
* Fixed `BoundingBox.deepcopy()` creating only shallow copies of labels. #356
* Fixed `dtypes.change_dtypes_()` #366
* Fixed argument `round` being ignored if input images were a list.
* Fixed failure if input images were a list and dtypes a single numpy
dtype function.
* Fixed `dtypes.get_minimal_dtype()` failing if argument `arrays` contained
not *exactly* two items. #366
* Fixed calls of `CloudLayer.get_parameters()` resulting in errors. #309
* Fixed `SimplexNoiseAlpha` and `FrequencyNoiseAlpha` not handling
`sigmoid` argument correctly. #343
* Fixed `SnowflakesLayer` crashing for grayscale images. #345
* Fixed `Affine` heatmap augmentation crashing for arrays with more than
four channels and `order!=0`. #381
* Fixed an outdated error message in `Affine`. #381
* Fixed `Polygon.clip_out_of_image()` crashing if the intersection between
polygon and image plane was an edge or point. #382
* Fixed `Polygon.clip_out_of_image()` potentially failing for polygons
containing two or fewer points. #382
* Fixed `Polygon.is_out_of_image()` returning wrong values if the image plane
was fully contained inside the polygon with no intersection between the
image plane and the polygon edge. #382
* Fixed `Fliplr` and `Flipud` using for coordinate-based inputs and image-like
inputs slightly different conditions for when to actually apply
augmentations. #385
* Fixed `Convolve` using an overly restrictive check when validating inputs
for `matrix` w.r.t. whether they are callables. The check should now also
support class methods (and possibly various other callables). #407
* Fixed `CropAndPad`, `Pad` and `PadToFixedSize` still clipping `cval` samples
to the `uint8`. They now clip to the input array's dtype's value range. #407
* Fixed `WithColorspace` not propagating polygons to child augmenters. #409
* Fixed `WithHueAndSaturation` not propagating segmentation maps and polygons
to child augmenters. #409
* Fixed `AlphaElementwise` to blend coordinates (for keypoints, polygons,
line strings) on a point-by-point basis following the image's average
alpha value in the sampled alpha mask of the point's coordinate.
Previously, the average over the whole mask was used and then either all
points of the first branch or all of the second branch were used as the
augmentation output. This also affects `SimplexNoiseAlpha` and
`FrequencyNoiseAlpha`. #410
* Fixed many augmenters and helper functions producing errors if the height,
width and/or channels of input arrays were exactly `0` or the channels
were `>512`. #433
* Fixed `Rot90` not supporting `imgaug.ALL`. #434
* Fixed `PiecewiseAffine` possibly generating samples for non-image data
when using `absolute_scale=True` that were not well aligned with the
corresponding images. #437
================================================
FILE: changelogs/0.3.0/v0.3.0.uncleaned.md
================================================
# 0.3.0, Uncleaned Raw Log Of Changes
* Added argument `output_buffer_size` to `multicore.Pool.imap_batches()`
and `multicore.Pool.imap_batches_unordered()` to control the maximum number
of batches in the background augmentation pipeline (allows to limit
maximum RAM demands).
* Increased `max_distance` thresholds for `almost_equals()`,
`exterior_almost_equals()` and `coords_almost_equals()` in `Polygon` and
`LineString` from `1e-6` to `1e-4`.
This should fix false-negative problems related to float inaccuracies.
* Added module `imgaug.augmenters.edges`.
* Added interface `augmenters.edges.BinaryImageColorizerIf`, which
contains the interface for classes used to convert binary images to RGB
images.
* Added `augmenters.edges.RandomColorsBinaryImageColorizer`, which
converts binary images to RGB images by sampling uniformly RGB colors for
`True` and `False` values.
* Added `augmenters.edges.Canny`, which applies canny edge detection with alpha
blending and random coloring to images.
* Renamed `imgaug/external/poly_point_isect.py` to
`imgaug/external/poly_point_isect_py3.py.bak`.
The file is in the library only for completeness and contains python3 syntax.
`poly_point_isect_py2py3.py` is actually used.
* Added dtype gating to `dtypes.clip_()`.
* Added module `augmenters.pooling`. #317
* Added `augmenters.pooling._AbstractPoolingBase`. #317
* Added `augmenters.pooling.AveragePooling`. #317
* Added `augmenters.pooling.MaxPooling`. #317
* Added `augmenters.pooling.MinPooling`. #317
* Added `augmenters.pooling.MedianPooling`. #317
* `augmenters.color.AddToHueAndSaturation`
* [rarely breaking] Refactored `AddToHueAndSaturation` to clean it up.
Re-running old code with the same seeds will now produce different
images. #319
* [rarely breaking] The `value` parameter is now interpreted by the
augmenter to return first the hue and then the saturation value to add,
instead of the other way round.
(This shouldn't affect anybody.) #319
* [rarely breaking] Added `value_hue` and `value_saturation` arguments,
which allow to set individual parameters for hue and saturation
instead of having to use one parameter for both (they may not be set
if `value` is already set).
This changes the order of arguments of the augmenter and code that relied
on that order will now break.
This also changes the output of
`AddToHueAndSaturation.get_parameters()`. #319
* Added `augmenters.color.AddToHue`, a shortcut for
`AddToHueAndSaturation(value_hue=...)`. #319
* Added `augmenters.color.AddToSaturation`, a shortcut for
`AddToHueAndSaturation(value_saturation=...)`. #319
* Added `augmenters.color.WithHueAndSaturation`. #319
* Added `augmenters.color.MultiplyHueAndSaturation`. #319
* Added `augmenters.color.MultiplyHue`. #319
* Added `augmenters.color.MultiplySaturation`. #319
* Refactored `augmenters/weather.py` (general code and docstring cleanup). #336
* [rarely breaking] Refactored `augmenters/convolutional.py`
(general code and docstring cleanup).
This involved changing the random state handling.
Old seeds might now produce different result images for convolutional
augmenters (`Convolve`, `Sharpen`, `Emboss`, `EdgeDetect`,
`DirectedEdgeDetect`). #335
* [rarely breaking] Added argument `polygon_recoverer` to
`augmenters.geometric.PerspectiveTransform`. This changes the order of
arguments of the augmenter and code that relied on that order will now
break. #338
* Changed `_ConcavePolygonRecoverer` to not search for segment intersection
points in polygons with very large absolute coordinate values.
This prevents rare errors due to floating point inaccuracies. #338
* Changed `_ConcavePolygonRecoverer` to raise warnings instead of throwing
exceptions when the underlying search for segment intersection points
crashes. #338
* Added the library to `conda-forge` so it can now be installed via
`conda install imgaug` (provided the conda-forge channel was added
before that). #320 #339
* Changed dependency `opencv-python` to `opencv-python-headless`.
This should improve support for some system without GUIs.
* Refactored code in `augmenters.segmentation` (general code and docstring cleanup). #334
* Refactored code in `augmenters.arithmetic` (general code and docstring cleanup). #328
* Added check to `dtypes.gate_dtypes()` verifying that arguments `allowed`
and `disallowed` have no intersection. #346
* Added dependency `pytest-subtests` for the library's unittests. #366
* Added `imgaug.is_np_scalar()`, analogous to `imgaug.is_np_array()`. #366
* Reworked and refactored code in `dtypes.py` (general code cleanup). #366
* Added `dtypes.normalize_dtypes()`.
* Added `dtypes.normaliz_dtypes()`.
* Refactored `dtypes.promote_array_dtypes_()` to use
`dtypes.change_dtypes_()`.
* Reworked dtype normalization. All functions in the module that required
dtype inputs accept now dtypes, dtype functions, dtype names, ndarrays
or numpy scalars.
* [rarely breaking] `dtypes.restore_dtypes_()`
* Improved error messages.
* Changed so that if `images` is a list of length `N` and `dtypes` is a
list of length `M` and `N!=M`, the function now raises an
`AssertionError`.
* The argument `round` is now ignored if the input array does not have
a float dtype.
* Renamed `dtypes.restore_dtypes_()` to `dtypes.change_dtypes_()` (old name
still exists too and redirects to new name).
* Added `dtypes.change_dtype_()`, analogous to `dtypes.change_dtypes_()`.
* Added `dtypes.increase_itemsize_of_dtype()`.
* Refactored `dtypes.get_minimal_dtype()` to use that new function.
* [rarely breaking] Removed `dtypes.get_minimal_dtype_for_values()`. The
function was not used anywhere in the library.
* [rarely breaking] Removed `dtypes.get_minimal_dtype_by_value_range()`. The
function was not used anywhere in the library.
* Changed `dtypes.get_value_range_of_dtype()` to return a float as the center
value of `uint` dtypes.
* [rarely breaking] Removed argument `affects` from
`dtypes.promote_array_dtypes_()` as it was unnecessary and not used anywhere
in the library. #366
* Added `imgaug.warn()` function. #367
* Changed `multicore.Pool` to produce a warning if it cannot find or call the
function `multiprocessing.cpu_count()` instead of silently failing.
(In both cases it falls back to a default value.) #367
* Refactored code in `multicore` (general code and docstring cleanup). #367
* Improved error messages in `multicore`.
* Added `imgaug.min_pool()`. #369
* Refactored `augmenters.pooling.MinPooling` to use `imgaug.min_pool()`.
* Added `imgaug.median_pool()`. #369
* Refactored `augmenters.pooling.MedianPooling` to use
`imgaug.median_pool()`.
* Added `imgaug.compute_paddings_to_reach_multiples_of()`. #369
* Added `imgaug.pad_to_multiples_of()`. #369
* Refactored `imgaug.pool()` to use `imgaug.pad()` for image padding. #369
* [rarely breaking] Added a `pad_mode` argument to `imgaug.pool()`,
`imgaug.avg_pool()`, `imgaug.max_pool()`, `imgaug.min_pool()` and
`imgaug.median_pool()`. This breaks code relying on the order of the
functions arguments. #369
* Changed the default `pad_mode` of `avg_pool` from `constant` (`cval=128`)
to `reflect`.
* Changed the default `pad_mode` of `max_pool` from `constant` (`cval=0`)
to `edge`.
* Changed the default `pad_mode` of `min_pool` from `constant` (`cval=255`)
to `edge`.
* Changed the default `pad_mode` of `median_pool` from `constant`
(`cval=128`) to `reflect`.
* Renamed argument `cval` to `pad_cval` in `imgaug.pool()`,
`imgaug.avg_pool()` and `imgaug.max_pool()`. The old name `cval` is now
deprecated. #369
* Added `augmenters.color._AbstractColorQuantization`. #347
* Added `augmenters.color.KMeansColorQuantization` and corresponding
`augmenters.color.quantize_colors_kmeans()`. Both deal with quantizing
similar colors using k-Means clustering. #347
* Added a check script for `KMeansColorQuantization` under
`checks/check_kmeans_color_quantization.py`. #347
* Added `augmenters.color.UniformColorQuantization` and corresponding
`augmenters.color.quantize_colors_uniform()`. Both deal with quantizing
similar colors using k-Means clustering. #347
* Added a check script for `UniformColorQuantization` under
`checks/check_uniform_color_quantization.py`. #347
* Added `imgaug.imgaug.normalize_random_state()`. #348
* Added `imgaug.augmenters.segmentation._ensure_image_max_size()`. #348
* Added `imgaug.augmenters.segmentation.PointsSamplerIf`. An interface for
classes used for sampling (usually random) coordinate arrays on images.
* Added `imgaug.augmenters.segmentation._verify_sample_points_images()`. #348
* Added `imgaug.augmenters.segmentation.RegularGridPointsSampler`. A class
used to generate regular grids of `rows x columns` points on images. #348
* Added `imgaug.augmenters.segmentation.RelativeRegularGridPointsSampler`.
Similar to `RegularGridPointsSampler`, but number of rows/columns is set
as fractions of image sizes, leading to more rows/columns for larger
images. #348
* Added `imgaug.augmenters.segmentation.DropoutPointsSampler`. A class
used to randomly drop `p` percent of all coordinates sampled by another
another points sampler. #348
* Added `imgaug.augmenters.segmentation.UniformPointsSampler`. A class used
to sample `N` points on each image with y-/x-coordinates uniformly sampled
using the corresponding image height/width. #348
* Added `imgaug.augmenters.segmentation.SubsamplingPointsSampler`. A class
that ensures that another points sampler does not produce more than
`N` points by subsampling a random subset of the produced points if `N`
is exceeded. #348
* Added `imgaug.augmenters.segmentation.segment_voronoi()`. A function that
converts an image into a voronoi image, i.e. averages the colors within
voronoi cells placed on the image. #348
* Also added in the same module the functions
`_match_pixels_with_voronoi_cells()`, `_generate_pixel_coords()`,
`_compute_avg_segment_colors()`, `_render_segments()`.
* Added `imgaug.augmenters.segmentation.Voronoi`. An augmenter that converts
an image to a voronoi image. #348
* Added a check script for `Voronoi` in `checks/check_voronoi.py`.
* Added `imgaug.augmenters.segmentation.UniformVoronoi`, a shortcut for
`Voronoi(UniformPointsSamper)`. #348
* Added `imgaug.augmenters.segmentation.RegularGridVoronoi`, a shortcut for
`Voronoi(DropoutPointsSampler(RegularGridPointsSampler))`. #348
* Added `imgaug.augmenters.segmentation.RelativeRegularGridVoronoi`, a shortcut
for `Voronoi(DropoutPointsSampler(RelativeRegularGridPointsSampler))`. #348
* Add to `Resize` the ability to resize the shorter and longer sides of
images (instead of only height/width). #349
* Improved the docstrings of most augmenters and added code examples. #302
* Changes to support numpy 1.17 #302
* [rarely breaking] Deactivated support for `int64` in
`imgaug.dtypes.clip_()`. This is due to numpy 1.17 turning `int64` to
`float64` in `numpy.clip()` (possible that this happened in some way
before 1.17 too).
* [rarely breaking] Changed `imgaug.dtypes.clip()` to never clip `int32`
in-place, as `numpy.clip()` turns it into `float64` since 1.17 (possible
that this happend in some way before 1.17 too).
* [rarely breaking] Deactivated support for `int64` in
`ReplaceElementwise`. See `clip` issue above.
* [rarely breaking] Changed `parameters.DiscreteUniform` to always return
arrays of dtype `int32`. Previously it would automatically return
`int64`.
* [rarely breaking] Changed `parameters.Deterministic` to always return
`int32` for integers and always `float32` for floats.
* [rarely breaking] Changed `parameters.Choice` to limit integer
dtypes to `int32` or lower, uints to `uint32` or lower and floats
to `float32` or lower.
* [rarely breaking] Changed `parameters.Binomial` and `parameters.Poisson`
to always return `int32`.
* [rarely breaking] Changed `parameters.Normal`,
`parameters.TruncatedNormal`, `parameters.Laplace`,
`parameters.ChiSquare`, `parameters.Weibull`, `parameters.Uniform` and
`parameters.Beta` to always return `float32`.
* [rarely breaking] Changed `augmenters.arithmetic.Add`,
`augmenters.arithmetic.AddElementwise`, `augmenters.arithmetic.Multiply`
and `augmenters.arithmetic.MultiplyElementwise` to no longer internally
increase itemsize of dtypes by a factor of 2 for
dtypes `uint16`, `int8` and `uint16`. For `Multiply*` this also
covers `float16` and `float32`. This protects against crashes due to
clipping `int64` or `uint64` data. In rare cases this can lead to
overflows if `image + random samples` or `image * random samples`
exceeds the value range of `int32` or `uint32`. This change may affect
various other augmenters that are wrappers around the mentioned ones,
e.g. `AdditiveGaussianNoise`.
* [rarely breaking] Decreased support of dtypes `uint16`, `int8`,
`int16`, `float16`, `float32` and `bool` in `augmenters.arithmetic.Add`,
`AddElementwise`, `Multiply` and `MultiplyElementwise` from "yes" to
"limited".
* [rarely breaking] Decreased support of dtype `int64` in
`augmenters.arithmetic.ReplaceElementwise` from "yes" to "no". This also
affects all `*Noise` augmenters (e.g. `AdditiveGaussianNoise`,
`ImpulseNoise`), all `Dropout` augmenters, all `Salt` augmenters and
all `Pepper` augmenters.
* [rarely breaking] Changed `augmenters.contrast.adjust_contrast_log`
and thereby `LogContrast` to no longer support dtypes `uint32`, `uint64`,
`int32` and `int64`.
* Replaced all calls of `imgaug.imgaug.do_assert` by ordinary `assert`
statements. This is a bit less secure, but should overall improve
performance. #387
* Added error messages to `assert` statements throughout the library. #387
* Improved code style and documentation of (#389, #402):
* `imgaug.augmentables.bbs`.
* `imgaug.augmentables.heatmaps`.
* `imgaug.augmentables.kps`.
* `imgaug.augmentables.lines`.
* `imgaug.augmentables.normalization`.
* `imgaug.augmentables.polys`.
* `imgaug.augmentables.segmaps`.
* `imgaug.augmentables.utils`.
* `imgaug.imgaug`.
* `imgaug.parameters`.
* `imgaug.augmenters.weather`.
* `imgaug.augmenters.size`.
* `imgaug.augmenters.segmentation`.
* `imgaug.augmenters.meta`.
* `imgaug.augmenters.geometric`.
* `imgaug.augmenters.flip`.
* `imgaug.augmenters.contrast`.
* `imgaug.augmenters.blur`.
* `imgaug.augmenters.blend`.
* `imgaug.augmenters.weather`.
* Removed image-channel check for cv2-based warp in `Affine`. Images with any
channel number can now be warped using the cv2 backend (previously: only
`<=4`, others would be warped via skimage). #381
* Improved performance of `augmenters.flip.Fliplr`. #385
* Improved performance of `augmenters.flip.Flipud`. #385
* Added function `augmenters.flip.fliplr()`. #385
* Added function `augmenters.flip.flipud()`. #385
* Removed the requirement to implement `_augment_keypoints()` and
`_augment_heatmaps()` in augmenters. The methods now default to doing
nothing. Also removed all such noop-implementations of these methods from
all augmenters. #380
* Increased minimum version requirement for `scikit-image` to
`0.14.2`. #377, #399
* Marked the following functions as deprecated (#398):
* `imgaug.augmenters.meta.clip_augmented_image_`
* `imgaug.augmenters.meta.clip_augmented_image`
* `imgaug.augmenters.meta.clip_augmented_images_`
* `imgaug.augmenters.meta.clip_augmented_images`
* Refactored all calls of `warnings.warn()` to use `imgaug.imgaug.warn()
instead. #401
* [rarely breaking] Refactored most of the augmenters from functions to
classes. Previously, some augmenters were functions that returned an
instance of another augmenter (with adjusted hyperparameters) when being
called. Aside from a few corner cases, these have been switched to classes
inheriting from the augmenters that were previously returned. This should
make some outputs less confusing (ass `print(A())` does not lead to class
`B` being printed). All arguments stayed the same and this is not expected
to affect any user code negatively. The augmenters listed below are
affected by this change. #396
* `imgaug.augmenters.arithmetic.AdditiveGaussianNoise`
* `imgaug.augmenters.arithmetic.AdditiveLaplaceNoise`
* `imgaug.augmenters.arithmetic.AdditivePoissonNoise`
* `imgaug.augmenters.arithmetic.Dropout`
* `imgaug.augmenters.arithmetic.CoarseDropout`
* `imgaug.augmenters.arithmetic.ImpulseNoise`
* `imgaug.augmenters.arithmetic.SaltAndPepper`
* `imgaug.augmenters.arithmetic.CoarseSaltAndPepper`
* `imgaug.augmenters.arithmetic.Salt`
* `imgaug.augmenters.arithmetic.CoarseSalt`
* `imgaug.augmenters.arithmetic.Pepper`
* `imgaug.augmenters.arithmetic.CoarsePepper`
* `imgaug.augmenters.blend.SimplexNoiseAlpha`
* `imgaug.augmenters.blend.FrequencyNoiseAlpha`
* `imgaug.augmenters.blur.MotionBlur`
* `imgaug.augmenters.contrast.MultiplyHueAndSaturation`
* `imgaug.augmenters.contrast.MultiplyHue`
* `imgaug.augmenters.contrast.MultiplySaturation`
* `imgaug.augmenters.contrast.AddToHue`
* `imgaug.augmenters.contrast.AddToSaturation`
* `imgaug.augmenters.contrast.Grayscale`
* `imgaug.augmenters.contrast.GammaContrast`
* `imgaug.augmenters.contrast.SigmoidContrast`
* `imgaug.augmenters.contrast.LogContrast`
* `imgaug.augmenters.contrast.LinearContrast`
* `imgaug.augmenters.convolutional.Sharpen`
* `imgaug.augmenters.convolutional.Emboss`
* `imgaug.augmenters.convolutional.EdgeDetect`
* `imgaug.augmenters.convolutional.DirectedEdgeDetect`
* `imgaug.augmenters.meta.OneOf`
* `imgaug.augmenters.meta.AssertLambda`
* `imgaug.augmenters.meta.AssertShape`
* `imgaug.augmenters.size.Pad`
* `imgaug.augmenters.size.Crop`
* `imgaug.augmenters.weather.Clouds`
* `imgaug.augmenters.weather.Fog`
* `imgaug.augmenters.weather.Snowflakes`
* Marked `imgaug.augmenters.arithmetic.ContrastNormalization` as deprecated.
Use `imgaug.augmenters.contrast.LinearContrast` instead. #396
* Renamed argument `X` of `imgaug.augmentables.kps.compute_geometric_median()`
to `points`. The old argument is still accepted, but now deprecated. #402
* Refactored `Affine` to improve code quality and minimize code
duplication. #407
* [rarely breaking] Removed `Affine.VALID_DTYPES_CV2_ORDER_0`.
* [rarely breaking] Removed `Affine.VALID_DTYPES_CV2_ORDER_NOT_0`.
* [rarely breaking] Removed `Affine.order_map_skimage_cv2`.
* [rarely breaking] Removed `Affine.mode_map_skimage_cv2`.
* Refactored `CropAndPad` to improve code quality and minimize code
duplication. #407
* Refactored module `size` to decrease code duplication between different
augmenters. #407
* Changed `imgaug.imgaug.pad` to automatically clip the `cval` argument
to the value range of the array to be padded. #407
* Moved matrix generation logic of augmenters in module `convolutional`
to classes, one per augmenter (i.e. one per category of convolutional
matrix). This should avoid errors related to pickling of functions. #407
* Refactored `imgaug.augmenters.color` (#409):
* Added to `imgaug.augmenters.color` the constants `CSPACE_RGB`,
`CSPACE_BGR`, `CSPACE_GRAY`, `CSPACE_CIE`, `CSPACE_YCrCb`, `CSPACE_HSV`,
`CSPACE_HLS`, `CSPACE_Lab`, `CSPACE_Luv`, `CSPACE_YUV`, `CSPACE_ALL`.
* Added `imgaug.augmenters.color.change_colorspace_()`.
* Added `imgaug.augmenters.color.change_colorspace_batch_()`.
* Refactored color augmenters to use `change_colorspace_()` and
`change_colorspace_batch_()`.
* [rarely breaking] Removed attributes `colorspace_changer` and
`colorspace_changer_inv` from `AddToHueAndSaturation`.
* Added attribute `from_colorspace` to `AddToHueAndSaturation`. This also
affects `AddToHue` and `AddToSaturation`.
* Added output `from_colorspace` to
`AddToHueAndSaturation.get_parameters()`. This also affects `AddToHue`
and `AddToSaturation`.
* [rarely breaking] Changed colorspace transformations throughout the
library to fail if the input image does not have three channels.
* Changed colorspace transformations throughout the library to also
support `YUV` colorspace.
* Added function `imgaug.augmentables.utils.copy_augmentables`. #410
* Refactored `Alpha` to decrease code duplication. #410
* Refactored `AlphaElementwise` to decrease code duplication. #410
* [rarely breaking] Changed `AlphaElementwise` to verify for keypoint
and line string augmentation that the number of coordinates before/after
augmentation does not change. Previously this was allowed. This also
affects `SigmoidNoiseAlpha` and `FrequenceNoiseAlpha`.
* [rarely breaking] Changed `AlphaElementwise` to use for keypoint,
line string and bounding box augmentation a pointwise approach, where
per coordinate a decision is made whether the new coordinate from the
first branch's (augmented) results or the second branch's (augmented)
results are used. The decision is based on the average alpha mask value
at the xy-location of the coordinate. For polygons, the old mode is
still used where either all coordinates from the first branch's results
or the second branch's results are used. This also affects
`SigmoidNoiseAlpha` and `FrequenceNoiseAlpha`.
* Added function `imgaug.augmenters.arithmetic.add_scalar()`. #411
* Refactored `Add` to use that function.
* Added function `imgaug.augmenters.arithmetic.add_elementwise()`. #411
* Refactored `AddElementwise` to use that function.
* Removed restrictions of `value` parameter in `AddElementwise`.
The value range is now no longer limited to `[-255, 255]` and floats
are now allowed.
* Added function `imgaug.augmenters.arithmetic.replace_elementwise_()`. #411
* Refactored `ReplaceElementwise` to use that function.
* [rarely breaking] Removed class constant `ALLOW_DTYPES_CUSTOM_MINMAX`
from `Invert`.
* [rarely breaking] Removed attribute `dtype_kind_to_invert_func` from
`Invert`.
* Added function `imgaug.augmenters.arithmetic.compress_jpg()`. #411
* Refactored `JpegCompression` to use that function.
* [rarely breaking] Removed attribute `maximum_quality` from
`JpegCompression`.
* [rarely breaking] Removed attribute `minimum_quality` from
`JpegCompression`.
* Refactored `Affine` to improve code quality and decrease code
duplication. #413
* Refactored `PiecewiseAffine` to improve code quality and decrease code
duplication. #413
* Refactored `PerspectiveTransform` to improve code quality and decrease code
duplication. #413
* Refactored `ElasticTransformation` to improve code quality and decrease code
duplication. #413
* [rarely breaking] Renamed `ElasticTransformation.generate_shift_maps()` to
`ElasticTransformation._generate_shift_maps()`.
* [rarely breaking] Renamed `ElasticTransformation.map_coordinates()` to
`ElasticTransformation._map_coordinates()`.
* Refactored `Rot90` to improve code quality and decrease code
duplication. #413
* Added `imgaug.testutils.ArgCopyingMagicMock`. #413
* Refactored `Augmenter.augment_images()`, `Augmenter.augment_heatmaps()`,
`Augmenter.augment_segmentation_maps()`, `Augmenter.augment_polygons()`,
`Augmenter.augment_line_strings()` and `Augmenter._augment_coord_augables()`
to improve code quality and remove redundancies. #413
* Refactored `imgaug.imgaug.imresize_single_image()`. #413
* Added module `imgaug.validation`. #413
* Added `imgaug.validation.convert_iterable_to_string_of_types()`.
* Added `imgaug.validation.is_iterable_of()`.
* Added `imgaug.validation.assert_is_iterable_of()`.
* Refactored `Sequential` to reduce code duplication. #413
* Refactored `SomeOf` to improve code quality. #413
* Refactored `Sometimes` to reduce code duplication. #413
* Refactored `AssertShape` to reduce code duplication. #413
* Refactored `ChannelShuffle` to improve code quality. #413
* [rarely breaking] Changed `KeypointsOnImage.from_keypoints_image()` to
return `(x+0.5, y+0.5)` instead of `(x, y)` where `(x, y)` denotes the
coordinates of the pixel in which a maximum was found. This change matches
the standard that all pixels are given with subpixel accuracy and therefore
any whole pixel with a maximum should denote the coordinates of that
pixel's center. #413
* Changed default `output_buffer_size` in `Augmenter.augment_batches()` from
"unlimited" to `10*C`, where `C` is the number of logical CPU cores. #417
## Improved Segmentation Map Augmentation #302
Augmentation of Segmentation Maps is now faster and more memory efficient.
This required some breaking changes to `SegmentationMapOnImage`.
To adapt to the new version, the following steps should be sufficient for most
users:
* Rename all calls of `SegmentationMapOnImage` to `SegmentationMapsOnImage`
(Map -> Maps).
* Rename all calls of `SegmentationMapsOnImage.get_arr_int()` to
`SegmentationMapsOnImage.get_arr()`.
* Remove the argument `nb_classes` from all calls of `SegmentationMapsOnImage`.
* Remove the arguments `background_id` and `background_threshold` from all
calls as these are no longer supported.
* Ensure that the input array to `SegmentationMapsOnImage` is always an
int-like (int, uint or bool).
Float arrays are no longer accepted.
* Adapt all calls `SegmentationMapsOnImage.draw()` and
`SegmentationMapsOnImage.draw_on_image()`, as both of these now return a
list of drawn images instead of a single array. (For a segmentation map
array of shape `(H,W,C)` they return `C` drawn images. In most cases `C=1`,
so simply call `draw()[0]` or `draw_on_image()[0]`.)
* Ensure that if `SegmentationMapsOnImage.arr` is accessed anywhere, the
respective code can handle the new `int32` `(H,W,#maps)` array form.
Previously it was `float32` and the channel-axis had the same size as the
max class id (+1) that could appear in the map.
Changes:
- Changes to class `SegmentationMapOnImage`:
- Renamed `SegmentationMapOnImage` to plural `SegmentationMapsOnImage`
and deprecated the old name.
This was changed due to the input array now being allowed to contain
several channels, with each such channel containing one full segmentation
map.
- Changed `SegmentationMapsOnImage.__init__` to produce a deprecation
warning for float arrays as `arr` argument.
- **[breaking]** Changed `SegmentationMapsOnImage.__init__` to no longer
accept `uint32` and larger itemsizes as `arr` argument, only `uint16`
and below is accepted. For `int` the allowed maximum is `int32`.
- Changed `SegmentationMapsOnImage.__init__` to always accept `(H,W,C)`
`arr` arguments.
- **[breaking]** Changed `SegmentationMapsOnImage.arr` to always be
`int32` `(H,W,#maps)` (previously: `float32` `(H,W,#nb_classes)`).
- Deprecated `nb_classes` argument in `SegmentationMapsOnImage.__init__`.
The argument is now ignored.
- Added `SegmentationMapsOnImage.get_arr()`, which always returns a
segmentation map array with similar dtype and number of dimensions as
was originally input when creating a class instance.
- Deprecated `SegmentationMapsOnImage.get_arr_int()`.
The method is now an alias for `get_arr()`.
- `SegmentationMapsOnImage.draw()`:
- **[breaking]** Removed argument `return_foreground_mask` and
corresponding optional output. To generate a foreground mask
for the `c`-th segmentation map on a given image (usually `c=0`),
use `segmentation_map.arr[:, :, c] != 0`, assuming that `0` is
the integer index of your background class.
- **[breaking]** Changed output of drawn image to be a list of arrays
instead of a single array (one per `C` in input array `(H,W,C)`).
- Refactored to be a wrapper around
`SegmentationMapsOnImage.draw_on_image()`.
- The `size` argument may now be any of: A single `None` (keep shape),
a single integer (use as height and width), a single float (relative
change to shape) or a tuple of these values. ("shape" here denotes
the value of the `.shape` attribute.)
- `SegmentationMapsOnImage.draw_on_image()`:
- **[breaking]** The argument `background_threshold` is now deprecated
and ignored. Providing it will lead to a deprecation warning.
- **[breaking]** Changed output of drawn image to be a list of arrays
instead of a single array (one per `C` in input array `(H,W,C)`).
- Changed `SegmentationMapsOnImage.resize()` to use nearest neighbour
interpolation by default.
- **[rarely breaking]** Changed `SegmentationMapsOnImage.copy()` to create
a shallow copy instead of being an alias for `deepcopy()`.
- Added optional arguments `arr` and `shape` to
`SegmentationMapsOnImage.copy()`.
- Added optional arguments `arr` and `shape` to
`SegmentationMapsOnImage.deepcopy()`.
- Refactored `SegmentationMapsOnImage.pad()`,
`SegmentationMapsOnImage.pad_to_aspect_ratio()` and
`SegmentationMapsOnImage.resize()` to generate new object instances via
`SegmentationMapsOnImage.deepcopy()`.
- **[rarely breaking]** Renamed `SegmentationMapsOnImage.input_was` to
`SegmentationMapsOnImage._input_was`.
- **[rarely breaking]** Changed `SegmentationMapsOnImage._input_was` to
always save `(input array dtype, input array ndim)` instead of mixtures
of strings/ints that varied by dtype kind.
- **[rarely breaking]** Restrict `shape` argument in
`SegmentationMapsOnImage.__init__` to tuples instead of accepting all
iterables.
- **[breaking]** Removed `SegmentationMapsOnImage.to_heatmaps()` as the
new segmentation map class is too different to sustain the old heatmap
conversion methods.
- **[breaking]** Removed `SegmentationMapsOnImage.from_heatmaps()` as the
new segmentation map class is too different to sustain the old heatmap
conversion methods.
- Changes to class `Augmenter`:
- **[breaking]** Automatic segmentation map normalization from arrays or
lists of arrays now expects a single `(N,H,W,C)` array (before:
`(N,H,W)`) or a list of `(H,W,C)` arrays (before: `(H,W)`).
This affects valid segmentation map inputs for `Augmenter.augment()`
and its alias `Augmenter.__call__()`,
`imgaug.augmentables.batches.UnnormalizedBatch()` and
`imgaug.augmentables.normalization.normalize_segmentation_maps()`.
- Added `Augmenter._augment_segmentation_maps()`.
- Changed `Augmenter.augment_segmentation_maps()` to no longer be a
wrapper around `Augmenter.augment_heatmaps()` and instead call
`Augmenter._augment_segmentation_maps()`.
- Added special segmentation map handling to all augmenters that modified
segmentation maps
(`Sequential`, `SomeOf`, `Sometimes`, `WithChannels`,
`Lambda`, `AssertLambda`, `AssertShape`,
`Alpha`, `AlphaElementwise`, `WithColorspace`, `Fliplr`, `Flipud`, `Affine`,
`AffineCv2`, `PiecewiseAffine`, `PerspectiveTransform`, `ElasticTransformation`,
`Rot90`, `Resize`, `CropAndPad`, `PadToFixedSize`, `CropToFixedSize`,
`KeepSizeByResize`).
- **[rarely breaking]** This changes the order of arguments in
`Lambda.__init__()`, `AssertLambda.__init__()`, `AssertShape.__init__()`
and hence breaks if one relied on that order.
## New RNG handling #375
* Adapted library to automatically use the new `numpy.random` classes of
numpy 1.17 -- if they are available. If they are not available (i.e. numpy
version is <=1.16), the library automatically falls back to the old
interface (i.e. `numpy.random.RandomState`).
* Added module `imgaug.random`.
* Added class `imgaug.random.RNG`. This is now the preferred way to represent
RNG states (previously: `numpy.random.RandomState`). Instantiate it
via e.g. `RNG(1052912236)`, where `1052912236` is a seed.
* Added `imgaug.random.supports_new_rng_style()`.
* Added `imgaug.random.get_global_rng()`.
* Added `imgaug.random.seed()`.
* Added `imgaug.random.normalize_generator()`.
* Added `imgaug.random.normalize_generator_()`.
* Added `imgaug.random.convert_seed_to_generator()`.
* Added `imgaug.random.convert_seed_sequence_to_generator()`.
* Added `imgaug.random.create_pseudo_random_generator_()`.
* Added `imgaug.random.create_fully_random_generator()`.
* Added `imgaug.random.generate_seed_()`.
* Added `imgaug.random.generate_seeds_()`.
* Added `imgaug.random.copy_generator()`.
* Added `imgaug.random.copy_generator_unless_global_generator()`.
* Added `imgaug.random.reset_generator_cache_()`.
* Added `imgaug.random.derive_generator_()`.
* Added `imgaug.random.derive_generators_()`.
* Added `imgaug.random.get_generator_state()`.
* Added `imgaug.random.set_generator_state_()`.
* Added `imgaug.random.is_generator_equal_to()`.
* Added `imgaug.random.advance_generator_()`.
* Added `imgaug.random.polyfill_integers()`.
* Added `imgaug.random.polyfill_random()`.
* Refactored all arguments related to random state handling to also accept
`imgaug.random.RNG`, as well as the new numpy random classes. This
particularly affects `imgaug.augmenters.meta.Augmenter` and
`imgaug.parameters.StochasticParameter` (argument `random_state` for both).
* Marked old RNG related functions in `imgaug.imgaug` as deprecated.
They will now produce warnings and redirect towards corresponding functions
in `imgaug.random`. This does not yet affect `imgaug.imgaug.seed()`.
It does affect the functions listed below.
* `imgaug.imgaug.normalize_random_state()`.
* `imgaug.imgaug.current_random_state()`.
* `imgaug.imgaug.new_random_state()`.
* `imgaug.imgaug.dummy_random_state()`.
* `imgaug.imgaug.copy_random_state()`.
* `imgaug.imgaug.derive_random_state()`.
* `imgaug.imgaug.derive_random_states()`.
* `imgaug.imgaug.forward_random_state()`.
* [rarely breaking] Removed `imgaug.imgaug.CURRENT_RANDOM_STATE`.
Use `imgaug.random.get_global_rng()` instead.
* [rarely breaking] Removed `imgaug.imgaug.SEED_MIN_VALUE`.
Use `imgaug.random.SEED_MIN_VALUE` instead or sample seeds via
`imgaug.random.generate_seeds_()`.
* [rarely breaking] Removed `imgaug.imgaug.SEED_MAX_VALUE`.
Use `imgaug.random.SEED_MAX_VALUE` instead or sample seeds via
`imgaug.random.generate_seeds_()`.
* Optimized RNG handling throughout all augmenters to minimize the number of
RNG copies. RNGs are now re-used as often as possible. This improves
performance, but has the disadvantage that adding images to a batch will now
often affect the samples of the other images in the same batch. E.g.
previously for a batch of images `A,B,C` and seed `1`, the samples of `A,B,C`
would remain unchanged if the batch was changed to `A,B,C,D` (provided the
seed stayed the same). Now, if `D` is added the samples of `A,B,C` may
change.
* [breaking] The above listed changes will lead to different values being
sampled for the same seeds (compared to past versions of the library).
* [breaking] The seed for `imgaug`'s global random number generator is now
sampled from numpy's default random number generator. That means, that every
run of a program using `imgaug` will by default use a different seed and
hence result in different samples. Previously, a fixed seed was used,
resulting in the same samples for each run (unless the seed was manually
changed to a fixed one). It also means that seeding numpy will automatically
also seed imgaug (not guarantueed that this behaviour will be kept in
future releases). The change from fixed to random seed was done, because the
old (fixed) behaviour didn't match the common practice (and especially not
numpy's standard behaviour) and hence led to confusion. #408
## Fixes
* Fixed an issue with `Polygon.clip_out_of_image()`,
which would lead to exceptions if a polygon had overlap with an image,
but not a single one of its points was inside that image plane.
* Fixed `multicore` methods falsely not accepting
`augmentables.batches.UnnormalizedBatch`.
* `Rot90` now uses subpixel-based coordinate remapping.
I.e. any coordinate `(x, y)` will be mapped to `(H-y, x)` for a rotation by
90deg.
Previously, an integer-based remapping to `(H-y-1, x)` was used.
Coordinates are e.g. used by keypoints, bounding boxes or polygons.
* `augmenters.arithmetic.Invert`
* [rarely breaking] If `min_value` and/or `max_value` arguments were
set, `uint64` is no longer a valid input array dtype for `Invert`.
This is due to a conversion to `float64` resulting in loss of resolution.
* Fixed `Invert` in rare cases restoring dtypes improperly.
* Fixed `dtypes.gate_dtypes()` crashing if the input was one or more numpy
scalars instead of numpy arrays or dtypes.
* Fixed `augmenters.geometric.PerspectiveTransform` producing invalid
polygons (more often with higher `scale` values). #338
* Fixed errors caused by `external/poly_point_isect_py2py3.py` related to
floating point inaccuracies (changed an epsilon from `1e-10` to `1e-4`,
rounded some floats). #338
* Fixed `Superpixels` breaking when a sampled `n_segments` was `<=0`.
`n_segments` is now treated as `1` in these cases.
* Fixed `ReplaceElementwise` both allowing and disallowing dtype `int64`. #346
* Fixed `BoundingBox.deepcopy()` creating only shallow copies of labels. #356
* Fixed `dtypes.change_dtypes_()` #366
* Fixed argument `round` being ignored if input images were a list.
* Fixed failure if input images were a list and dtypes a single numpy
dtype function.
* Fixed `dtypes.get_minimal_dtype()` failing if argument `arrays` contained
not *exactly* two items. #366
* Fixed calls of `CloudLayer.get_parameters()` resulting in errors. #309
* Fixed `SimplexNoiseAlpha` and `FrequencyNoiseAlpha` not handling
`sigmoid` argument correctly. #343
* Fixed `SnowflakesLayer` crashing for grayscale images. #345
* Fixed `Affine` heatmap augmentation crashing for arrays with more than
four channels and `order!=0`. #381
* Fixed an outdated error message in `Affine`. #381
* Fixed `Polygon.clip_out_of_image()` crashing if the intersection between
polygon and image plane was an edge or point. #382
* Fixed `Polygon.clip_out_of_image()` potentially failing for polygons
containing two or fewer points. #382
* Fixed `Polygon.is_out_of_image()` returning wrong values if the image plane
was fully contained inside the polygon with no intersection between the
image plane and the polygon edge. #382
* Fixed `Fliplr` and `Flipud` using for coordinate-based inputs and image-like
inputs slightly different conditions for when to actually apply
augmentations. #385
* Fixed `Convolve` using an overly restrictive check when validating inputs
for `matrix` w.r.t. whether they are callables. The check should now also
support class methods (and possibly various other callables). #407
* Fixed `CropAndPad`, `Pad` and `PadToFixedSize` still clipping `cval` samples
to the `uint8`. They now clip to the input array's dtype's value range. #407
* Fixed `WithColorspace` not propagating polygons to child augmenters. #409
* Fixed `WithHueAndSaturation` not propagating segmentation maps and polygons
to child augmenters. #409
* Fixed `AlphaElementwise` to blend coordinates (for keypoints, polygons,
line strings) on a point-by-point basis following the image's average
alpha value in the sampled alpha mask of the point's coordinate.
Previously, the average over the whole mask was used and then either all
points of the first branch or all of the second branch were used as the
augmentation output. This also affects `SimplexNoiseAlpha` and
`FrequencyNoiseAlpha`. #410
* Fixed many augmenters and helper functions producing errors if the height,
width and/or channels of input arrays were exactly `0` or the channels
were `>512`. #433
* Fixed `Rot90` not supporting `imgaug.ALL`. #434
* Fixed `PiecewiseAffine` possibly generating samples for non-image data
when using `absolute_scale=True` that were not well aligned with the
corresponding images. #437
================================================
FILE: changelogs/0.4.0/20191003_reworked_aug_methods.md
================================================
# Reworked Augmentation Methods #451 #566
* Added method `to_normalized_batch()` to `imgaug.augmentables.batches.Batch`
to have the same interface in `Batch` and `UnnormalizedBatch`.
* Added method `get_augmentable()` to
`imgaug.augmentables.batches.Batch` and
`imgaug.augmentables.batches.UnnormalizedBatch`.
* Added method `get_augmentable_names()` to
`imgaug.augmentables.batches.Batch` and
`imgaug.augmentables.batches.UnnormalizedBatch`.
* Added method `to_batch_in_augmentation()` to
`imgaug.augmentables.batches.Batch`.
* Added method `fill_from_batch_in_augmentation_()` to
`imgaug.augmentables.batches.Batch`.
* Added method `fill_from_augmented_normalized_batch_()` to
`imgaug.augmentables.batches.UnnormalizedBatch`.
* Added class `imgaug.augmentables.batches._BatchInAugmentation`.
* Added method `_augment_batch_()` in `imgaug.augmenters.meta.Augmenter`.
This method is now called from `augment_batch_()`.
* Changed `augment_images()` in `imgaug.augmenters.meta.Augmenter` to be
a thin wrapper around `augment_batch_()`.
* Changed `augment_heatmaps()` in `imgaug.augmenters.meta.Augmenter` to be
a thin wrapper around `augment_batch_()`.
* Changed `augment_segmentation_maps()` in `imgaug.augmenters.meta.Augmenter`
to be a thin wrapper around `augment_batch_()`.
* Changed `augment_keypoints()` in `imgaug.augmenters.meta.Augmenter` to be
a thin wrapper around `augment_batch_()`.
* Changed `augment_bounding_boxes()` in `imgaug.augmenters.meta.Augmenter` to be
a thin wrapper around `augment_batch_()`.
* Changed `augment_polygons()` in `imgaug.augmenters.meta.Augmenter` to be
a thin wrapper around `augment_batch_()`.
* Changed `augment_line_strings()` in `imgaug.augmenters.meta.Augmenter` to be
a thin wrapper around `augment_batch_()`.
* Changed `augment_image()`, `augment_images()`, `augment_heatmaps()`,
`augment_segmentation_maps()`, `augment_keypoints()`,
`augment_bounding_boxes()`, `augment_polygons()` and `augment_line_strings()`
to return `None` inputs without change. Previously they resulted in an
exception. This is more consistent with the behaviour in the other
`augment_*` methods.
* Added method `imgaug.augmenters.meta.Augmenter.augment_batch_()`,
similar to `augment_batch()`, but explicitly works in-place and has a
`parent` parameter.
* Deprecated `imgaug.augmenters.meta.Augmenter.augment_batch()`.
Use `.augment_batch_()` instead.
* Changed `augment_images()` to no longer be abstract. It defaults
to not changing the input images.
* Refactored `Sequential` to use single `_augment_batch_()` method.
* Refactored `SomeOf` to use single `_augment_batch_()` method.
* Refactored `Sometimes` to use single `_augment_batch_()` method.
* Refactored `AveragePooling`, `MaxPooling`, `MinPooling`, `MedianPooling`
to use single `_augment_batch_()` method.
* Refactored `ElasticTransformation` to use single `_augment_batch_()` method.
* Refactored `Alpha` to use single `_augment_batch_()` method.
* Refactored `AlphaElementwise` to use single `_augment_batch_()` method.
* Refactored `WithColorspace` to use single `_augment_batch_()` method.
* Refactored `WithHueAndSaturation` to use single `_augment_batch_()` method.
* Refactored `Fliplr` to use single `_augment_batch_()` method.
* Refactored `Flipud` to use single `_augment_batch_()` method.
* Refactored `Affine` to use single `_augment_batch_()` method.
* Refactored `Rot90` to use single `_augment_batch_()` method.
* Refactored `Resize` to use single `_augment_batch_()` method.
* Refactored `CropAndPad` to use single `_augment_batch_()` method.
* Refactored `PadToFixedSize` to use single `_augment_batch_()` method.
* Refactored `CropToFixedSize` to use single `_augment_batch_()` method.
* Refactored `KeepSizeByResize` to use single `_augment_batch_()` method.
* Refactored `PiecewiseAffine` to use single `_augment_batch_()` method.
* Refactored `PerspectiveTransform` to use single `_augment_batch_()` method.
* Refactored `WithChannels` to use single `_augment_batch_()` method.
* Refactored `Add` to use single `_augment_batch_()` method.
* Refactored `AddElementwise` to use single `_augment_batch_()` method.
* Refactored `Multiply` to use single `_augment_batch_()` method.
* Refactored `MultiplyElementwise` to use single `_augment_batch_()` method.
* Refactored `ReplaceElementwise` to use single `_augment_batch_()` method.
* Refactored `Invert` to use single `_augment_batch_()` method.
* Refactored `JpegCompression` to use single `_augment_batch_()` method.
* Refactored `GaussianBlur` to use single `_augment_batch_()` method.
* Refactored `AverageBlur` to use single `_augment_batch_()` method.
* Refactored `MedianBlur` to use single `_augment_batch_()` method.
* Refactored `BilateralBlur` to use single `_augment_batch_()` method.
* Refactored `AddToHueAndSaturation` to use single `_augment_batch_()` method.
* Refactored `ChangeColorspace` to use single `_augment_batch_()` method.
* Refactored `_AbstractColorQuantization` to use single `_augment_batch_()`
method.
* Refactored `_ContrastFuncWrapper` to use single `_augment_batch_()` method.
* Refactored `AllChannelsCLAHE` to use single `_augment_batch_()` method.
* Refactored `CLAHE` to use single `_augment_batch_()` method.
* Refactored `AllChannelsHistogramEqualization` to use single
`_augment_batch_()` method.
* Refactored `HistogramEqualization` to use single `_augment_batch_()` method.
* Refactored `Convolve` to use single `_augment_batch_()` method.
* Refactored `Canny` to use single `_augment_batch_()` method.
* Refactored `ChannelShuffle` to use single `_augment_batch_()` method.
* Refactored `Superpixels` to use single `_augment_batch_()` method.
* Refactored `Voronoi` to use single `_augment_batch_()` method.
* Refactored `FastSnowyLandscape` to use single `_augment_batch_()` method.
* Refactored `CloudLayer` to use single `_augment_batch_()` method.
* Refactored `SnowflakesLayer` to use single `_augment_batch_()` method.
* Added validation of input arguments to `KeypointsOnImage.from_xy_array()`.
* Improved validation of input arguments to
`BoundingBoxesOnImage.from_xyxy_array()`.
* Added method `BoundingBoxesOnImage.to_keypoints_on_image()`.
* Added method `PolygonsOnImage.to_keypoints_on_image()`.
* Added method `LineStringsOnImage.to_keypoints_on_image()`.
* Added method `KeypointsOnImage.to_keypoints_on_image()`.
* Added method `BoundingBoxesOnImage.invert_to_keypoints_on_image_()`.
* Added method `PolygonsOnImage.invert_to_keypoints_on_image_()`.
* Added method `LineStringsOnImage.invert_to_keypoints_on_image_()`.
* Added method `KeypointsOnImage.invert_to_keypoints_on_image_()`.
* Added method `imgaug.augmentables.polys.recover_psois_()`.
* Added method `imgaug.augmentables.utils.convert_cbaois_to_kpsois()`.
* Added method `imgaug.augmentables.utils.invert_convert_cbaois_to_kpsois_()`.
* Added method `imgaug.augmentables.utils.deepcopy_fast()`.
* Added method `imgaug.augmentables.kps.BoundingBoxesOnImage.to_xy_array()`.
* Added method `imgaug.augmentables.kps.PolygonsOnImage.to_xy_array()`.
* Added method `imgaug.augmentables.kps.LineStringsOnImage.to_xy_array()`.
* Added method `imgaug.augmentables.kps.KeypointsOnImage.fill_from_xy_array_()`.
* Added method `imgaug.augmentables.kps.BoundingBoxesOnImage.fill_from_xy_array_()`.
* Added method `imgaug.augmentables.kps.PolygonsOnImage.fill_from_xy_array_()`.
* Added method `imgaug.augmentables.kps.LineStringsOnImage.fill_from_xy_array_()`.
* Added method `imgaug.augmentables.bbs.BoundingBoxesOnImage.fill_from_xyxy_array_()`.
* Added method `imgaug.augmentables.bbs.BoundingBox.from_point_soup()`.
* Added method `imgaug.augmentables.bbs.BoundingBoxesOnImages.from_point_soups()`.
* Changed `imgaug.augmentables.BoundingBoxesOnImage.from_xyxy_array()` to also
accept `(N, 2, 2)` arrays instead of only `(N, 4)`.
* Added context `imgaug.testutils.TemporaryDirectory`.
================================================
FILE: changelogs/0.4.0/20191016_pooling_affects_maps.md
================================================
# Pooling Augmenters now affects Maps #457
Pooling augmenters were previously implemented so that they did not pool
the arrays of maps (i.e. heatmap arrays, segmentation map arrays). Only
the image shape saved within `HeatmapsOnImage.shape` and
`SegmentationMapsOnImage.shape` were updated. That was done because the library
can handle map arrays that are larger than the corresponding images and hence
no pooling was necessary for the augmentation to work correctly. This was now
changed and pooling augmenters will also pool map arrays
(if `keep_size=False`). The motiviation for this change is that the old
behaviour was unintuitive and inconsistent with other augmenters (e.g. `Crop`).
================================================
FILE: changelogs/0.4.0/20191026_reworked_quantization.md
================================================
# Reworked Quantization #467
* Renamed `imgaug.augmenters.color.quantize_colors_uniform(image, n_colors)`
to `imgaug.augmenters.color.quantize_uniform(arr, nb_bins)`. The old name
is now deprecated.
* Renamed `imgaug.augmenters.color.quantize_colors_kmeans(image, n_colors)`
to `imgaug.augmenters.color.quantize_kmeans(arr, nb_clusters)`. The old name
is now deprecated.
* Improved performance of `quantize_uniform()` by roughly 10x (small images
around 64x64) to 100x (large images around 1024x1024). This also affects
`UniformColorQuantization`.
* Improved performance of `UniformColorQuantization` by using more in-place
functions.
* Added argument `to_bin_centers=True` to `quantize_uniform()`, controling
whether each bin `(a, b)` should be quantized to `a + (b-a)/2` or `a`.
* Added function `imgaug.augmenters.color.quantize_uniform_()`, the in-place
version of `quantize_uniform()`.
* Added function `imgaug.augmenters.color.quantize_uniform_to_n_bits()`.
* Added function `imgaug.augmenters.color.quantize_uniform_to_n_bits_()`.
* Added function `imgaug.augmenters.color.posterize()`, an alias of
`quantize_uniform_to_n_bits()` that produces the same outputs as
`PIL.ImageOps.posterize()`.
* Added augmenter `UniformColorQuantizationToNBits`.
* Added augmenter `Posterize` (alias of `UniformColorQuantizationToNBits`).
* Fixed `quantize_uniform()` producing wrong outputs for non-contiguous arrays.
================================================
FILE: changelogs/0.4.0/20191027_improve_invert.md
================================================
# Improve Invert #469
* Improved performance of `imgaug.augmenters.arithmetic.invert()` and
`imgaug.augmenters.arithmetic.Invert` for `uint8` images.
* Added function `imgaug.augmenters.arithmetic.invert_()`, an in-place version
of `imgaug.augmenters.arithmetic.invert()`.
* Added parameters `threshold` and `invert_above_threshold` to
`imgaug.augmenters.arithmetic.invert()`
* Added parameters `threshold` and `invert_above_threshold` to
`imgaug.augmenters.arithmetic.Invert`.
* Added function `imgaug.augmenters.arithmetic.solarize()`, a wrapper around
`solarize_()`.
* Added function `imgaug.augmenters.arithmetic.solarize_()`, a wrapper around
`invert_()`.
* Added augmenter `imgaug.augmenters.Solarize`, a wrapper around `Invert`.
================================================
FILE: changelogs/0.4.0/20191111_pickleable.md
================================================
# All Augmenters Pickle-able #493 #575
Ensured that all augmenters can be pickled.
* Added function `imgaug.testutils.runtest_pickleable_uint8_img()`.
* Fixed `imgaug.augmenters.blur.MotionBlur` not being pickle-able.
* Fixed `imgaug.augmenters.meta.AssertLambda` not being pickle-able.
* Fixed `imgaug.augmenters.meta.AssertShape` not being pickle-able.
* Fixed `imgaug.augmenters.color.MultiplyHueAndSaturation` not supporting
all standard RNG datatypes for `random_state`.
================================================
FILE: changelogs/0.4.0/20191113_iterable_augmentables.md
================================================
# Simplified Access to Coordinates and Items in Augmentables #495 #541
* Added module `imgaug.augmentables.base`.
* Added interface `imgaug.augmentables.base.IAugmentable`, implemented by
`HeatmapsOnImage`, `SegmentationMapsOnImage`, `KeypointsOnImage`,
`BoundingBoxesOnImage`, `PolygonsOnImage` and `LineStringsOnImage`.
* Added ability to iterate over coordinate-based `*OnImage` instances
(keypoints, bounding boxes, polygons, line strings), e.g.
`bbsoi = BoundingBoxesOnImage(bbs, shape=...); for bb in bbsoi: ...`.
would iterate now over `bbs`.
* Added implementations of `__len__` methods to coordinate-based `*OnImage`
instances, e.g.
`bbsoi = BoundingBoxesOnImage(bbs, shape=...); print(len(bbsoi))`
would now print the number of bounding boxes in `bbsoi`.
* Added ability to iterate over coordinates of `BoundingBox` (top-left,
bottom-right), `Polygon` and `LineString` via `for xy in obj: ...`.
* Added ability to access coordinates of `BoundingBox`, `Polygon` and
`LineString` using indices or slices, e.g. `line_string[1:]` to get an
array of all coordinates except the first one.
* Added property `Keypoint.xy`.
* Added property `Keypoint.xy_int`.
================================================
FILE: changelogs/0.4.0/20191610_crop_and_pad.md
================================================
# Changes to Crop and Pad augmenters #459
The following functions were moved. Their old names are now deprecated.
* Moved `imgaug.imgaug.pad` to `imgaug.augmenters.size.pad`
* Moved `imgaug.imgaug.pad_to_aspect_ratio` to
`imgaug.augmenters.size.pad_to_aspect_ratio`.
* Moved `imgaug.imgaug.pad_to_multiples_of` to
`imgaug.augmenters.size.pad_to_multiples_of`.
* Moved `imgaug.imgaug.compute_paddings_for_aspect_ratio` to
`imgaug.augmenters.size.compute_paddings_to_reach_aspect_ratio`.
* Moved `imgaug.imgaug.compute_paddings_to_reach_multiples_of`
to `imgaug.augmenters.size.compute_paddings_to_reach_multiples_of`.
The following augmenters were added:
* Added augmenter `CenterCropToFixedSize`.
* Added augmenter `CenterPadToFixedSize`.
* Added augmenter `CropToMultiplesOf`.
* Added augmenter `CenterCropToMultiplesOf`.
* Added augmenter `PadToMultiplesOf`.
* Added augmenter `CenterPadToMultiplesOf`.
* Added augmenter `CropToPowersOf`.
* Added augmenter `CenterCropToPowersOf`.
* Added augmenter `PadToPowersOf`.
* Added augmenter `CenterPadToPowersOf`.
* Added augmenter `CropToAspectRatio`.
* Added augmenter `CenterCropToAspectRatio`.
* Added augmenter `PadToAspectRatio`.
* Added augmenter `CenterPadToAspectRatio`.
* Added augmenter `PadToSquare`.
* Added augmenter `CenterPadToSquare`.
* Added augmenter `CropToSquare`.
* Added augmenter `CenterCropToSquare`.
All `Center` augmenters are wrappers around `` with parameter
`position="center"`.
Added functions:
* Added function
`imgaug.augmenters.size.compute_croppings_to_reach_aspect_ratio()`.
* Added function
`imgaug.augmenters.size.compute_croppings_to_reach_multiples_of()`.
* Added function
`imgaug.augmenters.size.compute_croppings_to_reach_powers_of()`.
* Added function
`imgaug.augmenters.size.compute_paddings_to_reach_powers_of()`.
Other changes:
* Extended augmenter `CropToFixedSize` to support `height` and/or `width`
parameters to be `None`, in which case the respective axis is not changed.
* Extended augmenter `PadToFixedSize` to support `height` and/or `width`
parameters to be `None`, in which case the respective axis is not changed.
* [rarely breaking] Changed `CropToFixedSize.get_parameters()` to also
return the `height` and `width` values.
* [rarely breaking] Changed `PadToFixedSize.get_parameters()` to also
return the `height` and `width` values.
* [rarely breaking] Changed the order of parameters returned by
`PadToFixedSize.get_parameters()` to match the order in
`PadToFixedSize.__init__()`
* Changed `PadToFixedSize` to prefer padding the right side over the left side
and the bottom side over the top side. E.g. if using a center pad and
`3` columns have to be padded, it will pad `1` on the left and `2` on the
right. Previously it was the other way round. This was changed to establish
more consistency with the various other pad and crop methods.
* Changed the projection of pad/crop values between images and non-images
to make the behaviour slightly more accurate in fringe cases.
* Improved behaviour of function
`imgaug.augmenters.size.compute_paddings_for_aspect_ratio()` for zero-sized
axes.
* Changed function `imgaug.augmenters.size.compute_paddings_for_aspect_ratio()`
to also support shape tuples instead of only ndarrays.
* Changed function
`imgaug.augmenters.size.compute_paddings_to_reach_multiples_of()`
to also support shape tuples instead of only ndarrays.
Fixes:
* Fixed a formatting error in an error message of
`compute_paddings_to_reach_multiples_of()`.
================================================
FILE: changelogs/0.4.0/20191610_perspective_transform.md
================================================
# Changes to PerspectiveTransform #452 #456
* [rarely breaking] PerspectiveTransform has now a `fit_output` parameter,
similar to `Affine`. This change may break code that relied on the order of
arguments to `__init__`.
* The sampling code of `PerspectiveTransform` was reworked and should now
be faster.
================================================
FILE: changelogs/0.4.0/20200107_improved_blending.md
================================================
# More Choices for Image Blending #462 #556
The available augmenters for alpha-blending of images were
significantly extended. There are now new blending
augmenters available to alpha-blend acoording to:
* Some randomly chosen colors. (`BlendAlphaSomeColors`)
* Linear gradients. (`BlendAlphaHorizontalLinearGradient`,
`BlendAlphaVerticalLinearGradient`)
* Regular grids and checkerboard patterns. (`BlendAlphaRegularGrid`,
`BlendAlphaCheckerboard`)
* Only at locations that overlap with specific segmentation class
IDs (or the inverse of that). (`BlendAlphaSegMapClassIds`)
* Only within bounding boxes with specific labels (or the inverse
of that). (`BlendAlphaBoundingBoxes`)
This allows to e.g. randomly remove some colors while leaving
other colors unchanged (`BlendAlphaSomeColors(Grayscale(1.0))`),
to change the color of some objects
(`BlendAlphaSegMapClassIds(AddToHue((-256, 256)))`), to add
cloud-patterns only to the top of images
(`BlendAlphaVerticalLinearGradient(Clouds())`) or to apply
augmenters in some coarse rectangular areas (e.g.
`BlendAlphaRegularGrid(Multiply(0.0))` to achieve a similar
effect to `CoarseDropout` or
`BlendAlphaRegularGrid(AveragePooling(8))` to pool in equally
coarse image sub-regions).
Other mask-based alpha blending techniques can be achieved by
subclassing `IBatchwiseMaskGenerator` and providing an
instance of such a class to `BlendAlphaMask`.
This patch also changes the naming of the blending augmenters
as follows:
* `Alpha` -> `BlendAlpha`
* `AlphaElementwise` -> `BlendAlphaElementwise`
* `SimplexNoiseAlpha` -> `BlendAlphaSimplexNoise`
* `FrequencyNoiseAlpha` -> `BlendAlphaFrequencyNoise`
The old names are now deprecated.
Furthermore, the parameters `first` and `second`, which were
used by all blending augmenters, have now the names `foreground`
and `background`.
List of changes:
* Added `imgaug.augmenters.blend.BlendAlphaMask`, which uses
a mask generator instance to generate per batch alpha masks and
then alpha-blends using these masks.
* Added `imgaug.augmenters.blend.BlendAlphaSomeColors`.
* Added `imgaug.augmenters.blend.BlendAlphaHorizontalLinearGradient`.
* Added `imgaug.augmenters.blend.BlendAlphaVerticalLinearGradient`.
* Added `imgaug.augmenters.blend.BlendAlphaRegularGrid`.
* Added `imgaug.augmenters.blend.BlendAlphaCheckerboard`.
* Added `imgaug.augmenters.blend.BlendAlphaSegMapClassIds`.
* Added `imgaug.augmenters.blend.BlendAlphaBoundingBoxes`.
* Added `imgaug.augmenters.blend.IBatchwiseMaskGenerator`,
an interface for classes generating masks on a batch-by-batch
basis.
* Added `imgaug.augmenters.blend.StochasticParameterMaskGen`,
a helper to generate masks from `StochasticParameter` instances.
* Added `imgaug.augmenters.blend.SomeColorsMaskGen`, a generator
that produces masks marking randomly chosen colors in images.
* Added `imgaug.augmenters.blend.HorizontalLinearGradientMaskGen`,
a linear gradient mask generator.
* Added `imgaug.augmenters.blend.VerticalLinearGradientMaskGen`,
a linear gradient mask generator.
* Added `imgaug.augmenters.blend.RegularGridMaskGen`,
a checkerboard-like mask generator where every grid cell has
a random alpha value.
* Added `imgaug.augmenters.blend.CheckerboardMaskGen`,
a checkerboard-like mask generator where every grid cell has
the opposite alpha value of its 4-neighbours.
* Added `imgaug.augmenters.blend.SegMapClassIdsMaskGen`, a
segmentation map-based mask generator.
* Added `imgaug.augmenters.blend.BoundingBoxesMaskGen`, a bounding
box-based mask generator.
* Added `imgaug.augmenters.blend.InvertMaskGen`, an mask generator
that inverts masks produces by child generators.
* Changed `imgaug.parameters.SimplexNoise` and
`imgaug.parameters.FrequencyNoise` to also accept `(H, W, C)`
sampling shapes, instead of only `(H, W)`.
* Refactored `AlphaElementwise` to be a wrapper around
`BlendAlphaMask`.
* Renamed `Alpha` to `BlendAlpha`.
`Alpha` is now deprecated.
* Renamed `AlphaElementwise` to `BlendAlphaElementwise`.
`AlphaElementwise` is now deprecated.
* Renamed `SimplexNoiseAlpha` to `BlendAlphaSimplexNoise`.
`SimplexNoiseAlpha` is now deprecated.
* Renamed `FrequencyNoiseAlpha` to `BlendAlphaFrequencyNoise`.
`FrequencyNoiseAlpha` is now deprecated.
* Renamed arguments `first` and `second` to `foreground` and `background`
in `BlendAlpha`, `BlendAlphaElementwise`, `BlendAlphaSimplexNoise` and
`BlendAlphaFrequencyNoise`.
* Changed `imgaug.parameters.handle_categorical_string_param()` to allow
parameter `valid_values` to be `None`.
* Fixed a wrong error message in
`imgaug.augmenters.color.change_colorspace_()`.
================================================
FILE: changelogs/0.4.0/20200126_python38.md
================================================
# Support for Python 3.8 #600
The library is now tested in python 3.8 and compatible with that
version. The latest version of `Shapely` is required for that,
which can right now be installed via `pip install --pre Shapely`.
(Skipping the `--pre` currently leads to an older shapely version,
which causes an error during installation in python 3.8.)
================================================
FILE: changelogs/0.4.0/added/20190927_unwrapped_bb_aug.md
================================================
# Unwrapped Bounding Box Augmentation #446 #599
* Added property `coords` to `BoundingBox`. The property returns an `(N,2)`
numpy array containing the coordinates of the top-left and bottom-right
bounding box corners.
* Added method `BoundingBox.coords_almost_equals(other)`.
* Added method `BoundingBox.almost_equals(other)`.
* Changed method `Polygon.almost_equals(other)` to no longer verify the
datatype. It is assumed now that the input is a Polygon.
* Added property `items` to `KeypointsOnImage`, `BoundingBoxesOnImage`,
`PolygonsOnImage`, `LineStringsOnImage`. The property returns the
keypoints/BBs/polygons/LineStrings contained by that instance.
* Added method `Polygon.coords_almost_equals(other)`. Alias for
`Polygon.exterior_almost_equals(other)`.
* Added property `Polygon.coords`. Alias for `Polygon.exterior`.
* Added property `Keypoint.coords`.
* Added method `Keypoint.coords_almost_equals(other)`.
* Added method `Keypoint.almost_equals(other)`.
* Added method `imgaug.testutils.assert_cbaois_equal()`.
* Added internal `_augment_bounding_boxes()` methods to various augmenters.
This allows to individually control how bounding boxes are supposed to
be augmented. Previously, the bounding box augmentation was a wrapper around
keypoint augmentation that did not allow such control.
* [breaking] Added parameter `parents` to `Augmenter.augment_bounding_boxes()`.
This breaks if `hooks` was used as a *positional* argument in connection with
that method.
* [rarely breaking] Added parameter `func_bounding_boxes` to `Lambda`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
* [rarely breaking] Added parameter `func_bounding_boxes` to `AssertLambda`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
* [rarely breaking] Added parameter `check_bounding_boxes` to `AssertShape`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
================================================
FILE: changelogs/0.4.0/added/20191002_unwrapped_ls_aug.md
================================================
# Unwrapped Line String Augmentation #450
* Added internal `_augment_line_strings()` methods to various augmenters.
This allows to individually control how line strings are supposed to
be augmented. Previously, the line string augmentation was a wrapper around
keypoint augmentation that did not allow such control.
* [rarely breaking] Added parameter `func_line_strings` to `Lambda`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
* [rarely breaking] Added parameter `func_line_strings` to `AssertLambda`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
* [rarely breaking] Added parameter `check_line_strings` to `AssertShape`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
================================================
FILE: changelogs/0.4.0/added/20191013_change_color_temperature.md
================================================
# New Augmenter ChangeColorTemperature #454
* Added augmenter `imgaug.augmenters.color.ChangeColorTemperature`.
* Added function `imgaug.augmenters.color.change_color_temperatures_()`.
* Added function `imgaug.augmenters.color.change_color_temperature_()`.
================================================
FILE: changelogs/0.4.0/added/20191014_brightness_augmenters.md
================================================
# New brightness augmenters #455
* Added augmenter `imgaug.augmenters.color.WithBrightnessChannels`.
* Added augmenter `imgaug.augmenters.color.MultiplyAndAddToBrightness`.
* Added augmenter `imgaug.augmenters.color.MultiplyBrightness`.
* Added augmenter `imgaug.augmenters.color.AddToBrightness`.
* Added method `imgaug.parameters.handle_categorical_string_param()`.
* Changed `change_colorspaces_()` to accept any iterable of `str` for
argument `to_colorspaces`, not just `list`.
================================================
FILE: changelogs/0.4.0/added/20191016_dropout2d.md
================================================
# New Dropout Augmenters #458
* Added a new augmenter `Dropout2d`, which drops channels in images with
a defineable probability `p`. Dropped channels will be filled with zeros.
By default, the augmenter keeps at least one channel in each image
unaltered (i.e. not dropped).
* Added new augmenter `TotalDropout`, which sets all components to zero
for `p` percent of all images. The augmenter should be used in connection
with e.g. blend augmenters.
================================================
FILE: changelogs/0.4.0/added/20191019_colorwise_grayscaling.md
================================================
# Added `RemoveSaturation` #462
* Added `RemoveSaturation`, a shortcut for `MultiplySaturation((0.0, 1.0))`
with outputs similar to `Grayscale((0.0, 1.0))`.
================================================
FILE: changelogs/0.4.0/added/20191020_cartoon.md
================================================
# New Augmenter `Cartoon` #463
* Added module `imgaug.augmenters.artistic`.
* Added function `imgaug.augmenters.artistic.stylize_cartoon(image)`.
* Added augmenter `imgaug.augmenters.artistic.Cartoon`.
================================================
FILE: changelogs/0.4.0/added/20191023_mean_shift_blur.md
================================================
# Added Augmenter `MeanShiftBlur` #466
* Added function `imgaug.augmenters.blur.blur_mean_shift_(image)`.
* Added augmenter `imgaug.augmenters.blur.MeanShiftBlur`.
================================================
FILE: changelogs/0.4.0/added/20191027_jigsaw.md
================================================
# Jigsaw Augmenter #476 #577
* Added function `imgaug.augmenters.geometric.apply_jigsaw()`.
* Added function `imgaug.augmenters.geometric.apply_jigsaw_to_coords()`.
* Added function `imgaug.augmenters.geometric.generate_jigsaw_destinations()`.
================================================
FILE: changelogs/0.4.0/added/20191101_deterministic_list.md
================================================
# Added DeterministicList #475
* Added `imgaug.parameters.DeterministicList`. Upon a request to generate
samples of shape `S`, this parameter will create a new array of shape `S`
and fill it by cycling over its list of values repeatedly.
================================================
FILE: changelogs/0.4.0/added/20191102_autocontrast.md
================================================
# Autocontrast #479
* Added `imgaug.augmenters.pillike.autocontrast()`, a function with identical
inputs and outputs to `PIL.ImageOps.autocontrast`.
* Added `imgaug.augmenters.pillike.Autocontrast`.
================================================
FILE: changelogs/0.4.0/added/20191103_affine_shear_y.md
================================================
# Affine Shear on the Y-Axis #482
* [rarely breaking] Extended `Affine` to also support shearing on the
y-axis (previously, only x-axis was possible). This feature can be used
via e.g. ``Affine(shear={"x": (-30, 30), "y": (-10, 10)})``. If instead
a single number is used (e.g. ``Affine(shear=15)``), shearing will be done
only on the x-axis. If a single ``tuple``, ``list`` or
``StochasticParameter`` is used, the generated samples will be used
identically for both the x-axis and y-axis (this is consistent with
translation and scaling). To get independent random samples per axis use
the dictionary form.
================================================
FILE: changelogs/0.4.0/added/20191103_equalize.md
================================================
# Equalize #480
* Added `imgaug.augmenters.pillike.equalize`, similar to
`PIL.ImageOps.equalize`.
* Added `imgaug.augmenters.pillike.equalize_`.
* Added `imgaug.augmenters.pillike.Equalize`.
================================================
FILE: changelogs/0.4.0/added/20191103_identity.md
================================================
# Added Identity #481
* [rarely breaking] Added `imgaug.augmenters.meta.Identity`, an alias of
`Noop`. `Identity` is now the recommended augmenter for identity
transformations. This change can break code that explicitly relied on
exactly `Noop` being used, e.g. via `isinstance` checks.
* Renamed parameter `noop_if_topmost` to `identity_if_topmost` in
method `imgaug.augmenters.meta.Augmenter.remove_augmenters()`. The old name
is now deprecated.
================================================
FILE: changelogs/0.4.0/added/20191105_affine_wrappers.md
================================================
# Added Wrappers around `Affine` #484
* Added `imgaug.augmenters.geometric.ScaleX`.
* Added `imgaug.augmenters.geometric.ScaleY`.
* Added `imgaug.augmenters.geometric.TranslateX`.
* Added `imgaug.augmenters.geometric.TranslateY`.
* Added `imgaug.augmenters.geometric.Rotate`.
* Added `imgaug.augmenters.geometric.ShearX`.
* Added `imgaug.augmenters.geometric.ShearY`.
================================================
FILE: changelogs/0.4.0/added/20191106_ooi_removal.md
================================================
# Removal of Coordinate-Based Augmentables Outside of the Image Plane #487
* Added `Keypoint.is_out_of_image()`.
* Added `BoundingBox.compute_out_of_image_area()`.
* Added `Polygon.compute_out_of_image_area()`.
* Added `Keypoint.compute_out_of_image_fraction()`
* Added `BoundingBox.compute_out_of_image_fraction()`.
* Added `Polygon.compute_out_of_image_fraction()`.
* Added `LineString.compute_out_of_image_fraction()`.
* Added `KeypointsOnImage.remove_out_of_image_fraction()`.
* Added `BoundingBoxesOnImage.remove_out_of_image_fraction()`.
* Added `PolygonsOnImage.remove_out_of_image_fraction()`.
* Added `LineStringsOnImage.remove_out_of_image_fraction()`.
* Added `KeypointsOnImage.clip_out_of_image()`.
* Added `imgaug.augmenters.meta.RemoveCBAsByOutOfImageFraction`.
Removes coordinate-based augmentables (e.g. BBs) that have at least a
specified fraction of their area outside of the image plane.
* Added `imgaug.augmenters.meta.ClipCBAsToImagePlanes`.
Clips off all parts from coordinate-based augmentables (e.g. BBs) that are
outside of the corresponding image.
* Changed `Polygon.area` to return `0.0` if the polygon contains less than
three points (previously: exception).
================================================
FILE: changelogs/0.4.0/added/20191110_bb_polygon_conversion.md
================================================
# Bounding Box to Polygon Conversion #489
* Added method `imgaug.augmentables.bbs.BoundingBox.to_polygon()`.
* Added method
`imgaug.augmentables.bbs.BoundingBoxesOnImage.to_polygons_on_image()`.
================================================
FILE: changelogs/0.4.0/added/20191110_polygon_subdivision.md
================================================
# Added Polygon Subdivision #489
* Added method `imgaug.augmentables.polys.Polygon.subdivide(N)`.
The method increases the polygon's corner point count by interpolating
`N` points on each edge with regular distance.
* Added method `imgaug.augmentables.polys.PolygonsOnImage.subdivide(N)`.
================================================
FILE: changelogs/0.4.0/added/20191110_withpolarwarping.md
================================================
# Added `WithPolarWarping` #489
* Added augmenter `imgaug.augmenters.geometric.WithPolarWarping`, an
augmenter that applies child augmenters in a polar representation of the
image.
================================================
FILE: changelogs/0.4.0/added/20191117_debug_images.md
================================================
# Generate Debug Images #502
* Added module `imgaug.augmenters.debug`.
* Added function `imgaug.augmenters.debug.draw_debug_image()`. The function
draws an image containing debugging information for a provided set of
images and non-image data (e.g. segmentation maps, bounding boxes)
corresponding to a single batch. The debug image visualizes these
informations (e.g. bounding boxes drawn on images) and offers relevant
information (e.g. actual value ranges of images, labels of bounding
boxes and their counts, etc.).
* Added augmenter `imgaug.augmenters.debug.SaveDebugImageEveryNBatches`.
Augmenter corresponding to `draw_debug_image()`. Saves an image at every
n-th batch into a provided folder.
================================================
FILE: changelogs/0.4.0/added/20191117_pad_multi_cval.md
================================================
# Multi-Channel cvals in `pad()` #502
Improved `imgaug.augmenters.size.pad()` to support multi-channel values
for the `cval` parameter (e.g. RGB colors).
================================================
FILE: changelogs/0.4.0/added/20191218_imagecorruptions.md
================================================
# Added Wrappers for `imagecorruptions` Package #530
Added wrappers around the functions from package
[bethgelab/imagecorruptions](https://github.com/bethgelab/imagecorruptions).
The functions in that package were used in some recent papers and are added
here for convenience.
The wrappers produce arrays containing values identical to the output
arrays from the corresponding `imagecorruptions` functions when called
via the `imagecorruptions.corrupt()` (verified via unittests).
The interfaces of the wrapper functions are identical to the
`imagecorruptions` functions, with the only difference of also supporting
`seed` parameters.
* Added module `imgaug.augmenters.imgcorruptlike`. The `like` signals that
the augmentation functions do not *have* to wrap `imagecorruptions`
internally. They merely have to produce the same outputs.
* Added the following functions to module `imgaug.augmenters.imgcorruptlike`:
* `apply_gaussian_noise()`
* `apply_shot_noise()`
* `apply_impulse_noise()`
* `apply_speckle_noise()`
* `apply_gaussian_blur()`
* `apply_glass_blur()` (improved performance over original function)
* `apply_defocus_blur()`
* `apply_motion_blur()`
* `apply_zoom_blur()`
* `apply_fog()`
* `apply_snow()`
* `apply_spatter()`
* `apply_contrast()`
* `apply_brightness()`
* `apply_saturate()`
* `apply_jpeg_compression()`
* `apply_pixelate()`
* `apply_elastic_transform()`
* Added function
`imgaug.augmenters.imgcorruptlike.get_corruption_names(subset)`.
Similar to `imagecorruptions.get_corruption_names(subset)`, but returns a
tuple
`(list of corruption method names, list of corruption method functions)`,
instead of only the names.
* Added the following augmenters to module `imgaug.augmenters.imgcorruptlike`:
* `GaussianNoise`
* `ShotNoise`
* `ImpulseNoise`
* `SpeckleNoise`
* `GaussianBlur`
* `GlassBlur`
* `DefocusBlur`
* `MotionBlur`
* `ZoomBlur`
* `Fog`
* `Frost`
* `Snow`
* `Spatter`
* `Contrast`
* `Brightness`
* `Saturate`
* `JpegCompression`
* `Pixelate`
* `ElasticTransform`
* Added context `imgaug.random.temporary_numpy_seed()`.
================================================
FILE: changelogs/0.4.0/added/20191220_cutout.md
================================================
# Cutout Augmenter #531 #570
* Added `imgaug.augmenters.arithmetic.apply_cutout_()`, which replaces
in-place a single rectangular area with a constant intensity value or a
constant color or gaussian noise.
See also the [paper](https://arxiv.org/abs/1708.04552) about Cutout.
* Added `imgaug.augmenters.arithmetic.apply_cutout()`. Same as
`apply_cutout_()`, but copies the input images before applying cutout.
* Added `imgaug.augmenters.arithmetic.Cutout`.
================================================
FILE: changelogs/0.4.0/added/20191221_inplace_cba_methods.md
================================================
# Added in-place Methods for Coordinate-based Augmentables #532
* Added `Keypoint.project_()`.
* Added `Keypoint.shift_()`.
* Added `KeypointsOnImage.on_()`.
* Added setter for `KeypontsOnImage.items`.
* Added setter for `BoundingBoxesOnImage.items`.
* Added setter for `LineStringsOnImage.items`.
* Added setter for `PolygonsOnImage.items`.
* Added `KeypointsOnImage.remove_out_of_image_fraction_()`.
* Added `KeypointsOnImage.clip_out_of_image_fraction_()`.
* Added `KeypointsOnImage.shift_()`.
* Added `BoundingBox.project_()`.
* Added `BoundingBox.extend_()`.
* Added `BoundingBox.clip_out_of_image_()`.
* Added `BoundingBox.shift_()`.
* Added `BoundingBoxesOnImage.on_()`.
* Added `BoundingBoxesOnImage.clip_out_of_image_()`.
* Added `BoundingBoxesOnImage.remove_out_of_image_()`.
* Added `BoundingBoxesOnImage.remove_out_of_image_fraction_()`.
* Added `BoundingBoxesOnImage.shift_()`.
* Added `imgaug.augmentables.utils.project_coords_()`.
* Added `LineString.project_()`.
* Added `LineString.shift_()`.
* Added `LineStringsOnImage.on_()`.
* Added `LineStringsOnImage.remove_out_of_image_()`.
* Added `LineStringsOnImage.remove_out_of_image_fraction_()`.
* Added `LineStringsOnImage.clip_out_of_image_()`.
* Added `LineStringsOnImage.shift_()`.
* Added `Polygon.project_()`.
* Added `Polygon.shift_()`.
* Added `Polygon.on_()`.
* Added `Polygon.subdivide_()`.
* Added `PolygonsOnImage.remove_out_of_image_()`.
* Added `PolygonsOnImage.remove_out_of_image_fraction_()`.
* Added `PolygonsOnImage.clip_out_of_image_()`.
* Added `PolygonsOnImage.shift_()`.
* Added `PolygonsOnImage.subdivide_()`.
* Switched `BoundingBoxesOnImage.copy()` to a custom copy operation (away
from module `copy` module).
* Added parameters `bounding_boxes` and `shape` to
BoundingBoxesOnImage.copy()`.
* Added parameters `bounding_boxes` and `shape` to
BoundingBoxesOnImage.deepcopy()`.
* Switched `KeypointsOnImage.copy()` to a custom copy operation (away
from module `copy` module).
* Switched `PolygonsOnImage.copy()` to a custom copy operation (away
from module `copy` module).
* Added parameters `polygons` and `shape` to
PolygonsOnImage.copy()`.
* Added parameters `polygons` and `shape` to
PolygonsOnImage.deepcopy()`.
* Switched augmenters to use in-place functions for keypoints,
bounding boxes, line strings and polygons.
================================================
FILE: changelogs/0.4.0/added/20191224_pil_module.md
================================================
# Added Module `imgaug.augmenters.pillike` #479 #480 #538
* Added module `imgaug.augmenters.pillike`, which contains augmenters and
functions corresponding to commonly used PIL functions. Their outputs
are guaranteed to be identical to the PIL outputs.
* Added the following functions to the module:
* `imgaug.augmenters.pillike.equalize`
* `imgaug.augmenters.pillike.equalize_`
* `imgaug.augmenters.pillike.autocontrast`
* `imgaug.augmenters.pillike.autocontrast_`
* `imgaug.augmenters.pillike.solarize`
* `imgaug.augmenters.pillike.solarize_`
* `imgaug.augmenters.pillike.posterize`
* `imgaug.augmenters.pillike.posterize_`
* `imgaug.augmenters.pillike.enhance_color`
* `imgaug.augmenters.pillike.enhance_contrast`
* `imgaug.augmenters.pillike.enhance_brightness`
* `imgaug.augmenters.pillike.enhance_sharpness`
* `imgaug.augmenters.pillike.filter_blur`
* `imgaug.augmenters.pillike.filter_smooth`
* `imgaug.augmenters.pillike.filter_smooth_more`
* `imgaug.augmenters.pillike.filter_edge_enhance`
* `imgaug.augmenters.pillike.filter_edge_enhance_more`
* `imgaug.augmenters.pillike.filter_find_edges`
* `imgaug.augmenters.pillike.filter_contour`
* `imgaug.augmenters.pillike.filter_emboss`
* `imgaug.augmenters.pillike.filter_sharpen`
* `imgaug.augmenters.pillike.filter_detail`
* `imgaug.augmenters.pillike.warp_affine`
* Added the following augmenters to the module:
* `imgaug.augmenters.pillike.Solarize`
* `imgaug.augmenters.pillike.Posterize`.
(Currently alias for `imgaug.augmenters.color.Posterize`.)
* `imgaug.augmenters.pillike.Equalize`
* `imgaug.augmenters.pillike.Autocontrast`
* `imgaug.augmenters.pillike.EnhanceColor`
* `imgaug.augmenters.pillike.EnhanceContrast`
* `imgaug.augmenters.pillike.EnhanceBrightness`
* `imgaug.augmenters.pillike.EnhanceSharpness`
* `imgaug.augmenters.pillike.FilterBlur`
* `imgaug.augmenters.pillike.FilterSmooth`
* `imgaug.augmenters.pillike.FilterSmoothMore`
* `imgaug.augmenters.pillike.FilterEdgeEnhance`
* `imgaug.augmenters.pillike.FilterEdgeEnhanceMore`
* `imgaug.augmenters.pillike.FilterFindEdges`
* `imgaug.augmenters.pillike.FilterContour`
* `imgaug.augmenters.pillike.FilterEmboss`
* `imgaug.augmenters.pillike.FilterSharpen`
* `imgaug.augmenters.pillike.FilterDetail`
* `imgaug.augmenters.pillike.Affine`
================================================
FILE: changelogs/0.4.0/added/20191230_standardized_lut.md
================================================
# Standardized LUT Methods #542
* Added `imgaug.imgaug.apply_lut()`, which applies a lookup table to an image.
* Added `imgaug.imgaug.apply_lut_()`. In-place version of `apply_lut()`.
* Refactored all augmenters to use these new LUT functions.
This likely fixed some so-far undiscovered bugs in augmenters using LUT
tables.
================================================
FILE: changelogs/0.4.0/added/20200101_bb_label_drawing.md
================================================
# Drawing Bounding Box Labels #545
When drawing bounding boxes on images via `BoundingBox.draw_on_image()`
or `BoundingBoxesOnImage.draw_on_image()`, a box containing the label will now
be drawn over each bounding box's rectangle. If the bounding box's label is
set to `None`, the label box will not be drawn. For more detailed control,
use `BoundingBox.draw_label_on_image()`.
* Added method `imgaug.augmentables.BoundingBox.draw_label_on_image()`.
* Added method `imgaug.augmentables.BoundingBox.draw_box_on_image()`.
* Changed method `imgaug.augmentables.BoundingBox.draw_on_image()`
to automatically draw a bounding box's label.
================================================
FILE: changelogs/0.4.0/added/20200102_cbasoi_getitem.md
================================================
# Index-based Access to Coordinate-based `*OnImage` Instances #547
Enabled index-based access to coordinate-based `*OnImage` instances, i.e. to
`KeypointsOnImage`, `BoundingBoxesOnImage`, `LineStringsOnImage` and
`PolygonsOnImage`. This allows to do things like
`bbsoi = BoundingBoxesOnImage(...); bbs = bbsoi[0:2];`.
* Added `imgaug.augmentables.kps.KeypointsOnImage.__getitem__()`.
* Added `imgaug.augmentables.bbs.BoundingBoxesOnImage.__getitem__()`.
* Added `imgaug.augmentables.lines.LineStringsOnImage.__getitem__()`.
* Added `imgaug.augmentables.polys.PolygonsOnImage.__getitem__()`.
================================================
FILE: changelogs/0.4.0/added/20200105_discretize_round.md
================================================
# Added `round` Parameter to `Discretize` #553
Added the parameter `round` to `imgaug.parameters.Discretize`. The parameter
defaults to `True`, i.e. the default behaviour of `Discretize` did not change.
================================================
FILE: changelogs/0.4.0/added/20200106_rain.md
================================================
# Added Rain Augmenters #551
Added augmenter(s) to create fake rain effects. They currently seem to work
best at around medium-sized images (~224px).
* Added `imgaug.augmenters.weather.Rain`.
* Added `imgaug.augmenters.weather.RainLayer`.
================================================
FILE: changelogs/0.4.0/added/20200106_randaugment.md
================================================
# Add RandAugment #553
Added a RandAugment augmenter, similar to the one described in the paper
"RandAugment: Practical automated data augmentation with a reduced
search space".
* Added module `imgaug.augmenters.collections`
* Added augmenter `imgaug.augmenters.collections.RandAugment`.
================================================
FILE: changelogs/0.4.0/added/20200125_image_warnings.md
================================================
# Improved Warnings on Probably-Wrong Image Inputs #594
Improved the errors and warnings on image augmentation calls.
`augment_image()` will now produce a more self-explanatory error
message when calling it as in `augment_image(list of images)`.
Calls of single-image augmentation functions (e.g.
`augment(image=...)`) with inputs that look like multiple images
will now produce warnings. This is the case for `(H, W, C)`
inputs when `C>=32` (as that indicates that `(N, H, W)` was
actually provided).
Calls of multi-image augmentation functions (e.g.
`augment(images=...)`) with inputs that look like single images
will now produce warnings. This is the case for `(N, H, W)`
inputs when `W=1` or `W=3` (as that indicates that `(H, W, C)`
was actually provided.)
* Added an assert in `augment_image()` to verify that inputs are
arrays.
* Added warnings for probably-wrong image inputs in
`augment_image()`, `augment_images()`, `augment()` (and its
alias `__call__()`).
* Added module `imgaug.augmenters.base`.
* Added warning
`imgaug.augmenters.base.SuspiciousMultiImageShapeWarning`.
* Added warning
`imgaug.augmenters.base.SuspiciousSingleImageShapeWarning`.
* Added `imgaug.testutils.assertWarns`, similar to `unittest`'s
`assertWarns`, but available in python <3.2.
================================================
FILE: changelogs/0.4.0/changed/20190929_rngs_polygon_recoverer.md
================================================
# Improved RNG Handling during Polygon Augmentation #447
* Changed `Augmenter.augment_polygons()` to copy the augmenter's RNG
before starting concave polygon recovery. This is done for cleanliness and
should not have any effects for users.
* Removed RNG copies in `_ConcavePolygonRecoverer` to improve performance.
================================================
FILE: changelogs/0.4.0/changed/20191110_affine_translation_precision.md
================================================
# Affine Translation Precision #489
* Removed a rounding operation in `Affine` translation that would unnecessarily
round floats to integers. This should make coordinate augmentation overall
more accurate.
================================================
FILE: changelogs/0.4.0/changed/20191128_affine_translate.md
================================================
# `Affine.get_parameters()` and `translate_px`/`translate_percent` #508
* Changed `Affine.get_parameters()` to always return a tuple `(x, y, mode)`
for translation, where `mode` is either `px` or `percent`,
and `x` and `y` are stochastic parameters. `y` may be `None` if the same
parameter (and hence samples) are used for both axes.
================================================
FILE: changelogs/0.4.0/changed/20191230_dont_import_msgs.md
================================================
# Removed Outdated "Don't Import from this Module" Messages #539
The docstring of each module in ``imgaug.augmenters`` previously included a
suggestion to not directly import from that module, but instead use
``imgaug.augmenters.``. That was due to the categorization
still being unstable. As the categorization has now been fairly stable
for a long time, the suggestion was removed from all modules. Calling
``imgaug.augmenters.`` instead of
``imgaug.augmenters..`` is however still the
preferred way.
================================================
FILE: changelogs/0.4.0/changed/20200103_standardized_shift_interfaces.md
================================================
# Standardized `shift()` Interfaces of Coordinate-Based Augmentables #548
The interfaces for shift operations of all coordinate-based
augmentables (Keypoints, BoundingBoxes, LineStrings, Polygons)
were standardized. All of these augmentables have now the same
interface for shift operations. Previously, Keypoints used
a different interface (using `x` and `y` arguments) than the
other augmentables (using `top`, `right`, `bottom`, `left`
arguments). All augmentables use now the interface of Keypoints
as that is simpler and less ambiguous. Old arguments are still
accepted, but will produce deprecation warnings. Change the
arguments to `x` and `y` following `x=left-right` and
`y=top-bottom`.
**[breaking]** This breaks if one relied on calling `shift()` functions of
`BoundingBox`, `LineString`, `Polygon`, `BoundingBoxesOnImage`,
`LineStringsOnImage` or `PolygonsOnImage` without named arguments.
E.g. `bb = BoundingBox(...); bb_shifted = bb.shift(1, 2, 3, 4);`
will produce unexpected outputs now (equivalent to
`shift(x=1, y=2, top=3, right=4, bottom=0, left=0)`),
while `bb_shifted = bb.shift(top=1, right=2, bottom=3, left=4)` will still
work as expected.
* Added arguments `x`, `y` to `BoundingBox.shift()`, `LineString.shift()`
and `Polygon.shift()`.
* Added arguments `x`, `y` to `BoundingBoxesOnImage.shift()`,
`LineStringsOnImage.shift()` and `PolygonsOnImage.shift()`.
* Marked arguments `top`, `right`, `bottom`, `left` in
`BoundingBox.shift()`, `LineString.shift()` and `Polygon.shift()`
as deprecated. This also affects the corresponding `*OnImage`
classes.
* Added function `testutils.wrap_shift_deprecation()`.
================================================
FILE: changelogs/0.4.0/changed/20200112_simplified_augmenter_args.md
================================================
# Simplified Standard Parameters of Augmenters #567 #595
Changed the standard parameters shared by all augmenters to a
reduced and more self-explanatory set. Previously, all augmenters
shared the parameters `name`, `random_state` and `deterministic`.
The new parameters are `seed` and `name`.
`deterministic` was removed as it was hardly ever used and because
it caused frequently confusion with regards to its meaning. The
parameter is still accepted but will now produce a deprecation
warning. Use `.to_deterministic()` instead.
(Reminder: `to_deterministic()` is necessary if you want to get
the same samples in consecutive augmentation calls. It is *not*
necessary if you want your generated samples to be dependent on
an initial seed or random state as that is *always* the case
anyways. You only have to manually set the seed, either
augmenter-specific via the `seed` parameter or global via
`imgaug.random.seed()` (affects only augmenters without their
own seed).)
`random_state` was renamed to `seed` as providing a seed value
is the more common use case compared to providing a random state.
Many users also seemed to be unaware that `random_state` accepted
seed values. The new name should make this more clear.
The old parameter `random_state` is still accepted, but will
likely be deprecated in the future.
**[breaking]** This patch breaks if one relied on the order of
`name`, `random_state` and `deterministic`. The new order is now
`seed=..., name=..., random_state=..., deterministic=...` (with the
latter two parameters being outdated or deprecated)
as opposed to previously
`name=..., deterministic=..., random_state=...`.
================================================
FILE: changelogs/0.4.0/changed/20200115_changed_defaults.md
================================================
# Improved Default Values of Augmenters #582
**[breaking]** Most augmenters had previously default values that
made them equivalent to identity functions. Users had to explicitly
change the defaults to proper values in order to "activate"
augmentations. To simplify the usage of the library, the default
values of most augmenters were changed to medium-strength
augmentations. E.g.
`Sequential([Affine(), UniformVoronoi(), CoarseDropout()])`
should now produce decent augmentations.
A few augmenters were set to always-on, maximum-strength
augmentations. This is the case for:
* `Grayscale` (always fully grayscales images, use
`Grayscale((0.0, 1.0))` for random strengths)
* `RemoveSaturation` (same as `Grayscale`)
* `Fliplr` (always flips images, use `Fliplr(0.5)` for 50%
probability)
* `Flipud` (same as `Fliplr`)
* `TotalDropout` (always drops everything, use
`TotalDropout(0.1)` to drop everything for 10% of all images)
* `Invert` (always inverts images, use `Invert(0.1)` to invert
10% of all images)
* `Rot90` (always rotates exactly once clockwise by 90 degrees,
use `Rot90((0, 3))` for any rotation)
These settings seemed to better match user-expectations.
Such maximum-strength settings however were not chosen for all
augmenters where one might expect them. The defaults are set to
varying strengths for, e.g. `Superpixels` (replaces only some
superpixels with cellwise average colors), `UniformVoronoi` (also
only replaces some cells), `Sharpen` (alpha-blends with variable
strength, the same is the case for `Emboss`, `EdgeDetect` and
`DirectedEdgeDetect`) and `CLAHE` (variable clip limits).
*Note*: Some of the new default values will cause issues with
non-`uint8` inputs.
*Note*: The defaults for `per_channel` and `keep_size` were not
adjusted. It is currently still the default behaviour of all
augmenters to affect all channels in the same way and to resize
their outputs back to the input sizes.
The exact changes to default values are listed below.
**imgaug.arithmetic**
* `Add`
* `value`: `0` -> `(-20, 20)`
* `AddElementwise`
* `value`: `0` -> `(-20, 20)`
* `AdditiveGaussianNoise`
* `scale`: `0` -> `(0, 15)`
* `AdditiveLaplaceNoise`
* `scale`: `0` -> `(0, 15)`
* `AdditivePoissonNoise`
* `scale`: `0` -> `(0, 15)`
* `Multiply`
* `mul`: `1.0` -> `(0.8, 1.2)`
* `MultiplyElementwise`:
* `mul`: `1.0` -> `(0.8, 1.2)`
* `Dropout`:
* `p`: `0.0` -> `(0.0, 0.05)`
* `CoarseDropout`:
* `p`: `0.0` -> `(0.02, 0.1)`
* `size_px`: `None` -> `(3, 8)`
* `min_size`: `4` -> `3`
* Default for `size_px` is only used if neither `size_percent`
nor `size_px` is provided by the user.
* `CoarseSaltAndPepper`:
* `p`: `0.0` -> `(0.02, 0.1)`
* `size_px`: `None` -> `(3, 8)`
* `min_size`: `4` -> `3`
* Default for `size_px` is only used if neither `size_percent`
nor `size_px` is provided by the user.
* `CoarseSalt`:
* `p`: `0.0` -> `(0.02, 0.1)`
* `size_px`: `None` -> `(3, 8)`
* `min_size`: `4` -> `3`
* Default for `size_px` is only used if neither `size_percent`
nor `size_px` is provided by the user.
* `CoarsePepper`:
* `p`: `0.0` -> `(0.02, 0.1)`
* `size_px`: `None` -> `(3, 8)`
* `min_size`: `4` -> `3`
* Default for `size_px` is only used if neither `size_percent`
nor `size_px` is provided by the user.
* `SaltAndPepper`:
* `p`: `0.0` -> `(0.0, 0.03)`
* `Salt`:
* `p`: `0.0` -> `(0.0, 0.03)`
* `Pepper`:
* `p`: `0.0` -> `(0.0, 0.05)`
* `ImpulseNoise`:
* `p`: `0.0` -> `(0.0, 0.03)`
* `Invert`:
* `p`: `0` -> `1`
* `JpegCompression`:
* `compression`: `50` -> `(0, 100)`
**imgaug.blend**
* `BlendAlpha`
* `factor`: `0` -> `(0.0, 1.0)`
* `BlendAlphaElementwise`
* `factor`: `0` -> `(0.0, 1.0)`
**imgaug.blur**
* `GaussianBlur`:
* `sigma`: `0` -> `(0.0, 3.0)`
* `AverageBlur`:
* `k`: `1` -> `(1, 7)`
* `MedianBlur`:
* `k`: `1` -> `(1, 7)`
* `BilateralBlur`:
* `d`: `1` -> `(1, 9)`
* `MotionBlur`:
* `k`: `5` -> `(3, 7)`
**imgaug.color**
* `MultiplyHueAndSaturation`:
* `mul_hue`: `None` -> `(0.5, 1.5)`
* `mul_saturation`: `None` -> `(0.0, 1.7)`
* These defaults are only used if the user provided neither
`mul` nor `mul_hue` nor `mul_saturation`.
* `MultiplyHue`:
* `mul`: `(-1.0, 1.0)` -> `(-3.0, 3.0)`
* `AddToHueAndSaturation`:
* `value_hue`: `None` -> `(-40, 40)`
* `value_saturation`: `None` -> `(-40, 40)`
* These defaults are only used if the user provided neither
`value` nor `value_hue` nor `value_saturation`.
* `Grayscale`:
* `alpha`: `0` -> `1`
**imgaug.contrast**
* `GammaContrast`:
* `gamma`: `1` -> `(0.7, 1.7)`
* `SigmoidContrast`:
* `gain`: `10` -> `(5, 6)`
* `cutoff`: `0.5` -> `(0.3, 0.6)`
* `LogContrast`:
* `gain`: `1` -> `(0.4, 1.6)`
* `LinearContrast`:
* `alpha`: `1` -> `(0.6, 1.4)`
* `AllChannelsCLAHE`:
* `clip_limit`: `40` -> `(0.1, 8)`
* `tile_grid_size_px`: `8` -> `(3, 12)`
* `CLAHE`:
* `clip_limit`: `40` -> `(0.1, 8)`
* `tile_grid_size_px`: `8` -> `(3, 12)`
**convolutional**
* `Sharpen`:
* `alpha`: `0` -> `(0.0, 0.2)`
* `lightness`: `1` -> `(0.8, 1.2)`
* `Emboss`:
* `alpha`: `0` -> `(0.0, 1.0)`
* `strength`: `1` -> `(0.25, 1.0)`
* `EdgeDetect`:
* `alpha`: `0` -> `(0.0, 0.75)`
* `DirectedEdgeDetect`:
* `alpha`: `0` -> `(0.0, 0.75)`
**imgaug.flip**
* `Fliplr`:
* `p`: `0` -> `1`
* `Flipud`:
* `p`: `0` -> `1`
**imgaug.geometric**
* `Affine`:
* `scale`: `1` -> `{"x": (0.9, 1.1), "y": (0.9, 1.1)}`
* `translate_percent`: None -> `{"x": (-0.1, 0.1), "y": (-0.1, 0.1)}`
* `rotate`: `0` -> `(-15, 15)`
* `shear`: `0` -> `shear={"x": (-10, 10), "y": (-10, 10)}`
* These defaults are only used if no affine transformation
parameter was set by the user. Otherwise the not-set
parameters default again towards the identity function.
* `PiecewiseAffine`:
* `scale`: `0` -> `(0.0, 0.04)`
* `nb_rows`: `4` -> `(2, 4)`
* `nb_cols`: `4` -> `(2, 4)`
* `PerspectiveTransform`:
* `scale`: `0` -> `(0.0, 0.06)`
* `ElasticTransformation`:
* `alpha`: `0` -> `(0.0, 40.0)`
* `sigma`: `0` -> `(4.0, 8.0)`
* `Rot90`:
* `k`: `(no default)` -> `k=1`
**imgaug.pooling**
* `AveragePooling`:
* `k`: `(no default)` -> `(1, 5)`
* `MaxPooling`:
* `k`: `(no default)` -> `(1, 5)`
* `MinPooling`:
* `k`: `(no default)` -> `(1, 5)`
* `MedianPooling`:
* `k`: `(no default)` -> `(1, 5)`
**imgaug.segmentation**
* `Superpixels`:
* `p_replace`: `0.0` -> `(0.5, 1.0)`
* `n_segments`: `100` -> `(50, 120)`
* `UniformVoronoi`:
* `n_points`: `(no default)` -> `(50, 500)`
* `p_replace`: `1.0` -> `(0.5, 1.0)`.
* `RegularGridVoronoi`:
* `n_rows`: `(no default)` -> `(10, 30)`
* `n_cols`: `(no default)` -> `(10, 30)`
* `p_drop_points`: `0.4` -> `(0.0, 0.5)`
* `p_replace`: `1.0` -> `(0.5, 1.0)`
* `RelativeRegularGridVoronoi`: Changed defaults from
* `n_rows_frac`: `(no default)` -> `(0.05, 0.15)`
* `n_cols_frac`: `(no default)` -> `(0.05, 0.15)`
* `p_drop_points`: `0.4` -> `(0.0, 0.5)`
* `p_replace`: `1.0` -> `(0.5, 1.0)`
**imgaug.size**
* `CropAndPad`:
* `percent`: `None` -> `(-0.1, 0.1)`
* This default is only used if the user has provided
neither `px` nor `percent`.
* `Pad`:
* `percent`: `None` -> `(0.0, 0.1)`
* This default is only used if the user has provided
neither `px` nor `percent`.
* `Crop`:
* `percent`: `None` -> `(0.0, 0.1)`
* This default is only used if the user has provided
neither `px` nor `percent`.
================================================
FILE: changelogs/0.4.0/changed/20200125_any_opencv_accepted.md
================================================
# `setup.py` Now Accepts Any `opencv-*` Installation #586
`setup.py` was changed so that it now accepts `opencv-python`,
`opencv-python-headless`, `opencv-contrib-python` and
`opencv-contrib-python-headless` as valid OpenCV installations.
Previously, only `opencv-python-headless` was accepted, which
could easily cause conflicts when another one of the mentioned
libraries was already installed.
If none of the mentioned libraries is installed, `setup.py`
will default to adding `opencv-python` as a requirement.
================================================
FILE: changelogs/0.4.0/deprecated/20190926_rename_inplace.md
================================================
* Deprecated method `Augmenter.reseed()`.
Use `Augmenter.seed_()` instead. #444
* Deprecated method `Augmenter.remove_augmenters_inplace()`.
Use `Augmenter.remove_augmenters_()` instead. #444
================================================
FILE: changelogs/0.4.0/deprecated/20191230_deprecate_affinecv2.md
================================================
# Deprecated AffineCv2 in Favor of Affine #540
The augmenter `imgaug.augmenters.geometric.AffineCv2` was not properly
maintained for quite a while and its functionality is already covered
by `imgaug.augmenters.geometric.Affine` using parameter
`backend='cv2'`. Hence, it was now deprecated. Use `Affine` instead.
================================================
FILE: changelogs/0.4.0/fixed/20190926_fixed_resize_dtype.md
================================================
* Fixed `Resize` always returning an `uint8` array during image augmentation
if the input was a single numpy array and all augmented images had the
same shape. #442 #443
================================================
FILE: changelogs/0.4.0/fixed/20190928_fixed_affine_coords_aug.md
================================================
* Fixed `Affine` coordinate-based augmentation applying wrong offset
when shifting images to/from top-left corner. This would lead to an error
of around 0.5 to 1.0 pixels. #446
================================================
FILE: changelogs/0.4.0/fixed/20190928_fixed_pwa_empty_kps_unaligned.md
================================================
* Fixed keypoint augmentation in `PiecewiseAffine` potentially being
unaligned if a `KeypointsOnImage` instance contained no keypoints. #446
================================================
FILE: changelogs/0.4.0/fixed/20190928_fixed_type_val.md
================================================
* Fixed `imgaug.validation.convert_iterable_to_string_of_types()` crashing due
to not converting types to strings before joining them. #446
================================================
FILE: changelogs/0.4.0/fixed/20190929_fixed_assert_is_iterable_of.md
================================================
* Fixed `imgaug.validation.assert_is_iterable_of()` producing a not
well-designed error if the input was not an iterable. #446
================================================
FILE: changelogs/0.4.0/fixed/20191003_fixed_image_normalization.md
================================================
* Fixed image normalization crashing when an input ndarray of multiple images
was changed during augmentation to a list of multiple images with different
shapes *and* the original input ndarray represented a single image or
a collection of 2D `(H,W)` images. This problem affected `augment()`,
`augment_batch()` and `augment_batches()`.
================================================
FILE: changelogs/0.4.0/fixed/20191003_fixed_typo.md
================================================
* Fixed typo in image normalization. #451
================================================
FILE: changelogs/0.4.0/fixed/20191006_fixed_withchannels_alignment.md
================================================
* Fixed a problem in `WithChannels` that could lead random sampling in child
augmenters being unaligned between images and corresponding non-image
data. #451
================================================
FILE: changelogs/0.4.0/fixed/20191106_fixed_random_state_funcs_missing.md
================================================
# Fixed Missing RandomState Methods #486
* Added aliases to `imgaug.random.RNG` for some outdated numpy random number
sampling methods that existed in `numpy.random.RandomState` but not in
numpy's new RNG system (1.17+). These old methods are not used in `imgaug`,
but some custom augmenters and `Lambda` calls may require them when
interacting with the provided `random_state` instances.
================================================
FILE: changelogs/0.4.0/fixed/20191110_fixed_affine_map_aug.md
================================================
# Fixed Affine Translation of Map-Data #489
* Fixed `Affine` producing unaligned augmentations between images and
segmentation maps or heatmaps when using `translate_px` and the segmentation
map or heatmap had a different height/width than corresponding image.
================================================
FILE: changelogs/0.4.0/fixed/20191111_fixed_snowflakeslayer_crash.md
================================================
# Fixed `SnowflakesLayer` crash #471
* Fixed a crash in `SnowflakesLayer` that could occur when using values
close to `1.0` for `flake_size`.
================================================
FILE: changelogs/0.4.0/fixed/20191111_multiplyhueandsaturation_rng.md
================================================
# Fixed `MultiplyHueAndSaturation` RNG #493
* Fixed `MultiplyHueAndSaturation` crashing if the RNG provided via
`random_state` was not `None` or `imgaug.random.RNG`.
================================================
FILE: changelogs/0.4.0/fixed/20191124_fixed_cloud_layer_float.md
================================================
* Fixed `CloudLayer.draw_on_image()` producing tuples instead of arrays
as output for `float` input images. #540
================================================
FILE: changelogs/0.4.0/fixed/20191128_fixed_affine_translate.md
================================================
# `Affine` Translate Type Falsely dependent on float/int Samples #508
* Fixed `Affine` parameter `translate_px` behaving like `translate_percent`
if a continuous stochastic parameter was provided.
Analogously `translate_percent` would behave like `translate_px` if
a discrete stochastic parameter was provided.
================================================
FILE: changelogs/0.4.0/fixed/20191128_fixed_hanging_nixos.md
================================================
# Fixed Hanging Code in NixOS #414 #510
* Fixed code hanging indefinitely when using multicore augmentation
on NixOS.
================================================
FILE: changelogs/0.4.0/fixed/20191217_collections_abc.md
================================================
# Fixed Abstract Base Classes Import #527
* Fixed a deprecation warning and potential crash in python 3.8
related to the use of `collections` instead of `collections.abc`.
================================================
FILE: changelogs/0.4.0/fixed/20191218_fixed_fromfunction_deprecated.md
================================================
# Fixed `scipy.fromfunction` Deprecated #529
* Fixed deprecated `scipy.fromfunction()` being called.
================================================
FILE: changelogs/0.4.0/fixed/20191222_fixed_numpy_1_18.md
================================================
# Fixed crashes in numpy 1.18 #534
* Fixed `imgaug.random.normalize_generator()` crashing in numpy 1.18.
The function relied on `numpy.random.bit_generator.BitGenerator`, which
was moved in numpy 1.18 to `numpy.random.BitGenerator` without a
deprecation period for the old name.
================================================
FILE: changelogs/0.4.0/fixed/20191223_fixed_opencv_multicore_aug_hanging.md
================================================
# Fixed OpenCV Hanging in Multicore Augmentation #535
* Fixed an issue that could lead to endlessly hanging programs on some OS
when using multicore augmentation (e.g. via pool) and augmenters using
OpenCV.
================================================
FILE: changelogs/0.4.0/fixed/20200110_fixed_seed.md
================================================
# Fixed `random.seed` not always seeding in-place #557
Fixed `imgaug.random.seed()` not seeding the global `RNG` in-place
in numpy 1.17+. The (unfixed) function instead created a new
global `RNG` with the given seed. This set the seed of augmenters
created *after* the `seed()` call, but not of augmenters created
*before* the `seed()` call as they would continue to use the old
global RNG.
================================================
FILE: changelogs/0.4.0/fixed/20200111_fixed_elastic_transformation_cval.md
================================================
# Fixed `cval` in `ElasticTransformation` #561 #562
* Fixed `cval` in `ElasticTransformation` resulting new pixels in RGB images
being filled with `(cval, 0, 0)` instead of `(cval, cval, cval)`.
================================================
FILE: changelogs/0.4.0/fixed/20200113_fixed_weather_randomness.md
================================================
* Fixed some augmenters in module `weather` not transferring seed values
or random states that were provided upon creation to child augmenters. #568
================================================
FILE: changelogs/0.4.0/fixed/20200118_perspt_inaccuracy.md
================================================
* Fixed an inaccuracy in `PerspectiveTransform` that could lead to slightly
misaligned transformations between images and coordinate-based
augmentables (e.g. bounding boxes). The problem was more significant the
smaller the images and larger the `scale` values were. It was also
worsened by using `fit_output`. #585
================================================
FILE: changelogs/0.4.0/fixed/20200122_fix_keepsizebyresize.md
================================================
* Fixed `KeepSizeByResize` potentially crashing if a single numpy array
was provided as the input for an iterable of images (as opposed to
a list of numpy arrays). #590
================================================
FILE: changelogs/0.4.0/fixed/20200126_shapely_17a2.md
================================================
* Fixed an issue in Shapely 1.7a2 (python 3.8) that could lead to
a crash in `LineString.find_intersections_with()` if there was
no intersection. #600
================================================
FILE: changelogs/0.4.0/refactored/20191124_pylint.md
================================================
# Refactored according to pylint requirements
* Refactored all core library files to fulfill (most) pylint requirements.
* [rarely breaking] Renamed
`imgaug.augmenters.size.KeepSizeByResize.get_shapes()` to `_get_shapes()`.
* Added a project-specific pylint configuration.
================================================
FILE: changelogs/0.4.0/refactored/20200111_opencv_normalization.md
================================================
# Unified OpenCV Input Normalization #565
* Refactored various augmenters to use the same normalization for OpenCV
inputs. This probably fixes some previously undiscovered bugs.
================================================
FILE: changelogs/0.4.0/renamed/20190926_rename_inplace.md
================================================
* Renamed `Augmenter.reseed()` to `Augmenter.seed_()`. #444
* Renamed `Augmenter.remove_augmenters_inplace()` to
`Augmenter.remove_augmenters_()`. #444
================================================
FILE: changelogs/master/20200206_data_module.md
================================================
# Add New `data` Module #606
This patch moves the example data functions from `imgaug.imgaug` to
the new module `imgaug.data`.
Add Modules:
* `imgaug.data`
Add Functions:
* `imgaug.data.quokka()`
* `imgaug.data.quokka_square()`
* `imgaug.data.quokka_heatmap()`
* `imgaug.data.quokka_segmentation_map()`
* `imgaug.data.quokka_keypoints()`
* `imgaug.data.quokka_bounding_boxes()`
* `imgaug.data.quokka_polygons()`
Deprecated Functions:
* `imgaug.imgaug.quokka()`.
Use `imgaug.data.quokka()` instead.
* `imgaug.imgaug.quokka_square()`.
Use `imgaug.data.quokka_square()` instead.
* `imgaug.imgaug.quokka_heatmap()`.
Use `imgaug.data.quokka_heatmap()` instead.
* `imgaug.imgaug.quokka_segmentation_map()`.
Use `imgaug.data.quokka_segmentation_map()` instead.
* `imgaug.imgaug.quokka_keypoints()`.
Use `imgaug.data.quokka_keypoints()` instead.
* `imgaug.imgaug.quokka_bounding_boxes()`.
Use `imgaug.data.quokka_bounding_boxes()` instead.
* `imgaug.imgaug.quokka_polygons()`.
Use `imgaug.data.quokka_polygons()` instead.
Removed Constants:
* [rarely breaking] `imgaug.imgaug.FILE_DIR`
* [rarely breaking] `imgaug.imgaug.QUOKKA_FP`
* [rarely breaking] `imgaug.imgaug.QUOKKA_ANNOTATIONS_FP`
* [rarely breaking] `imgaug.imgaug.QUOKKA_DEPTH_MAP_HALFRES_FP`
================================================
FILE: changelogs/master/changed/20200222_shape_handling.md
================================================
# Stricter Shape Handling in Augmentables #623
Various methods of augmentables have so far accepted tuples
of integers or numpy arrays for `shape` parameters. This was
the case for e.g. `BoundingBoxesOnImage.__init__(bbs, shape)`
or `Polygon.clip_out_of_image(image)`. This tolerant handling
of shapes conveys some risk that an input is actually a
numpy representation of a shape, i.e. the equivalent of
`numpy.array(shape_tuple)`.
To decrease the risk of such an input leading to bugs, arrays
are no longer recommended inputs for `shape` in
`KeypointsOnImage.__init__`, `BoundingBoxesOnImage.__init__`,
`LineStringsOnImage.__init__`, and `PolygonsOnImage.__init__`.
Their usage in these methods will now raise a deprecation warning.
In all other methods of augmentables that currently accept
image-like numpy arrays and shape tuples for parameters,
only arrays that are 2-dimensional or 3-dimensional are from
now on accepted. Other arrays (e.g. 1-dimensional ones)
will be rejected with an assertion error.
Add functions:
* `imgaug.augmentables.utils.normalize_imglike_shape()`.
List of deprecations:
* `numpy.ndarray` as value of parameter `shape` in
`KeypointsOnImage.__init__`.
* `numpy.ndarray` as value of parameter `shape` in
`BoundingBoxesOnImage.__init__`.
* `numpy.ndarray` as value of parameter `shape` in
`LineStringsOnImage.__init__`.
* `numpy.ndarray` as value of parameter `shape` in
`PolygonsOnImage.__init__`.
================================================
FILE: changelogs/master/changed/20200522_limit_dtype_support_alphablend.md
================================================
# Limit dtype Support in Alpha Blending in Windows #678
This patch marks the dtypes uint64, int64 and float64
as 'only supported to a limited degree' in blend_alpha().
The dtypes require float128 for accurate output
computations, which is not supported in Windows.
Additionally, a better error message is provided if one
of these dtypes is used and float128 is not supported.
================================================
FILE: changelogs/master/fixed/20200217_legacy_kp_aug_fallback.md
================================================
* Fix legacy augmenters (i.e. no `_augment_batch_()`
implemented) not automatically falling back to
`_augment_keypoints()` for the augmentation of bounding
boxes, polygons and line strings. #617 #618
================================================
FILE: changelogs/master/fixed/20200225_fix_imageio.md
================================================
* Fixed a broken `imageio` dependency. The package no longer
supports python 3.4 and earlier and will fail to install in the
latest version. The dependency is now set to be more
restrictive for older python versions. #627 #628
================================================
FILE: changelogs/master/fixed/20200412_fix_change_color_temperature.md
================================================
* Fixed `change_color_temperatures_()` crashing on batches
that did not contain exactly `1` or `3` images. #646 #650
================================================
FILE: changelogs/master/fixed/20200521_fix_skimage_slic_warning.md
================================================
- Fixed an `skimage` deprecation warning in `Superpixels`. #672
================================================
FILE: changelogs/master/fixed/20200522_fix_blend_alpha_f128.md
================================================
- A problem was fixed that led to `blend_alpha()`
always producing assertion errors if the dtype
`float128` was not available on the given
system. #678
================================================
FILE: changelogs/master/fixed/20200522_fix_mac_multiprocessing.md
================================================
- Fixed an error on MacOS in python 3.7 that could appear
when using multicore augmentation. The library will now
use `spawn` mode in that situation. The error can thus
still appear when using a custom multiprocessing
implementation. It is recommended to use python 3.8 on
Mac. #673
================================================
FILE: changelogs/master/fixed/20200522_fix_pad_f128.md
================================================
- A problem was fixed that led to `pad()` always crashing
if the dtype `float128` was not available on the given
system. #678
================================================
FILE: changelogs/master/fixed/20200522_fix_permission_denied.md
================================================
- Fixed a `permission denied` error when calling
`StochasticParameter.draw_distribution_graph()` in
Windows. #678
================================================
FILE: changelogs/master/fixed/20200525_fix_affine_cval_float.md
================================================
- Fixed `Affine` casting float cvals to int, even when
the image had a float dtype, making it impossible to
properly use cvals for images with value
range `[0.0, 1.0]`. #669 #680
================================================
FILE: changelogs/master/fixed/20200601_fix_affine_skimage_order_0.md
================================================
- Fixed a deprecation warning in `Affine` that would
be caused when providing boolean images and
`order != 0`. #685
================================================
FILE: changelogs/master/improved/20200211_improve_performance_add.md
================================================
# Improve Performance of `Add` #608
This patch improves the performance of
`imgaug.arithmetic.add_scalar()`
and the corresponding augmenter `Add` for `uint8` inputs.
The expected performance improvement is 1.5x to 6x.
(More for image arrays with higher widths/heights than
smaller ones. More for more channels. More for a single
scalar added as opposed to channelwise values.)
Add functions:
* `imgaug.augmenters.arithmetic.add_scalar_()`.
================================================
FILE: changelogs/master/improved/20200213_improved_blend_performance.md
================================================
# Improve Performance of Alpha-Blending #610
This patch reworks `imgaug.augmenters.blend.blend_alpha()` to
improve its performance. In the case of a scalar constant alpha
value and both image inputs (foreground, background) being
`uint8`, the improved method is roughly 10x faster. In the case
of one constant alpha value per channel, the expected speedup
is around 2x to 7x (more for larger images). In the case of
`(H,W,[C])` alpha masks, the expected speedup is around 1.3x
to 2.0x (`(H,W)` masks are faster for larger images,
`(H,W,C)` the other way round).
Add functions:
* `imgaug.augmenters.blend.blend_alpha_()`
================================================
FILE: changelogs/master/improved/20200216_add_elementwise_performance.md
================================================
# Improve Performance of Elementwise Addition #612
This patch improves the performance of
`imgaug.augmenters.arithmetic.add_elementwise()` for `uint8`
images. The performance increase is expected to be between
roughly 1.5x and 5x (more for very dense `value` matrices,
i.e. for channelwise addition). This change affects
`AddElementwise`, `AdditiveGaussianNoise`,
`AdditiveLaplaceNoise` and `AdditivePoissonNoise`.
================================================
FILE: changelogs/master/improved/20200216_improve_multiply_scalar_perf.md
================================================
# Improve Performance of `multiply_scalar()` #614
This patch improves the performance of
`imgaug.augmenters.arithmetic.multiply_scalar()` for
`uint8` inputs. The function is now between 1.2x and 7x
faster (more for smaller images and channelwise
multipliers). This change affects `Multiply`.
Add functions:
* `imgaug.augmenters.arithmetic.multiply_scalar_()`.
================================================
FILE: changelogs/master/improved/20200216_improved_mul_elementwise_perf.md
================================================
# Improve Performance of Elementwise Multiplication #615
This patch improves the performance of
`imgaug.augmenters.arithmetic.multiply_elementwise()`. The
performance improvement is roughly between 1.5x and 10x.
The effect is stronger for smaller images and denser
matrices of multipliers (i.e. `(H,W,C)` instead of `(H,W)`).
This change affects `MultiplyElementwise`.
Add functions:
* `imgaug.augmenters.arithmetic.multiply_elementwise_()`.
================================================
FILE: changelogs/master/improved/20200217_vectorize_cropandpad.md
================================================
# Vectorize `CropAndPad` #619
This patch vectorizes parts of `CropAndPad`, especially the
sampling process, leading to an improved performance for large
batches.
Previously, cropping an image below a height and/or width of
`1` would be prevented by `CropAndPad` *and* a warning was
raised if it was tried. That warning was now removed, but
height/width of at least `1` is still ensured.
================================================
FILE: changelogs/master/improved/20200221_reworked_pooling.md
================================================
# Improved Performance of Pooling Operations #622
This patch improves the performance of pooling operations.
For `uint8` arrays, `max_pool()` and `min_pool()` are now
between 3x and 8x faster. The improvements are more
significant for larger images and smaller kernel sizes.
In-place versions of `max_pool()` and `min_pool()` are also
added. Both `MaxPooling` and `MinPooling` now use these
functions.
The performance of `avg_pool()` for `uint8` is improved by
roughly 4x to 15x. (More for larger images and smaller
kernel sizes.)
The performance of `median_pool()` for `uint8` images is
improved by roughly 1.7x to 7x, if the kernel size is 3
or 5 or if the kernel size is 7, 9, 11, 13 and the image
size is 32x32 or less. In both cases the kernel also has to be
symmetric.
In the case of a kernel size of 3, the performance improvement
is most significant for larger images. In the case of 5, it
is fairly even over all kernel sizes. In the case of 7 or higher,
it is more significant for smaller images.
Add functions:
* `imgaug.imgaug.min_pool_()`.
* `imgaug.imgaug.max_pool_()`.
================================================
FILE: changelogs/master/improved/20200223_blur_avg.md
================================================
# Improved Average Bluring #625
This patch adds `imgaug.augmenters.blur.blur_avg_()`,
which applies an averaging blur kernel to images. The method
is slightly faster for single image inputs (factor of 1.01x to
1.1x, more for medium-sized images around `128x128`) than
the one used in `AverageBlur`. The performance of `AverageBlur`
however is likely not changed significantly due to input
validation now being done per image instead of per batch.
Add functions:
* `imgaug.augmenters.blur.blur_avg_()`
================================================
FILE: changelogs/master/improved/20200223_faster_elastic_tf.md
================================================
# Improved Performance of `ElasticTransformation` #624
This patch applies various performance-related changes to
`ElasticTransformation`. These cover: (a) the re-use of
generated random samples for multiple images in the same
batch (with some adjustments so that they are not identical),
(b) the caching of generated and re-useable arrays,
(c) a performance-optimized smoothing method for the
underlying displacement maps and (d) the use of nearest
neighbour interpolation (`order=0`) instead of cubic
interpolation (`order=3`) as the new default parameter
for `order`.
These changes lead to a speedup of about 3x to 4x (more
for larger images) at a slight loss of visual
quality (mainly from `order=0`) and variety (due to the
re-use of random samples within each batch).
The new smoothing method leads to slightly stronger
displacements for larger `sigma` values.
================================================
FILE: changelogs/master/improved/20200229_convolve.md
================================================
# Improved Convolution Filters #632
This patch reworks the backend of all convolutional
filters. It extracts the convolution logic out of
`Convolve` and moves it into the new function
`imgaug.augmenters.convolutional.convolve_()` (with
non-in-place version `convolve()`).
The logic is also reworked so that fewer convolution
function calls and more in-place modification is
used. This should lead to an improved performance.
These changes also affect `Sharpen`, `Emboss`,
`EdgeDetect`, `DirectedEdgeDetect` and `MotionBlur`.
Add functions:
* `imgaug.augmenters.convolutional.convolve_()`
* `imgaug.augmenters.convolutional.convolve()`
================================================
FILE: changelogs/master/improved/20200229_faster_invert.md
================================================
# Improved Performance of `invert_()` #631
This patch improves the performance of
`imgaug.augmenters.arithmetic.invert_()` for `uint8`
images. The update is expected to improve the
performance by a factor of 4.5x to 5.3x (more for
smaller images) if no threshold is provided and by
1.5x to 2.7x (more for smaller images) if a threshold
is provided.
In both cases these improvements are only realised
if either no custom minimum and maximum for the
value range is provided or only a custom maximum
is provided. (This is expected to be the case for most
users.)
These improvements also affect `Invert` and `Solarize`.
================================================
FILE: changelogs/master/improved/20200308_prefetching.md
================================================
# Added Automatic Prefetching of Random Number Samples #634
This patch adds automatic prefetching of random samples,
which performs a single large random sampling call instead
of many smaller ones. This seems to improve the
performance of most augmenters by 5% to 40% for longer
augmentation sessions (50+ consecutive batches of 128
examples each). A few augmenters seem to have gotten
slightly slower, though these might be measuring errors.
The prefetching is done by adding a new parameter,
`imgaug.parameters.AutoPrefetcher`, which prefetches
samples from a child parameter.
The change is expected to have for most augmenters a
slight negative performance impact if the augmenters
are used only once and not for multiple batches. For a
few augmenters there might be sizeable negative
peformance impact (due to prefetching falsely being
performed). The negative impact can be avoided in
these cases by wrapping the augmentation calls in
`with imgaug.parameters.no_prefetching(): ...`.
This patch also adds the property `prefetchable` to
`StochasticParameter`, which defaults to `False` and
determines whether the parameter's outputs may be
prefetched.
It further adds to
`handle_continuous_param()`, `handle_discrete_param()`.
`handle_categorical_string_param()`,
`handle_discrete_kernel_size_param()` and
`handle_probability_param()` in `imgaug.parameters` the
new argument `prefetch`. If set to `True` (the default),
these functions may now partially or fully wrap their
results in `AutoPrefetcher`.
Add functions:
* `imgaug.random.RNG.create_if_not_rng_()`
* `imgaug.parameters.toggle_prefetching()`
* `imgaug.testutils.is_parameter_instance()`
* `imgaug.testutils.remove_prefetching()`
Add properties:
* `imgaug.parameters.StochasticParameter.prefetchable`
Add classes:
* `imgaug.parameters.toggled_prefetching()` (context)
* `imgaug.parameters.no_prefetching()` (context)
* `imgaug.parameters.AutoPrefetcher`
================================================
FILE: changelogs/master/improved/20200315_segment_replacement.md
================================================
# Improved Performance of Segment Replacement #640 #684
This patch improves the performance of segment
replacement (by average colors within the segments),
used in `Superpixels` and `segment_voronoi()`.
The new method is in some cases (especially small
images) up to 100x faster now. For 224x224 images
the speed improvement is around 1.4x to 10x,
depending on how many segments have to be replaced.
This change is expected to have a moderate positive
impact on `Superpixels` and `segment_voronoi()` (i.e.
`Voronoi`).
Added functions:
* `imgaug.augmenters.segmentation.replace_segments_`
Added classes:
* `imgaug.testutils.temporary_constants` (context)
================================================
FILE: changelogs/master/improved/20200413_frequency_noise.md
================================================
# Improve Performance of `FrequencyNoise` #651
This patch improves the performance of
`imgaug.parameters.FrequencyNoise`, which is used in some
weather augmenters. The parameter now samples `HxW` arrays
about 1.3x to 1.5x faster (more improvement for larger
images).
================================================
FILE: changelogs/master/improved/20200517_faster_dtype_checks.md
================================================
# Improved Performance of dtype checks #663
This patch improves the performance of dtype checks
throughout the library. The new method verifies input
arrays around 10x to 100x faster than the previous one.
Add functions:
* `imgaug.dtypes.gate_dtypes_strs()`
* `imgaug.dtypes.allow_only_uint8()`
Add decorators:
* `imgaug.testutils.ensure_deprecation_warning`
Deprecate functions:
* `imgaug.dtypes.gate_dtypes()`
================================================
FILE: changelogs/master/improved/20200521_improved_cicd_testing.md
================================================
# Improved CI/CD Testing #670 #678
This patch improves the CI/CD environment by adding
github actions. The library is now automatically tested
in Ubuntu with python 2.7, 3.5, 3.6, 3.7 and 3.8,
as well as MacOS and Windows with the same python
versions (except for 2.7 in Windows).
Previously, only Ubuntu with python <=3.7 was
automatically tested in the CI/CD chain.
Additionally, the CI/CD pipeline now also generates
wheel files (sdist, bdist) for every patch merged
into master.
================================================
FILE: changelogs/master/improved/20200522_tests_f128.md
================================================
# Removed Requirement of float128 Support from Tests #677
This patch modifies all tests so that they can be run on
systems that do not support the dtype `float128`, such
as Windows.
================================================
FILE: changelogs/master/improved/20200530_glass_blur_perf.md
================================================
# Improved Performance of Glass Blur #683 #684
This patch improves the performance of
`imgaug.augmenters.imgcorruptplike.apply_glass_blur()`
and the corresponding augmenter in python 3.6+.
The improvement is around 14x to 45x, depending on
the image size (larger images have more speedup).
Added dependencies:
* `numba` (requires python 3.6+)
================================================
FILE: changelogs/master/refactored/20200223_blur_gaussian.md
================================================
# Refactored `blur_gaussian_()` #626
This patch cleans up the code of
`imgaug.augmenters.blur.blur_gaussian_()`. A very small
performance improvement (factor ~1.03x) is expected.
================================================
FILE: changelogs/master/refactored/20200314_affine.md
================================================
# Refactored Affine #639
This patch refactors affine to make the code more
readable and change the matrix generation routine to
a numpy-based one. It also merges the matrix
generation of `Affine` and `pillike.Affine` and lays
the foundation for adding a `center` parameter to
`Affine`.
This patch also changes the shear mechanic in
`Affine`. When shearing on the x-axis, the points at
the top are now only moved to the left/right and no
longer up/down. Previously, they were also slightly
moved up/down. (Analogous for the y-axis.)
================================================
FILE: changelogs/v0.2.8.summary.md
================================================
# 0.2.8
This update focused on extending and documenting the library's dtype support, improving the performance and reworking multicore augmentation.
## dtype support
Previous versions of `imgaug` were primarily geared towards `uint8`.
In this version, all augmenters and helper functions were refactored to be more tolerant towards non-uint8 dtypes.
Additionally all augmenters were tested with non-uint8 dtypes and an overview of the expected support-level
is now listed in the documentation on page [dtype support](https://imgaug.readthedocs.io/en/latest/source/dtype_support.html).
Further details are listed in the docstrings of each individual augmenter or helper function.
## Performance Improvements
Below are some numbers for the achieved performance improvements compared to 0.2.7.
The measurements were taken using realistic 224x224x3 uint8 images and batch size 128.
The percentage values denote the increase in bandwidth (i.e. mbyte/sec) of the respective
augmenter given the described input. Improvements for smaller images, smaller batch sizes
and non-uint8 dtypes may differ. Augmenters with less than roughly 10% improvement are not
listed. While the numbers here are exact, there is some measurement error involved as they
were calculated based on a rather low number of 100 repetitions.
* Sequential (with 2x Noop as children) +184% to +276%
* SomeOf (with 3x Noop as children) +24% to +49%
* OneOf (with 3x Noop as children) +21%
* Sometimes (with Noop as child) +23%
* WithChannels +32%
* Add +216%
* AddElementwise +49%
* AdditiveGaussianNoise +26%
* AdditiveLaplaceNoise +20%
* AdditivePoissonNoise +18%
* Multiply +206%
* MultiplyElementwise +74%
* Dropout +154%
* CoarseDropout +246%
* ReplaceElementwise +119%
* ImpulseNoise +333%
* SaltAndPepper +184%
* CoarseSaltAndPepper +227%
* Salt +204%
* CoarseSalt +260%
* Pepper +208%
* CoarsePepper +276%
* Invert +1192%
* GaussianBlur +885%
* AddToHueAndSaturation +48%
* GammaContrast +2988%
* SigmoidContrast +519%
* LogContrast +1048%
* LinearContrast +448%
* Convolve +47%
* Sharpen +29%
* Emboss +18%
* EdgeDetect +41%
* DirectedEdgeDetect +53%
* Fliplr +75%
* Flipud +25%
* Affine +7% to +33%
* ElasticTransformation +650 to +680%
* CropAndPad +30% to +77% (from improved padding)
* Pad +40 to +140%
* PadToFixedSize +288%
* KeepSizeByResize (with CropToFixedSize as child) +58%
* Snowflakes +44%
* SnowflakesLayer +42%
## multicore augmentation
The implementation for multicore augmentation was completely rewritten and is now a wrapper around python's `multiprocessing.Pool`. Compared to the old version, it is by far less fragile and faster. It is also easier to use. Every augmenter now offers a simple `pool()` method, which can be used to quickly spawn a pool of child workers on multiple CPU cores. Example:
```python
aug = iaa.PiecewiseAffine(0.2)
with aug.pool(processes=-1, seed=123) as pool:
batches_aug = pool.imap_batches(batches_generator, chunksize=32)
for batch_aug in batches_aug:
# do something
```
Here, `batches_generator` is a generator that yields instances of `imgaug.Batch`, e.g. something like `imgaug.Batch(images=, keypoints=[imgaug.KeypointsOnImage(...), imgaug.KeypointsOnImage(...), ...])`. The arguement `processes=-1` spawns `N-1` workers, where `N` is the number of CPU cores (includes hyperthreads).
Note that `Augmenter.augment_batches(batches, background=True)` still works and now uses the above `pool()` method.
## imgaug.imgaug
* Added constants that control the min/max values for seed generation
* Improved performance of `pad()`
* this change also improves the performance of:
* `imgaug.imgaug.pad_to_aspect_ratio()`,
* `imgaug.imgaug.HeatmapsOnImage.pad()`,
* `imgaug.imgaug.HeatmapsOnImage.pad_to_aspect_ratio()`,
* `imgaug.imgaug.SegmentationMapOnImage.pad()`,
* `imgaug.imgaug.SegmentationMapOnImage.pad_to_aspect_ratio()`,
* `imgaug.augmenters.size.PadToFixedSize`,
* `imgaug.augmenters.size.Pad`,
* `imgaug.augmenters.size.CropAndPad`
* Changed `imshow()` to explicitly make the plot figure size dependent on the input image size.
* Refactored `SegmentationMapOnImage` to have simplified dtype handling in `__init__`
* Fixed an issue with `SEED_MAX_VALUE` exceeding the `int32` maximum on some systems, causing crashes related to
RandomState.
* Moved BatchLoader to `multicore.py` and replaced the class with an alias pointing to `imgaug.multicore.BatchLoader`.
* Moved BackgroundAugmenter to `multicore.py` and replaced the class with an alias pointing to `imgaug.multicore.BatchLoader`.
* Renamed `HeatmapsOnImage.scale()` to `HeatmapsOnImage.resize()`.
* Marked `HeatmapsOnImage.scale()` as deprecated.
* Renamed `SegmentationMapOnImage.scale()` to `SegmentationMapOnImage.resize()`.
* Marked `SegmentationMapOnImage.scale()` as deprecated.
* Renamed `BoundingBox.cut_out_of_image()` to `BoundingBox.clip_out_of_image()`.
* Marked `BoundingBox.cut_out_of_image()` as deprecated.
* Renamed `BoundingBoxesOnImage.cut_out_of_image()` to `BoundingBoxesOnImage.clip_out_of_image()`.
* Marked `BoundingBoxesOnImage.cut_out_of_image()` as deprecated.
* Marked `Polygon.cut_out_of_image()` as deprecated. (The analogous clip function existed already.)
* Renamed in `imgaug.Batch` the attributes storing input data `_unaug`, e.g. `imgaug.Batch.images` to `imgaug.Batch.images_unaug` or `imgaug.Batch.keypoints` to `imgaug.Batch.keypoints_unaug`. The old attributes are still accessible, but will raise a DeprecatedWarning.
## imgaug.multicore
* Created this file.
* Moved `BatchLoader` here from `imgaug.py`.
* Moved `BackgroudAugmenter` here from `imgaug.py`.
* Marked `BatchLoader` as deprecated.
* Marked `BackgroundAugmenter` as deprecated.
* Added class `Pool`. This is the new recommended way for multicore augmentation. `BatchLoader`/`BackgroundAugmenter` should not be used anymore. Example:
```python
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import multicore
import numpy as np
aug = iaa.Add(1)
images = np.zeros((16, 128, 128, 3), dtype=np.uint8)
batches = [ia.Batch(images=np.copy(images)) for _ in range(100)]
with multicore.Pool(aug, processes=-1, seed=2) as pool:
batches_aug = pool.map_batches(batches, chunksize=8)
print(np.sum(batches_aug[0].images_aug[0]))
```
The example starts a pool with N-1 workers (N=number of CPU cores) and augments 100 batches using these workers.
Use `imap_batches()` to feed in and get out a generator.
## imgaug.parameters
* Added `TruncatedNormal`
* Added `handle_discrete_kernel_size_param()`
* Improved dtype-related interplay of `FromLowerResolution` and `imresize_many_images()`
* Improved performance for sampling from `Deterministic` by about 2x
* Improved performance for sampling from `Uniform` with `a == b`
* Improved performance for sampling from `DiscreteUniform` with `a == b`
* Improved performance for sampling from `Laplace` with `scale=0`
* Improved performance for sampling from `Normal` with `scale=0`
* Improved performance of `Clip` and improved code style
* Refactored `float check in force_np_float_dtype()`
* Refactored `RandomSign`
* Refactored various unittests to be more flexible with regards to returned dtypes
* Refactored `StochasticParameter.draw_distribution_graph()` to use internally tempfile-based drawing. Should result in higher-quality outputs.
* Refactored unittest for `draw_distributions_grid()` to improve performance
* Fixed in `draw_distributions_grid()` a possible error from arrays with unequal shapes being combined to one array
* Fixed a problem with `Sigmoid` not returning floats
* Fixed noise produced by `SimplexNoise` having values below 0.0 or above 1.0
* Fixed noise produced by `SimplexNoise` being more biased towards 0 than it should be
## imgaug.dtypes
* Added new file `imgaug/dtypes.py` and respective test file `test_dtypes.py`.
* Added `clip_to_dtype_value_range_()`
* Added `get_value_range_of_dtype()`
* Added `promote_array_dtypes_()`
* Added `get_minimal_dtype()`
* Added `get_minimal_dtypes_for_values()`
* Added `get_minimal_dtype_by_value_range()`
* Added `restore_dtypes_()`
* Added `gate_dtypes()`
* Added `increase_array_resolutions()`
* Added `copy_dtypes_for_restore()`
## imgaug.augmenters.meta
* Added `estimate_max_number_of_channels()`
* Added `copy_arrays()`
* Added an optional parameter `default` to `handle_children_lists()`
* Enabled `None` as arguments for `Lambda` and made all arguments optional
* Enabled `None` as arguments for `AssertLambda` and made all arguments optional
* Improved dtype support of `AssertShape`
* Improved dtype support of `AssertLambda`
* Improved dtype support of `Lambda`
* Improved dtype support of `ChannelShuffle`
* Improved dtype support of `WithChannels`
* Improved dtype support of `Sometimes`
* Improved dtype support of `SomeOf`
* Improved dtype support of `Sequential`
* Improved dtype support of `Noop`
* [breaking, mostly internal] Removed `restore_augmented_images_dtypes()`
* [breaking, mostly internal] Removed `restore_augmented_images_dtypes_()`
* [breaking, mostly internal] Removed `restore_augmented_images_dtype()`
* [breaking, mostly internal] Removed `restore_augmented_images_dtype_()`
* [breaking, mostly internal] Refactored `Augmenter.augment_images()` and `Augmenter._augment_images()` to default
`hooks` to `None`
* This will affect any custom implemented augmenters that try to access the hooks argument.
* [breaking, mostly internal] Refactored `Augmenter.augment_heatmaps()` and `Augmenter._augment_heatmaps()` to default
`hooks` to `None`
* Same as above for images.
* [breaking, mostly internal] Refactored `Augmenter.augment_keypoints()` and `Augmenter._augment_keypoints()` to default
`hooks` to None
* Same as above for images.
* [breaking, mostly internal] Improved performance of image augmentation for augmenters with children
* For calls to `augment_image()`, the validation, normalization and copying steps are skipped if the call is
a child call (e.g. a `Sequential` calling `augment_images()` on a child `Add`). Hence, such child calls augment
now fully in-place (the top-most call still creates a copy though, so from the user perspective nothing
changes). Custom implemented augmenters that rely on child calls to `augment_images()` creating copies will
break from this change.
For an example `Sequential` containing two `Noop` augmenters, this change improves the performance by roughly 2x
* [breaking, mostly internal] Improved performance of heatmap augmentation for augmenters with children
* Same as above for images.
* Speedup is around 2-3x for an exemplary `Sequential` containing two `Noop`s.
* This will similarly affect segmentation map augmentation too.
* [breaking, mostly internal] Improved performance of keypoint augmentation for augmenters with children
* Same as above for images.
* Speedup is around 1.5-2x for an exemplary `Sequential` containing two Noops.
* This will similarly affect bounding box augmentation.
* [critical] Fixed a bug in the augmentation of empty `KeypointsOnImage` instances that would lead image and keypoint
augmentation to be un-aligned within a batch after the first empty `KeypointsOnImage` instance. (#231)
* Added `pool()` to `Augmenter`. This is a helper to start a `imgaug.multicore.Pool` via `with augmenter.pool() as pool: ...`.
* Refactored `Augmenter.augment_batches(..., background=True)` to use `imgaug.multicore.Pool`.
* Changed `to_deterministic()` in `Augmenter` and various child classes to derive its new random state from the augmenter's local random state instead of the global random state.
* Enabled support for non-list `HeatmapsOnImage` inputs in `Augmenter.augment_heatmaps()`. (Before, only lists were supported.)
* Enabled support for non-list `SegmentationMapOnImage` inputs in `Augmenter.augment_segmentation_maps()`. (Before, only lists were supported.)
* Enabled support for non-list `KeypointsOnImage` inputs in `Augmenter.augment_keypoints()`. (Before, only lists were supported.)
* Enabled support for non-list `BoundingBoxesOnImage` inputs in `Augmenter.augment_bounding_boxes()`. (Before, only lists were supported.)
## imgaug.augmenters.arithmetic
* `ContrastNormalization` is now an alias for `LinearContrast`
* Restricted `JpegCompression` to uint8 inputs. Other dtypes will now produce errors early on.
* Changed in `Add` the parameter `value` to be continuous and removed its `value_range`
## imgaug.augmenters.blend
* Renamed `imgaug.augmenters.overlay` to `imgaug.augmenters.blend`. Functions and classes in `imgaug.augmenters.overlay` are still accessible, but will now raise a DeprecatedWarning.
* Added `blend_alpha()`.
* Refactored `Alpha` to be simpler and use `blend_alpha()`.
* Fixed `Alpha` not having its own `__str__` method.
* Improved dtype support of `AlphaElementwise`.
## imgaug.augmenters.blur
* Added function `blur_gaussian()`
## imgaug.augmenters.color
* Added `Lab2RGB` and `Lab2BGR` to `ChangeColorspace`
* Refactored the main loop in `AddToHueAndSaturation` to make it simpler and faster
* Fixed `ChangeColorspace` not being able to convert from RGB/BGR to Lab/Luv
## imgaug.augmenters.contrast
* Added `AllChannelsCLAHE`
* Added `CLAHE`
* Added `AllChannelsHistogramEqualization`
* Added `HistogramEqualization`
* Added `_IntensityChannelBasedApplier`
* Added function `adjust_contrast_gamma()`
* Added function `adjust_contrast_sigmoid()`
* Added function `adjust_contrast_log()`
* Refactored random state handling in `_ContrastFuncWrapper`
* [breaking, internal] Removed `_PreserveDtype`
* [breaking, internal] Renamed `_adjust_linear` to `adjust_contrast_linear()`
## imgaug.augmenters.convolutional
* Refactored `AverageBlur` to have improved random state handling
* Refactored `GaussianBlur` to only overwrite input images when that is necessary
* Refactored `GaussianBlur` to have a simplified main loop
* Refactored `AverageBlur` to have a simplified main loop
* Refactored `MedianBlur` to have a simplified main loop
* Refactored `BilateralBlur` to have a simplified main loop
* Improved dtype support of `GaussianBlur`
* Improved dtype support of `AverageBlur`
* Improved dtype support of `Convolve`
## imgaug.augmenters.flip
* Improved dtype support of `Fliplr`
* Improved dtype support of `Flipud`
* Refactored `Fliplr` main loop to be more elegant and tolerant
* Refactored `Flipud` main loop to be more elegant and tolerant
* Added alias `HorizontalFlip` for `Fliplr`.
* Added alias `VerticalFlip` for `Flipud`.
## imgaug.augmenters.geometric
* `ElasticTransformation`
* [breaking, mostly internal] `generate_indices()` now returns only the pixelwise shift as a tuple of x and y
* [breaking, mostly internal] `generate_indices()` has no longer a `reshape` argument
* [breaking, mostly internal] `renamed generate_indices()` to `generate_shift_maps()`
* [breaking, mostly internal] `map_coordinates()` now expects to get the pixelwise shift as its input, instead of
the target coordinates
## imgaug.augmenters.segmentation
* Improved dtype support of `Superpixels`
## imgaug.augmenters.size
* Removed the restriction to `uint8` in `Scale`. The augmenter now supports the same dtypes as `imresize_many_images()`.
* Fixed missing pad mode `mean` in `Pad` and `CropAndPad`.
* Improved error messages related to pad mode.
* Improved and fixed docstrings of `CropAndPad`, `Crop`, `Pad`.
* Renamed `Scale` to `Resize`.
* Marked `Scale` as deprecated.
## other
* Improved descriptions of the library in `setup.py`
* `matplotlib` is now an optional dependency of the library and loaded lazily when needed
* `Shapely` is now an optional dependency of the library and loaded lazily when needed
* `opencv-python` is now a dependency of the library
* `setup.py` no longer enforces `cv2` to be installed (to allow installing libraries in random order)
* Minimum required `numpy` version is now 1.15
================================================
FILE: changelogs/v0.2.9.summary.md
================================================
# 0.2.9
This update mainly covers the following topics:
* Moved classes/methods related to augmentable data to their own modules.
* Added polygon augmentation methods.
* Added line strings and line string augmentation methods.
* Added easier augmentation interface.
## New 'augmentables' Modules
For the Polygon and Line String augmentation, new classes and methods had to be
added. The previous file for that was `imgaug/imgaug.py`, which however was
already fairly large. Therefore, all classes and methods related to augmentable
data were split off and moved to `imgaug/augmentables/.py`. The new
modules and their main contents are:
* `imgaug.augmentables.batches`: Contains `Batch`, `UnnormalizedBatch`.
* `imgaug.augmentables.utils`: Contains utility functions.
* `imgaug.augmentables.bbs`: Contains `BoundingBox`, `BoundingBoxesOnImage`.
* `imgaug.augmentables.kps`: Contains `Keypoint`, `KeypointsOnImage`.
* `imgaug.augmentables.polys`: Contains `Polygon`, `PolygonsOnImage`.
* `imgaug.augmentables.lines`: Contains `LineString`, `LineStringsOnImage`.
* `imgaug.augmentables.heatmaps`: Contains `HeatmapsOnImage`.
* `imgaug.augmentables.segmaps`: Contains `SegmentationMapOnImage`.
Currently, all augmentable classes can still be created via `imgaug.`,
e.g. `imgaug.BoundingBox` still works.
Changes related to the new modules:
* Moved `Keypoint`, `KeypointsOnImage` and `imgaug.imgaug.compute_geometric_median` to `augmentables/kps.py`.
* Moved `BoundingBox`, `BoundingBoxesOnImage` to `augmentables/bbs.py`.
* Moved `Polygon`, `PolygonsOnImage` and related classes/functions to `augmentables/polys.py`.
* Moved `HeatmapsOnImage` to `augmentables/heatmaps.py`.
* Moved `SegmentationMapOnImage` to `augmentables/segmaps.py`.
* Moved `Batch` to `augmentables/batches.py`.
* Added module `imgaug.augmentables.utils`.
* Added function `normalize_shape()`.
* Added function `project_coords()`.
* Moved line interpolation functions `_interpolate_points()`, `_interpolate_point_pair()` and `_interpolate_points_by_max_distance()` to `imgaug.augmentables.utils` and made them public functions.
* Refactored `__init__()` of `PolygonsOnImage`, `BoundingBoxesOnImage`, `KeypointsOnImage` to make use of `imgaug.augmentables.utils.normalize_shape()`.
* Refactored `KeypointsOnImage.on()` to use `imgaug.augmentables.utils.normalize_shape()`.
* Refactored `Keypoint.project()` to use `imgaug.augmentables.utils.project_coords()`.
## Polygon Augmentation
Polygons were already part of `imgaug` for quite a while, but couldn't be
augmented yet. This version adds methods to perform such augmentations.
It also makes some changes to the `Polygon` class, see the list of changes
below.
Example for polygon augmentation:
```python
import imgaug as ia
import imgaug.augmenters as iaa
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
image = ia.quokka(size=0.2)
psoi = PolygonsOnImage([
Polygon([(0, 0), (20, 0), (20, 20)])
], shape=image.shape)
image_aug, psoi_aug = iaa.Affine(rotate=45).augment(
images=[image],
polygons=[psoi]
)
```
See [imgaug-doc/notebooks](https://github.com/aleju/imgaug-doc/tree/master/notebooks)
for a jupyter notebook with many more examples.
Changes related to polygon augmentation:
* Added `_ConcavePolygonRecoverer` to `imgaug.augmentables.polys`.
* Added `PolygonsOnImage` to `imgaug.augmentables.polys`.
* Added polygon augmentation methods:
* Added `augment_polygons()` to `Augmenter`.
* Added `_augment_polygons()` to `Augmenter`.
* Added `_augment_polygons_as_keypoints()` to `Augmenter`.
* Added argument `polygons` to `imgaug.augmentables.batches.Batch`.
* Added attributes `polygons_aug` and `polygons_unaug` to `imgaug.augmentables.batches.Batch`.
* Added polygon handling to `Augmenter.augment_batches()`.
* Added property `Polygon.height`.
* Added property `Polygon.width`.
* Added method `Polygon.to_keypoints()`.
* Added optional drawing of corner points to `Polygon.draw_on_image()` and `PolygonsOnImage.draw_on_image()`.
* Added argument `raise_if_too_far_away=True` to `Polygon.change_first_point_by_coords()`.
* Added `imgaug.quokka_polygons()` function to generate example polygon data.
* [rarely breaking] `Polygon.draw_on_image()`, `PolygonsOnImage.draw_on_image()`
* Refactored to make partial use `LineString` methods.
* Added arguments `size` and `size_perimeter` to control polygon line thickness.
* Renamed arguments `alpha_perimeter` to `alpha_line`, `color_perimeter` to `color_line` to align with `LineStrings`.
* Renamed arguments `alpha_fill` to `alpha_face` and `color_fill` to `color_face`.
* [rarely breaking] Changed the output of `Polygon.clip_out_of_image()` from `MultiPolygon` to `list` of `Polygon`.
This breaks for anybody who has already used `Polygon.clip_out_of_image()`.
* Changed `Polygon.exterior_almost_equals()` to accept lists of tuples as argument `other_polygon`.
* Changed arguments `color` and `alpha` in `Polygon.draw_on_image()` and `PolygonsOnImage.draw_on_image()` to represent
the general color and alpha of the polygon. The colors/alphas of the inner area, perimeter and points are derived from
`color` and `alpha` (unless `color_inner`, `color_perimeter` or `color_points` are set (analogous for alpha)).
* Refactored `Polygon.project()` to use `LineString.project()`.
* Refactored `Polygon.shift()` to use `LineString.shift()`.
* [rarely breaking] `Polygon.exterior_almost_equals()`, `Polygon.almost_equals()`
* Refactored to make use of `LineString.coords_almost_equals()`.
* Renamed argument `interpolate` to `points_per_edge`.
* Renamed argument `other_polygon` to `other`.
* Renamed `color_line` to `color_lines`, `alpha_line` to `alpha_lines` in `Polygon.draw_on_image()` and `PolygonsOnImage.draw_on_image()`.
* Fixed `Polygon.clip_out_of_image(image)` not handling `image` being a tuple.
* Fixed `Polygon.is_out_of_image()` falsely only checking the corner points of the polygon.
## LineString Augmentation
This version adds Line String augmentation. Line Strings are simply lines made
up of consecutive corner points that are connected by straight lines.
Line strings have similarity with polygons, but do not have a filled inner area
and are not closed (i.e. first and last coordinate differ).
Similar to other augmentables, line string are represented with the classes
`LineString()` and
`LineStringsOnImage(, )`.
They are augmented e.g. via `Augmenter.augment_line_strings()`
or `Augmenter.augment(images=..., line_strings=...)`.
Example:
```python
import imgaug as ia
import imgaug.augmenters as iaa
from imgaug.augmentables.lines import LineString, LineStringsOnImage
image = ia.quokka(size=0.2)
lsoi = LineStringsOnImage([
LineString([(0, 0), (20, 0), (20, 20)])
], shape=image.shape)
image_aug, lsoi_aug = iaa.Affine(rotate=45).augment(
images=[image],
line_strings=[lsoi]
)
```
See [imgaug-doc/notebooks](https://github.com/aleju/imgaug-doc/tree/master/notebooks)
for a jupyter notebook with many more examples.
## Simplified Augmentation Interface
Augmentation of different data corresponding to the same image(s) has been
a bit convoluted in the past, as each data type had to be augmented on its own.
E.g. to augment an image and its bounding boxes, one had to first switch the
augmenters to deterministic mode, then augment the images, then the bounding
boxes. This version adds methods that perform these steps in one call.
Specifically, `Augmenter.augment(...)` is used for that, which has the alias
`Augmenter.__call__(...)`. One argument can be used for each augmentable,
e.g. `bounding_boxes=`.
Example:
```python
import imgaug as ia
import imgaug.augmenters as iaa
from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
image = ia.quokka(size=0.2)
kpsoi = KeypointsOnImage([Keypoint(x=0, y=10), Keypoint(x=10, y=5)],
shape=image.shape)
image_aug, kpsoi_aug = iaa.Affine(rotate=(-45, 45)).augment(
image=image,
keypoints=kpsoi
)
```
This will automatically make sure that image and keypoints are rotated by
corresponding amounts.
Normalization methods have been added to that class, which allow it to
process many more different inputs than just variations of `*OnImage`.
Example:
```python
import imgaug as ia
import imgaug.augmenters as iaa
image = ia.quokka(size=0.2)
kps = [(0, 10), (10, 5)]
image_aug, kps_aug = iaa.Affine(rotate=(-45, 45)).augment(
image=image, keypoints=kps)
```
Examples for other inputs that are automatically handled by `augment()`:
* Integer arrays as segmentation maps.
* Float arrays for heatmaps.
* `list([N,4] ndarray)` for bounding boxes. (One list for images,
then `N` bounding boxes in `(x1,y1,x2,y2)` form.)
* `list(list(list(tuple)))` for line strings. (One list for images,
one list for line strings on the image, one list for coordinates within
the line string. Each tuple must contain two values for xy-coordinates.)
* `list(list(imgaug.augmentables.polys.Polygon))` for polygons.
Note that this "skips" `imgaug.augmentables.polys.PolygonsOnImage`.
In **python <3.6**, `augment()` is limited to a maximum of two
inputs/outputs *and* if two inputs/outputs are used, then one of them must be
image data *and* such (augmented) image data will always be returned first,
independent of the argument's order.
E.g. `augment(line_strings=, polygons=)` would be invalid due to
not containing image data. `augment(polygons=, images=)` would
still return the images first, even though they are the second argument.
In **python >=3.6**, `augment()` may be called with more than two
arguments and will respect their order.
Example:
```python
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
image = ia.quokka(size=0.2)
kps = [(0, 10), (10, 5)]
heatmap = np.zeros((image.shape[0], image.shape[1]), dtype=np.float32)
rotate = iaa.Affine(rotate=(-45, 45))
heatmaps_aug, images_aug, kps_aug = rotate(
heatmaps=[heatmap],
images=[image],
keypoints=[kps]
)
```
To use more than two inputs/outputs in python <3.6, add the argument `return_batch=True`,
which will return an instance of `imgaug.augmentables.batches.UnnormalizedBatch`.
Changes related to the augmentation interface:
* Added `Augmenter.augment()` method.
* Added `Augmenter.augment_batch()` method.
* This method is now called by `Augmenter.augment_batches()` and multicore routines.
* Added `imgaug.augmentables.batches.UnnormalizedBatch`.
* Added module `imgaug.augmentables.normalization` for data normalization routines.
* Changed `augment_batches()`:
* Accepts now `UnnormalizedBatch` as input. It is automatically normalized before augmentation and unnormalized afterwards.
This allows to use `Batch` instances with non-standard datatypes.
* Accepts now single instances of `Batch` (and `UnnormalizedBatch`).
* The input may now also be a generator.
* The input may now be any iterable instead of just list (arrays or strings are not allowed).
* Marked support for non-`Batch` (and non-`UnnormalizedBatch`) inputs to `augment_batches()` as deprecated.
* Refactored `Batch.deepcopy()`
* Does no longer verify attribute datatypes.
* Allows now to directly change attributes of created copies, e.g. via `batch.deepcopy(images_aug=...)`.
## Other Additions, Changes and Refactorings
Keypoint augmentation
* Added method `Keypoint.draw_on_image()`.
* [mildly breaking] Added an `alpha` argument to `KeypointsOnImage.draw_on_image()`. This can break code that relied on
the order of arguments of the method (though will usually only have visual consequences).
* `KeypointsOnImage` and `Keypoint` copying:
* Added optional arguments `keypoints` and `shape` to `KeypointsOnImage.deepcopy()`.
* Added optional arguments `keypoints` and `shape` to `KeypointsOnImage.copy()`.
* Added method `Keypoint.copy()`.
* Added method `Keypoint.deepcopy()`.
* Refactored methods in `Keypoint` to use `deepcopy()` to create copies of itself (instead of instantiating new instances via `Keypoint(...)`).
* `KeypointsOnImage.deepcopy()` now uses `Keypoint.deepcopy()` to create Keypoint copies, making it more flexible.
* Refactored `KeypointsOnImage` to use `KeypointsOnImage.deepcopy()` in as many methods as possible to create copies of itself.
* Refactored `Affine`, `AffineCv2`, `PiecewiseAffine`, `PerspectiveTransform`, `ElasticTransformation`, `Rot90` to use `KeypointsOnImage.deepcopy()` and `Keypoint.deepcopy()` during keypoint augmentation.
* Changed `Keypoint.draw_on_image()` to draw a rectangle for the keypoint so long as *any* part of that rectangle is within the image plane.
(Previously, the rectangle was only drawn if the integer xy-coordinate of the point was inside the image plane.)
* Changed `KeypointsOnImage.draw_on_image()` to raise an error if an input image has shape `(H,W)`.
* Changed `KeypointsOnImage.draw_on_image()` to handle single-number inputs for `color`.
* `KeypointsOnImage.from_coords_array()`
* Marked as deprecated.
* Renamed to `from_xy_array()`.
* Renamed arg `coords` to `xy`.
* Changed the method from `staticmethod` to `classmethod`.
* Refactored to make code simpler.
* `KeypointsOnImage.get_coords_array()`
* Marked as deprecated.
* Renamed to `to_xy_array()`.
* Refactored `KeypointsOnImage.draw_on_image()` to use `Keypoint.draw_on_image()`.
Heatmap augmentation
* Changed `Affine`, `PiecewiseAffine`, `ElasticTransformation` to always use `order=3` for heatmap augmentation.
* Changed check in `HeatmapsOnImage` that validates whether the input array is within the desired value range `[min_value, max_value]`
from a hard exception to a soft warning (with clipping). Also improved the error message a bit.
Deprecation warnings:
* Added `imgaug.imgaug.DeprecationWarning`. The builtin python `DeprecationWarning` is silent since 2.7, which is why now a separate deprecation warning is used.
* Added `imgaug.imgaug.warn_deprecated()`.
* Refactored deprecation warnings to use this function.
* Added `imgaug.imgaug.deprecated` decorator.
* Refactored deprecation warnings to use this decorator.
Bounding Boxes:
* Added to `BoundingBox.extract_from_image()` the arguments `pad` and `pad_max`.
* Changed `BoundingBox.contains()` to also accept `Keypoint`.
* Changed `BoundingBox.project(from, to)` to also accept images instead of shapes.
* Renamed argument `thickness` in `BoundingBox.draw_on_image()` to `size` in order to match the name used for keypoints, polygons and line strings.
The argument `thickness` will still be accepted, but raises a deprecation warning.
* Renamed argument `thickness` in `BoundingBoxesOnImage.draw_on_image()` to `size` in order to match the name used for keypoints, polygons and line strings.
The argument `thickness` will still be accepted, but raises a deprecation warning.
* Refactored `BoundingBox` to reduce code repetition.
* Refactored `BoundingBox.extract_from_image()`. Improved some code fragments that looked wrong.
* Refactored `BoundingBoxesOnImage.draw_on_image()` to improve efficiency by evading unnecessary array copies.
Other:
* [rarely breaking] Added arguments `cval` and `mode` to `PerspectiveTransform` (PR #301).
This breaks code that relied on the order of the arguments and used `keep_size`, `name`, `deterministic` or `random_state` as positional arguments.
* Added `dtypes.clip_()` function.
* Added function `imgaug.imgaug.flatten()` that flattens nested lists/tuples.
* Changed `PerspectiveTransform` to ensure minimum height and width of output images (by default `2x2`).
This prevents errors in polygon augmentation (possibly also in keypoint augmentation).
* Refactored `imgaug.augmenters.blend.blend_alpha()` to no longer enforce a channel axis for foreground and background image.
* Refactored `imgaug/parameters.py` to reorder classes within the file.
* Re-allowed numpy 1.16 in `requirements.txt`.
## Fixes
* Fixed possible crash in `blend.blend_alpha()` if dtype numpy.float128 does not exist.
* Fixed a crash in `ChangeColorspace` when `cv2.COLOR_Lab2RGB` was actually called `cv2.COLOR_LAB2RGB` in the local OpenCV installation (analogous for BGR). (PR #263)
* Fixed `ReplaceElementwise` always sampling replacement per channel.
* Fixed an error in `draw_text()` due to arrays that could not be set to writeable after drawing the text via PIL.
* Fixed errors in docstring of `parameters.Subtract`.
* Fixed a division by zero bug in `angle_between_vectors()`.
* Augmentation of empty `KeypointsOnImage` instances
* Fixed `Rot90` not changing `KeypointsOnImage.shape` if `.keypoints` was empty.
* Fixed `Affine` not changing `KeypointsOnImage.shape` if `.keypoints` was empty.
* Fixed `PerspectiveTransform` not changing `KeypointsOnImage.shape` if `.keypoints` was empty.
* Fixed `Resize` not changing `KeypointsOnImage.shape` if `.keypoints` was empty.
* Fixed `CropAndPad` not changing `KeypointsOnImage.shape` if `.keypoints` was empty. (Same for `Crop`, `Pad`.)
* Fixed `PadToFixedSize` not changing `KeypointsOnImage.shape` if `.keypoints` was empty.
* Fixed `CropToFixedSize` not changing `KeypointsOnImage.shape` if `.keypoints` was empty.
* Fixed `KeepSizeByResize` not changing `KeypointsOnImage.shape` if `.keypoints` was empty.
* Fixed `Affine` heatmap augmentation producing arrays with values outside the range `[0.0, 1.0]` when `order` was set to `3`.
* Fixed `PiecewiseAffine` heatmap augmentation producing arrays with values outside the range `[0.0, 1.0]` when `order` was set to `3`.
* Fixed assert in `SegmentationMapOnImage` falsely checking if max class index is `<= nb_classes` instead of `< nb_classes`.
* Fixed an issue in `dtypes.clip_to_value_range_()` and `dtypes.restore_dtypes_()` causing errors when clip value range exceeded array dtype's value range.
* Fixed an issue in `dtypes.clip_to_value_range_()` and `dtypes.restore_dtypes_()` when the input array was scalar, i.e. had shape `()`.
* Fixed a Permission Denied error when using `JpegCompression` on windows (possibly also affected other systems). #297
================================================
FILE: changelogs/v0.3.0.summary.md
================================================
# 0.3.0 - Summary Of Changes
## Improved Segmentation Map Augmentation (#302)
The segmentation map augmentation was previously previously a wrapper
around heatmap augmentation. This patch introduces independent methods
for segmentation map augmentation. This makes the augmentation of such
inputs faster and more memory efficient. The internal representation (int
instead of floats) also becomes more intuitive.
This improvement leads to some **breaking changes**. To adapt to the new
version, the following steps should be sufficient for most users:
* Rename all calls of `SegmentationMapOnImage` to `SegmentationMapsOnImage`
(Map -> Maps).
* Rename all calls of `SegmentationMapsOnImage.get_arr_int()` to
`SegmentationMapsOnImage.get_arr()`.
* Remove the argument `nb_classes` from all calls of `SegmentationMapsOnImage`.
* Remove the argument `background_threshold` from all
calls as it is no longer supported.
* Remove the argument `draw_foreground_mask` from all calls of
`SegmentationMapsOnImage.draw_on_image()` as it is no longer supported.
* Ensure that the input array to `SegmentationMapsOnImage` is always an
int-like (int, uint or bool). Float arrays are now deprecated.
* Adapt all calls `SegmentationMapsOnImage.draw()` and
`SegmentationMapsOnImage.draw_on_image()`, as both of these now return a
list of drawn images instead of a single array. (For a segmentation map
array of shape `(H,W,C)` they return `C` drawn images. In most cases `C=1`,
so simply call `draw()[0]` or `draw_on_image()[0]`.)
* Ensure that if `SegmentationMapsOnImage.arr` is accessed anywhere, the
respective code can handle the new `int32` `(H,W,#maps)` array form.
Previously, it was `float32` and the channel-axis had the same size as the
max class id (+1) that could appear in the map.
* Ensure that calls of `.augment()` or `()` that
provide segmentation maps as numpy arrays (i.e. bypassing
`SegmentationMapsOnImage`) use the shape `(N,H,W,#maps)` as
`(N,H,W)` is no longer supported.
## New RNG System (#375, #408)
numpy 1.17 introduces a new API for random number generation. This patch
adapts `imgaug` to automatically use the new API if it is available and
fall back to the old one otherwise. To achieve that, the module
`imgaug.random` is introduced, containing the new standard random number
generator `imgaug.random.RNG`. You can create a new RNG using a seed value
via `RNG(seed)` and it will take care of the rest. It supports all sampling
functions that `numpy.random.RandomState` and `numpy.random.Generator`
support. This new random number generator is now supposed to be used
wherever previously `numpy.random.RandomState` would have been used.
(For most users, this shouldn't change anything. Integer seeds are
still supported. If you used `RandomState` anywhere, that is also still
supported.)
**Breaking changes** related to this patch:
* imgaug now uses a different seed at each run of the library. Previously,
a fixed seed was used for each run, leading to the same agumentations. That
confused some users as it differed from numpy's behaviour.
The new "dynamic" seed is derived from numpy's seed and hence seeding numpy
will also lead to imgaug being seeded. (It is not recommended to rely on
that behaviour as it might be changed in the future. Use
`imgaug.random.seed()` to set a custom seed.)
* The constants `imgaug.SEED_MIN_VALUE` and `imgaug.SEED_MAX_VALUE` were
removed. They are now in `imgaug.random`.
* The constant `imgaug.CURRENT_RANDOM_STATE` was removed.
Use `imgaug.random.get_global_rng()` instead.
## Other Changes Related to numpy 1.17 (#302)
numpy 1.17 uses a new implementation of `clip()`, which turns `int64` values
into `float64` values. As a result, it is no longer safe to use `int64` in
many augmenters and other functions/methods and hence these inputs are now
rejected. This affects at least `ReplaceElementwise` and thereby `Dropout`,
`CoarseDropout`, `Salt`, `Pepper`, `SaltAndPepper`, `CoarseSalt`,
`CoarsePepper` and `CoarseSaltAndPepper`. See the ReadTheDocs documentation
page about dtype support for more details.
In relation to this change, parameters in `imgaug.parameters` that previously
returned `int64` were modified to now return `int32` instead. Analogously,
`float64` results were changed to `float32`.
## New Augmenters
The following new augmenters were added to the library:
**Canny edge detection** (#316):
* `imgaug.augmenters.edges.Canny`. Performs canny edge detection and colorizes
the resulting binary image in random ways.
**Pooling** (#317):
* `imgaug.augmenters.edges.AveragePooling`. Performs average pooling using a
given kernel size. Very similar to `AverageBlur`.
* `imgaug.augmenters.edges.MaxPooling`. Performs maximum pooling using a
given kernel size.
* `imgaug.augmenters.edges.MinPooling`. Analogous.
* `imgaug.augmenters.edges.MedianPooling`. Analogous.
**Hue and Saturation** (#210, #319):
* `imgaug.augmenters.color.WithHueAndSaturation`. Apply child augmenters to
images in `HSV` colorspace. Automatically accounts for the hue being in
angular representation.
* `imgaug.augmenters.color.AddToHue`. Adds a defined value to the hue of each
pixel in input images.
* `imgaug.augmenters.color.AddToSaturation`. Adds a defined value to the
saturation of each pixel in input images.
* `imgaug.augmenters.color.MultiplyHueAndSaturation`. Multiplies the hue and/or
saturation of all pixels in input images.
* `imgaug.augmenters.color.MultiplyHue`. Analogous, affects always only the hue.
* `imgaug.augmenters.color.MultiplySaturation`. Analogous, affects always only
the saturation.
**Color Quantization** (#347):
* `imgaug.augmenters.color.UniformColorQuantization`. Uniformly splits all
possible colors into `N` different ones, then finds for each pixel in an
image among the `N` colors the most similar one and replaces that pixel's
color with the quantized color.
* `imgaug.augmenters.color.KMeansColorQuantization`. Groups all colors in an
each into `N` different ones using k-Means clustering. Then replaces each
pixel'S color, analogously to `UniformColorQuantization`.
**Voronoi** (#348):
* `imgaug.augmenters.segmentation.Voronoi`. Queries a point sampler to
generate a large number of `(x,y)` coordinates on an image. Each such
coordinate becomes a voronoi cell. All pixels within the voronoi cell
are replaced by their average color. (Similar to `Superpixels`, this
augmenter also supports to only replace `p%` of all cells with their
average color.)
* `imgaug.augmenters.segmentation.UniformVoronoi`. Shortcut to call `Voronoi`
with a uniform points sampler. That sampler places `N` points on an image
using uniform distributions (i.e. they are randomly spread over the image.)
* `imgaug.augmenters.segmentation.RegularGridVoronoi`. Shortcut to call
`Voronoi` with a regular grid points sampler. That points sampler generates
coordinate on a regular grid with `H` rows and `W` cols. Some of these points
can be randomly dropped to generate a less regular pattern.
* `imgaug.augmenters.segmentation.RelativeRegularGridVoronoi`. Same as
`RegularGridVoronoi`, but instead of using absolute numbers for `H` and `W`,
they are defined as relative amounts w.r.t. image shapes, leading to more
rows/cols on larger images.
## New Augmentation Functions
One of the long term goals of the library is to move as much augmentation
logic as possible out of `Augmenter` instances and into functions. This
patch therefore adds several new augmentation functions:
* `imgaug.min_pool()`. #369
* `imgaug.median_pool()`. #369
* `augmenters.segmentation.segment_voronoi()`. #348
* `augmenters.flip.fliplr()`. #385
* `augmenters.flip.flipud()`. #385
* `augmenters.color.change_colorspace_()`. #409
* `augmenters.color.change_colorspace_batch_()`. #409
* `augmenters.arithmetic.add_scalar()`. #411
* `augmenters.arithmetic.add_elementwise()`. #411
* `augmenters.arithmetic.replace_elementwise_()`. #411
* `augmenters.arithmetic.compress_jpg()`. #411
## Colorspace Changes (#409)
The color space naming within the library had become rather messy in the
past as there were many colorspace-related augmenters, with some of them
not using constants for colorspace names/IDs and others defining their own
ones. This patch introduces a unified colorspace naming system for which the
following constants were added:
* `imgaug.CSPACE_RGB`
* `imgaug.CSPACE_BGR`
* `imgaug.CSPACE_GRAY`
* `imgaug.CSPACE_CIE`
* `imgaug.CSPACE_YCrCb`
* `imgaug.CSPACE_HSV`
* `imgaug.CSPACE_HLS`
* `imgaug.CSPACE_Lab`
* `imgaug.CSPACE_Luv`
* `imgaug.CSPACE_YUV`
* `imgaug.CSPACE_ALL`
All colorspace-related augmenters should now support these constants.
Additionally, support for rarely used colorspaces -- mainly `CIE`, `YCrCb`,
`Luv` and `YUV` -- was previously unverified or non-existent. These colorspaces
are now tested for the underlying transformation functions and should be
supported by most colorspace-related augmenters. (Some augmenters may still
define their own subset of actually sensible colorspaces and only accept
these.)
## Setting limits on memory usage of background augmentation (#305, #417)
The methods `imap_batches()` and `imap_batches_unordered()` of
`imgaug.multicore.Pool` have now the new argument `output_buffer_size`.
The argument set the maximum number of batches that may be handled anywhere
in the augmentation pipeline at a given time (i.e. in the steps "loaded and
waiting", "in augmentation" or "augmented and waiting"). It denotes the
*total* number of batches over *all* processes. Setting this argument to
an integer value avoids situations where `Pool` eats up all the available
memory due to the data loading and augmentation running faster than the
training.
`Augmenter.augment_batches()` now uses a default value of `10*C`
for `output_buffer_size`, where `C` is the number of available logical CPU
cores.
## Performance Related Changes
The algorithms for `Fliplr` and `Flipud` were reworked to be as fast as
possible. In practice this should have no noticeable effects as both augmenters
were already very fast. (#385)
Furthermore, all assert statements within the library were changed from
`do_assert()` to standard `assert` statements. This is a bit less secure
(as `assert` statements can be optimized away), but should have a small
positive impact on the performance. (#387)
Large parts of the library were also refactored to reduce code duplication
and decrease the complexity of many functions. This should make future
improvements easier, but is expected to have a very small negative impact on
the performance due to an increased number of function calls.
It is also expected that numpy 1.17 can make some operations slower. This
is because (a) creating and copying random number generaters has become slower
and (b) `clip()` overall seems to be slower.
## Improved Error Messages (#366, #367, #387)
imgaug uses quite many `assert` statements and other checks on input data
to fail early instead of late. This is supposed to improve usability, but that
goal was not always reached as many errors had no associated error
messages. This patch changes that. Now, all `assert` statements and other
checks have an associated error message. This should protect users from having
to wade through the library's code in order to understand the root cause of
errors.
## (Almost) All Augmenters Are Now Classes (#396)
Some augmenters were previously defined as functions returning other
augmenters with appropriate settings. This could lead to confusing effects,
where seemingly instantiating an augmenters would lead to the instantiation
of a completely different augmenter. Hence, most of these augmenters were
switched from functions to classes. (The classes are now inheriting from the
previously returned augmenters, i.e. `instanceof` checks should still work.)
This affects: `AdditiveGaussianNoise`, `AdditiveLaplaceNoise`,
`AdditivePoissonNoise`, `Dropout`, `CoarseDropout`, `ImpulseNoise`,
`SaltAndPepper`, `CoarseSaltAndPepper`, `Salt`, `CoarseSalt`, `Pepper`,
`CoarsePepper`, `SimplexNoiseAlpha`, `FrequencyNoiseAlpha`, `MotionBlur`,
`MultiplyHueAndSaturation`, `MultiplyHue`, `MultiplySaturation`, `AddToHue`,
`AddToSaturation`, `Grayscale`, `GammaContrast`, `SigmoidContrast`,
`LogContrast`, `LinearContrast`, `Sharpen`, `Emboss`, `EdgeDetect`,
`DirectedEdgeDetect`, `OneOf`, `AssertLambda`, `AssertShape`, `Pad`, `Crop`,
`Clouds`, `Fog` and `Snowflakes`.
Not yet switched are: `InColorspace` (deprecated),
`ContrastNormalization` (deprecated), `HorizontalFlip` (pure alias
for `Fliplr`), `VerticalFlip` (pure alias for `Flipud`)
and `Scale` (deprecated).
## Augmenters are now more robust towards unusual axis-sizes (#428, #433)
Feeding images with height and/or width of `0` or a channel axis of size `0`
into augmenters would previously often result in crashes. This was also the
case for input arrays with more than `512` channels. Some of these errors
also included segmentation faults or endlessly hanging programs. Most
augmenters and helper functions were modified to be more robust towards
such unusual inputs and will no longer crash.
It is still good practice to avoid such inputs. Note e.g. that some helper
functions -- like drawing routines -- may still crash. The unittests
corresponding to this change also only cover image data. Using other inputs,
e.g. segmentation maps, might still induce problems.
## Other New Functions
The following (public) functions were added to the library (not listing
functions that were already mentioned above):
* Added `imgaug.is_np_scalar()`. #366
* Added `dtypes.normalize_dtypes()`. #366
* Added `dtypes.normalize_dtype()`. #366
* Added `dtypes.change_dtypes_()`. #366
* Added `dtypes.change_dtype_()`. #366
* Added `dtypes.increase_itemsize_of_dtype()`. #366
* Added `imgaug.warn()` function. #367
* Added `imgaug.compute_paddings_to_reach_multiples_of()`. #369
* Added `imgaug.pad_to_multiples_of()`. #369
* Added `augmentables.utils.copy_augmentables`. #410
* Added `validation.convert_iterable_to_string_of_types()`. #413
* Added `validation.is_iterable_of()`. #413
* Added `validation.assert_is_iterable_of()`. #413
* Added `random.supports_new_rng_style()`. #375
* Added `random.get_global_rng()`. #375
* Added `random.seed()`. #375
* Added `random.normalize_generator()`. #375
* Added `random.normalize_generator_()`. #375
* Added `random.convert_seed_to_generator()`. #375
* Added `random.convert_seed_sequence_to_generator()`. #375
* Added `random.create_pseudo_random_generator_()`. #375
* Added `random.create_fully_random_generator()`. #375
* Added `random.generate_seed_()`. #375
* Added `random.generate_seeds_()`. #375
* Added `random.copy_generator()`. #375
* Added `random.copy_generator_unless_global_generator()`. #375
* Added `random.reset_generator_cache_()`. #375
* Added `random.derive_generator_()`. #375
* Added `random.derive_generators_()`. #375
* Added `random.get_generator_state()`. #375
* Added `random.set_generator_state_()`. #375
* Added `random.is_generator_equal_to()`. #375
* Added `random.advance_generator_()`. #375
* Added `random.polyfill_integers()`. #375
* Added `random.polyfill_random()`. #375
## Other New Classes and Interfaces
The following (public) classes were added (not listing classes that were
already mentioned above):
* Added `augmenters.edges.IBinaryImageColorizer`. #316
* Added `augmenters.edges.RandomColorsBinaryImageColorizer`. #316
* Added `augmenters.segmentation.IPointsSampler`. #348
* Added `augmenters.segmentation.RegularGridPointsSampler`. #348
* Added `augmenters.segmentation.RelativeRegularGridPointsSampler`. #348
* Added `augmenters.segmentation.DropoutPointsSampler`. #348
* Added `augmenters.segmentation.UniformPointsSampler`. #348
* Added `augmenters.segmentation.SubsamplingPointsSampler`. #348
* Added `testutils.ArgCopyingMagicMock`. #413
The image colorization is used for `Canny` to turn binary images into color
images.
The points samplers are currently used within `Voronoi`.
## Refactorings
Due to fast growth of the library in the past, a significant amount of messy
code had accumulated. To fix that, a lot of time was spend to refactor the code
throughout the whole library to reduce code duplication and improve the
general quality. This also included a rewrite of many outdated docstrings.
There is still quite some mess remaining, but the current state should make
it somewhat easier to add future improvements.
As part of the refactorings, a few humongously large unittests were also
split up into many smaller tests. The library has now around 3000
unique unittests (i.e. each unittest function is counted once, even it is
called many times with different parameters).
Related PRs:
* #302, #319, #328, #329, #330, #331, #332, #333, #334, #335, #336, #351,
#352, #353, #354, #355, #356, #359, #362, #366, #367, #368, #369, #389,
#397, #401, #402, #403, #407, #409, #410, #411, #413, #419
## Deprecated
The following functions/classes/arguments are now deprecated:
* Function `imgaug.augmenters.meta.clip_augmented_image_`.
Use `imgaug.dtypes.clip_()` or `numpy.clip()` instead. #398
* Function `imgaug.augmenters.meta.clip_augmented_image`.
Use `imgaug.dtypes.clip_()` or `numpy.clip()` instead. #398
* Function `imgaug.augmenters.meta.clip_augmented_images_`.
Use `imgaug.dtypes.clip_()` or `numpy.clip()` instead. #398
* Function `imgaug.augmenters.meta.clip_augmented_images`.
Use `imgaug.dtypes.clip_()` or `numpy.clip()` instead. #398
* Function `imgaug.normalize_random_state`.
Use `imgaug.random.normalize_generator` instead. #375
* Function `imgaug.current_random_state`.
Use `imgaug.random.get_global_rng` instead. #375
* Function `imgaug.new_random_state`.
Use class `imgaug.random.RNG` instead. #375
* Function `imgaug.dummy_random_state`.
Use `imgaug.random.RNG(1)` instead. #375
* Function `imgaug.copy_random_state`.
Use `imgaug.random.copy_generator` instead.
* Function `imgaug.derive_random_state`.
Use `imgaug.random.derive_generator_` instead. #375
* Function `imgaug.normalize_random_states`.
Use `imgaug.random.derive_generators_` instead. #375
* Function `imgaug.forward_random_state`.
Use `imgaug.random.advance_generator_` instead. #375
* Augmenter `imgaug.augmenters.arithmetic.ContrastNormalization`.
Use `imgaug.augmenters.contrast.LinearContrast` instead. #396
* Argument `X` in `imgaug.augmentables.kps.compute_geometric_median()`.
Use argument `points` instead. #402
* Argument `cval` in `imgaug.pool()`, `imgaug.avg_pool()` and
`imgaug.max_pool()`. Use `pad_cval` instead. #369
## Dependencies
The following changes were made to the dependencies of the library:
* Increased minimum version requirement for `scikit-image` to
`0.14.2`. #377, #399
* Changed dependency `opencv-python` to `opencv-python-headless`.
This should improve support for some system without GUIs. #324
* Added dependency `pytest-subtests` for the library's unittests. #366
## conda-forge
The library was added to `conda-forge` so that it can now be installed via
`conda install imgaug`. (The conda-forge channel must be added first,
see installation docs or README.) #320 #339
## Fixes
* Fixed an issue with `Polygon.clip_out_of_image()`,
which would lead to exceptions if a polygon had overlap with an image,
but not a single one of its points was inside that image plane.
* Fixed `multicore` methods falsely not accepting
`augmentables.batches.UnnormalizedBatch`.
* `Rot90` now uses subpixel-based coordinate remapping.
I.e. any coordinate `(x, y)` will be mapped to `(H-y, x)` for a rotation by
90deg.
Previously, an integer-based remapping to `(H-y-1, x)` was used.
Coordinates are e.g. used by keypoints, bounding boxes or polygons.
* `augmenters.arithmetic.Invert`
* [rarely breaking] If `min_value` and/or `max_value` arguments were
set, `uint64` is no longer a valid input array dtype for `Invert`.
This is due to a conversion to `float64` resulting in loss of resolution.
* Fixed `Invert` in rare cases restoring dtypes improperly.
* Fixed `dtypes.gate_dtypes()` crashing if the input was one or more numpy
scalars instead of numpy arrays or dtypes.
* Fixed `augmenters.geometric.PerspectiveTransform` producing invalid
polygons (more often with higher `scale` values). #338
* Fixed errors caused by `external/poly_point_isect_py2py3.py` related to
floating point inaccuracies (changed an epsilon from `1e-10` to `1e-4`,
rounded some floats). #338
* Fixed `Superpixels` breaking when a sampled `n_segments` was `<=0`.
`n_segments` is now treated as `1` in these cases.
* Fixed `ReplaceElementwise` both allowing and disallowing dtype `int64`. #346
* Fixed `BoundingBox.deepcopy()` creating only shallow copies of labels. #356
* Fixed `dtypes.change_dtypes_()` #366
* Fixed argument `round` being ignored if input images were a list.
* Fixed failure if input images were a list and dtypes a single numpy
dtype function.
* Fixed `dtypes.get_minimal_dtype()` failing if argument `arrays` contained
not *exactly* two items. #366
* Fixed calls of `CloudLayer.get_parameters()` resulting in errors. #309
* Fixed `SimplexNoiseAlpha` and `FrequencyNoiseAlpha` not handling
`sigmoid` argument correctly. #343
* Fixed `SnowflakesLayer` crashing for grayscale images. #345
* Fixed `Affine` heatmap augmentation crashing for arrays with more than
four channels and `order!=0`. #381
* Fixed an outdated error message in `Affine`. #381
* Fixed `Polygon.clip_out_of_image()` crashing if the intersection between
polygon and image plane was an edge or point. #382
* Fixed `Polygon.clip_out_of_image()` potentially failing for polygons
containing two or fewer points. #382
* Fixed `Polygon.is_out_of_image()` returning wrong values if the image plane
was fully contained inside the polygon with no intersection between the
image plane and the polygon edge. #382
* Fixed `Fliplr` and `Flipud` using for coordinate-based inputs and image-like
inputs slightly different conditions for when to actually apply
augmentations. #385
* Fixed `Convolve` using an overly restrictive check when validating inputs
for `matrix` w.r.t. whether they are callables. The check should now also
support class methods (and possibly various other callables). #407
* Fixed `CropAndPad`, `Pad` and `PadToFixedSize` still clipping `cval` samples
to the `uint8`. They now clip to the input array's dtype's value range. #407
* Fixed `WithColorspace` not propagating polygons to child augmenters. #409
* Fixed `WithHueAndSaturation` not propagating segmentation maps and polygons
to child augmenters. #409
* Fixed `AlphaElementwise` to blend coordinates (for keypoints, polygons,
line strings) on a point-by-point basis following the image's average
alpha value in the sampled alpha mask of the point's coordinate.
Previously, the average over the whole mask was used and then either all
points of the first branch or all of the second branch were used as the
augmentation output. This also affects `SimplexNoiseAlpha` and
`FrequencyNoiseAlpha`. #410
* Fixed many augmenters and helper functions producing errors if the height,
width and/or channels of input arrays were exactly `0` or the channels
were `>512`. See further above for more details. #433
* Fixed `Rot90` not supporting `imgaug.ALL`. #434
* Fixed `PiecewiseAffine` possibly generating samples for non-image data
when using `absolute_scale=True` that were not well aligned with the
corresponding images. #437
================================================
FILE: changelogs/v0.4.0.summary.md
================================================
# Table of Contents
1. [Overview](#overview)
2. [Example Images](#example_images)
3. [Mixed-Category Patches](#mixed_category_patches)
4. [Added](#added)
5. [Changed](#changed)
6. [Refactored](#refactored)
7. [Fixed](#fixed)
# Overview
Release `0.4.0` focused mainly on adding new augmenters and improving
the internal augmentation "backend".
The following augmenters were added (see the
[overview](https://imgaug.readthedocs.io/en/latest/source/overview_of_augmenters.html)
docs for more details):
* `ChangeColorTemperature`: Gives images a red, orange or blue touch.
* New Brightness augmenters: `WithBrightnessChannels`,
`MultiplyAndAddToBrightness`, `MultiplyBrightness`, `AddToBrightness`.
* New Dropout augmenters: `Dropout2d`, `TotalDropout`.
* `RemoveSaturation`: Decreases the saturation of colors. Effects are similar
to `Grayscale`.
* `Cartoon`: Applies a cartoon-style to images (classical / not-learned).
* `MeanShiftBlur`: Blurs images using a mean-shift clustering method.
(Note: Very slow.)
* `Jigsaw`: Splits the image into rectangular cells and randomly switches
some pairs of neighbouring cells. (Note: Does not support bounding boxes,
polygons and line strings.)
* `WithPolarWarping`: Transforms images to polar coordinate space and applies
child augmenters there.
* `SaveDebugImageEveryNBatches`: Generates and saves at every `N`-th batch a
debug image visualizing all inputs within the batch. Useful to gauge
strength and effects of augmentations and quickly spot errors in ground truth
data (e.g. misaligned bounding boxes).
* `Cutout`: Removes rectangular subregions of images. Has some similarity with
`CoarseDropout`.
* `Rain` and `RainLayer`: Adds rain-like effects to images.
* `RandAugment`: Combination of multiple augmenters. Similar to the paper
description. (Note: Can currently only augment images.)
* `Identity`: Same as `Noop`. Does nothing.
* `UniformColorQuantizationToNBits`: Quantizes each image array component down
to `N` bits. Similar to `UniformColorQuantization`. Has the alias
`Posterize`.
* `Solarize`: Invert with threshold.
* `RemoveCBAsByOutOfImageFraction`, `ClipCBAsToImagePlanes`: Augmenters to
remove or clip coordinate-based augmentables, e.g. bounding boxes
* More blend augmenters:
* `BlendAlphaMask`: Uses batch-wise generated masks for alpha-blending.
* `BlendAlphaSomeColors`: Alpha-blends only within image regions having
specific randomly chosen colors.
* `BlendAlphaSegMapClassIds`: Alpha-blends only within image regions having
specific class ids in segmentation maps.
* `BlendAlphaBoundingBoxes`: Alpha-blends only within image regions covered
by bounding boxes having specific labels.
* `BlendAlphaHorizontalLinearGradient`: Alpha-blends using horizontal
linear gradients.
* `BlendAlphaVerticalLinearGradient`: Analogous.
* `BlendAlphaRegularGrid`: Places a regular grid on each image and
samples one alpha value per grid cell. Can be used e.g. to achieve
coarse dropout.
* `BlendAlphaCheckerboard`: Places also a regular grid on each image,
but neighbouring cells use alpha values that are inverse to each other.
* Shortcuts for `Affine`: `ScaleX`, `ScaleY`, `TranslateX`, `TranslateY`,
`Rotate`, `ShearX`, `ShearY`.
* Several new crop and pad augmenters: `CenterCropToFixedSize`,
`CenterPadToFixedSize`, `CropToMultiplesOf`, `CenterCropToMultiplesOf`,
`PadToMultiplesOf`, `CenterPadToMultiplesOf`, `CropToPowersOf`,
`CenterCropToPowersOf`, `PadToPowersOf`, `CenterPadToPowersOf`,
`CropToAspectRatio`, `CenterCropToAspectRatio`, `PadToAspectRatio`,
`CenterPadToAspectRatio`, `PadToSquare`, `CenterPadToSquare`,
`CropToSquare`, `CenterCropToSquare`.
* Wrappers around `imagecorruptions` package (verified to have identical
outputs):
`GaussianNoise`, `ShotNoise`, `ImpulseNoise`, `SpeckleNoise`,
`GaussianBlur`, `GlassBlur`, `DefocusBlur`, `MotionBlur`, `ZoomBlur`,
`Fog`, `Frost`, `Snow`, `Spatter`, `Contrast`, `Brightness`, `Saturate`,
`JpegCompression`, `Pixelate`, `ElasticTransform`.
The augmenters are accessible via `iaa.imgcorruptlike.`.
* Wrappers around `PIL` functions (verified to have identical outputs):
`Solarize`, `Posterize`, `Equalize`, `Autocontrast`, `EnhanceColor`,
`EnhanceContrast`, `EnhanceBrightness`, `EnhanceSharpness`, `FilterBlur`,
`FilterSmooth`, `FilterSmoothMore`, `FilterEdgeEnhance`,
`FilterEdgeEnhanceMore` `FilterFindEdges`, `FilterContour`,
`FilterEmboss`, `FilterSharpen`, `FilterDetail`, `Affine`.
The augmenters are accessible via `iaa.pillike.`.
Aside from these new augmenters, the following major changes were made:
* Bounding boxes and line strings have now native augmentation methods.
Previously, they were converted to keypoints at the start of the
augmentation, which meant that child augmenters were unable to use
augmentation routines geared towards these two input types as they would
merely see a bunch of keypoints.
* Augmentation happens now batchwise. Previously it was done
input type-wise. This change should improve performance for batches with
different types of inputs by re-using computation results for multiple
inputs. It also makes the library more flexible.
* Improved the default parameters of augmenters. Most of them will now
produce medium-strength augmentations when instantiated without and
parameters. E.g. `CoarseDropout()` will now produce decent augmentations
instead of doing nothing. When using default parameters,
`Fliplr()` and `Flipud()` will always flip (p=100%).
`TotalDropout()` will always drop everything (p=100%).
`Grayscale()` and `RemoveSaturation()` will always fully grayscale/desaturate.
`Rot90()` will always rotate once (clockwise). `Invert()` will always
invert all components (p=100%).
* Reworked the standard parameters shared by all augmenters.
`random_state` was renamed to `seed`, e.g. `Affine(..., seed=1)`
is now valid. The parameter `deterministic` is now deprecated.
* Many methods were added to augmentables, e.g. `BoundingBoxesOnImage`
now supports index-based access (`bbs[0]` instead
of `bbs.bounding_boxes[0]`).
* The bounding box drawing methods now also draw each BB's label.
* All augmenters are now tested to be pickle-able without errors.
* The library is now compatible with numpy 1.18 and python 3.8.
* This release fixes two significant bugs in `Affine` that could lead
to unaligned outputs. It also fixes significant bugs related to
bounding box augmentation and various other issues. The update
is recommended. There are now around 5000 unique tests.
# Example Images
## Brightness
Three new brightness-related augmenters are introduced. The example below
shows `AddToBrightness`. First image is the input, the others show
`AddToBrightness(-100)` to `AddToBrightness(100)`.
## Cartoon
A new cartoon style filter is introduced, shown below. Each row starts with
the input image.
## ChangeColorTemperature
The color temperature of images can now be modified. The example below
shows `ChangeColorTemperature(kelvin=1000)` to
`ChangeColorTemperature(kelvin=5000)`, with the first image being the input.
## Cutout
Cutout is added to the library. The first row shows the hyperparameters that
were used in the corresponding paper. The second row shows two cutout
iterations per image, using intensity values, random RGB values and gaussian
noise to fill in the pixels. First image in each row is the input.
## Dropout2d and TotalDropout
Two new dropout augmenters, `Dropout2d` and `TotalDropout`, are added.
The example below shows `Dropout2d`. First image is the input.
## Jigsaw
A jigsaw puzzle augmenter is added. The first row below shows its effects
using a grid size of `5x5`. The second row shows `10x10`.
First image in each row is the input.
## MeanShiftBlur
A mean shift-based blur augmenter is added. First image below shows the input,
followed by `MeanShiftBlur(5.0)` to `MeanShiftBlur(40.0)`.
## Posterize
The example below shows `Posterize` (aka `UniformQuantizationToNBits`)
with `n_bits=8` to `n_bits=1`. First image is the input.
## Solarize
The example below shows `Solarize`, which is the same as `Invert` with a
threshold. First image is the input.
## Rain
The example below shows the new `Rain` augmenter. First image is the input.
## RandAugment
This release adds an implementation of `RandAugment` the following example
shows `RandAugment(n=2, m=20)`. First image is the input.
## WithPolarWarping
The example below shows `WithPolarWarping()` in combination with
`CropAndPad` (first row), `Affine` (second row) and
`AveragePooling` (third row). First image in each row is the input.
The augmenter supports all input types, but bounding boxes and polygons
should be used with caution. (Bounding boxes, because they tend to produce
unintuitive results in combination with rotation-like augmentations.
Polygons, because they can become invalid under geometric augmentations
and will have to be repaired, which can easily mess them up.)
## imagecorruptions wrappers
Wrappers around the library `imagecorruptions` are added, which contains
augmentation methods introduced by `Hendrycks and Dietterich - Benchmarking
Neural Network Robustness to Common Corruptions and Surface Variations`.
The methods were used in some recent papers. The example below shows their
effects, always with `severity=3`.
## PIL wrappers
Various wrappers around popular `PIL` methods are added.
The image below shows in the first row `Autocontrast`, in the second
`EnhanceColor` (strength of `0.1` to `1.9`), the third
`EnhanceSharpness` (strength of `0.1` to `1.9`), the fourth shows
various convolution-based filters (`FilterBlur`, `FilterSmooth`,
`FilterEdgeEnhance`, `FilterFindEdges`, `FilterContour`, `FilterSharpen`,
`FilterDetail` -- in that order) and the fourth row shows `pillike.Affine`
with the top-left as the transformation origin.
The first image in each row is the input.
## More Blending Augmenters
Various new (alpha-)blending augmenters are introduced in this patch.
The following example makes use of a segmentation map in which all cars
are marked with a segmentation class id.
It uses roughly
`BlendAlphaSegMapClassIds(BlendAlphaSomeColors(AddToHueAndSaturation(...)))`
in order to modify some colors within the car classes.
Left is the input image, right is the output:
Note that `BlendAlphaSegMapClassIds` must be called with all inputs
at the same time, e.g. via `augmenters(images=..., segmentation_maps...)`.
This example changes the train color using
`BlendAlphaSegMapClassIds(AddToHueAndSaturation(...))`. The train has a
separate class in the segmentation map.
The next example applies blending to some non color-based augmenters. It uses
roughly `BlendAlphaSegMapClassIds(AdditiveGaussianNoise(...))` (left)
and `BlendAlphaSegMapClassIds(Emboss(...))` (right). The street has a separate
class in the segmentation map.
This example shows how blending can be used to achieve dropout effects.
It uses roughly `BlendAlphaRegularGrid(Multiply(0.0))` (left) and
`BlendAlphaCheckerboard(Multiply(0.0))` (right).
This example shows `BlendAlphaSomeColors(RemoveSaturation(1.0))`, applied
to a more colorful image:
This release also adds `BlendAlphaBoundingBoxes`,
`BlendAlphaHorizontalLinearGradient` and
`BlendAlphaVerticalLinearGradient`. These are not visualized here.
## SaveDebugImageEveryNBatches
A new debug helper -- `SaveDebugImageEveryNBatches` -- was added.
The example below shows one of its outputs for a batch containing images,
segmentation maps and bounding boxes.
Note that this augmenter must be called with all inputs at the same time,
e.g. via `augmenters(images=..., segmentation_maps...)` for image + segmap
inputs.
# Mixed-Category Patches
## Reworked Augmentation Methods [#451](https://github.com/aleju/imgaug/pull/451) [#566](https://github.com/aleju/imgaug/pull/566)
The internal backend of the library was changed so that augmentation now
happens batchwise instead of input-type-wise. Child augmenters still have
the option of using input-type-wise augmentation. All calls are now at some
point routed through `Augmenter.augment_batch_()` and child augmenters are
expected to implement `_augment_batch_()`. This change allows to re-use
information between different input types within the same batch, which in
turn improves performance and extends the space of possible augmentations.
Note: It is now recommended to use a batch-wise augmentation call. I.e.
use `.augment_batch_()` or `.augment()` or `.__call__()`. These calls provide
all inputs of a batch at the same time and several of the new augmenters now
explicitly require that (e.g. `BlendAlphaBoundingBoxes`). Example:
```python
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
images = [np.zeros((32, 32, 3), dtype=np.uint8),
np.zeros((64, 64, 3), dtype=np.uint8)]
bbs = [
[ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)],
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=2, y1=3, x2=4, y2=5)],
]
bbsois = [ia.BoundingBoxesOnImage(bbs[0], shape=images[0]),
ia.BoundingBoxesOnImage(bbs[1], shape=images[1])]
aug = iaa.Affine(rotate=(-30, 30))
# No longer recommended:
aug_det = aug.to_deterministic()
images_aug = aug_det.augment_images(images)
bbsois_aug = aug_det.augment_bounding_boxes(bbsois)
# Now recommended:
images_aug, bbs_aug = aug(images=images, bounding_boxes=bbs)
```
* Added methods:
* `augmentables.batches.Batch.to_normalized_batch()`.
* `augmentables.batches.Batch.get_augmentables()`.
* `augmentables.batches.UnnormalizedBatch.get_augmentables()`.
* `augmentables.batches.Batch.get_augmentable_names()`.
* `augmentables.batches.UnnormalizedBatch.get_augmentable_names()`.
* `augmentables.batches.Batch.to_batch_in_augmentation()`.
* `augmentables.batches.Batch.fill_from_batch_in_augmentation_()`.
* `augmentables.batches.UnnormalizedBatch.fill_from_augmented_normalized_batch()`.
* `augmenters.meta.Augmenter.augment_batch_()`, ,
similar to `augment_batch()`, but explicitly works in-place and has a
`parent` parameter.
* `augmenters.meta.Augmenter._augment_batch_()`.
* `augmentables.polys.recover_psois_()`.
* `augmentables.utils.convert_cbaois_to_kpsois()`.
* `augmentables.utils.invert_convert_cbaois_to_kpsois_()`.
* `augmentables.utils.deepcopy_fast()`.
* `augmentables.bbs.BoundingBox.from_point_soup()`.
* `augmentables.bbs.BoundingBoxesOnImages.from_point_soups()`.
* Added method `to_xy_array()` to:
* `augmentables.bbs.BoundingBoxesOnImage`.
* `augmentables.polys.PolygonsOnImage`.
* `augmentables.lines.LineStringsOnImage`.
* Added methods `to_keypoints_on_image()`,
`invert_to_keypoints_on_image_()` and `fill_from_xy_array_()` to:
* `augmentables.kps.KeypointsOnImage`.
* `augmentables.bbs.BoundingBoxesOnImage`.
* `augmentables.polys.PolygonsOnImage`.
* `augmentables.lines.LineStringsOnImage`.
* Added classes:
* `testutils.TemporaryDirectory` (context)
* Changed:
* Changed the following methods to be thin wrappers around
`augment_batch_()`:
* `augmenters.meta.Augmenter.augment_images()`
* `augmenters.meta.Augmenter.augment_heatmaps()`.
* `augmenters.meta.Augmenter.augment_segmentation_maps()`.
* `augmenters.meta.Augmenter.augment_keypoints()`.
* `augmenters.meta.Augmenter.augment_bounding_boxes()`.
* `augmenters.meta.Augmenter.augment_polygons()`.
* `augmenters.meta.Augmenter.augment_line_strings()`.
* Changed `augment_image()`, `augment_images()`, `augment_heatmaps()`,
`augment_segmentation_maps()`, `augment_keypoints()`,
`augment_bounding_boxes()`, `augment_polygons()` and
`augment_line_strings()` to return `None` inputs without change.
Previously they resulted in an exception. This is more consistent with
the behaviour in the other `augment_*` methods.
* Changed `augment_images()` to no longer be abstract. It defaults
to not changing the input images.
* Changed `imgaug.augmentables.BoundingBoxesOnImage.from_xyxy_array()`
to also accept `(N, 2, 2)` arrays instead of only `(N, 4)`.
Deprecated:
* Deprecated `imgaug.augmenters.meta.Augmenter.augment_batch()`.
Use `.augment_batch_()` instead.
Refactored:
* Refactored most augmenters to use single `_augment_batch_()` method.
Other changes:
* Added validation of input arguments to `KeypointsOnImage.from_xy_array()`.
* Improved validation of input arguments to
`BoundingBoxesOnImage.from_xyxy_array()`.
## Reworked Quantization [#467](https://github.com/aleju/imgaug/pull/467)
This patch reworked the quantization routines to also support quantization
to `N` bits instead of `N` colors in a way that is similar to posterization
in `PIL`. The patch added corresponding `UniformColorQuantizationToNBits`
and `Posterize` augmenters, as well as a `quantize_uniform_to_n_bits()`
function.
* Added classes:
* `augmenters.color.UniformColorQuantizationToNBits`.
* `augmenters.color.Posterize` (alias of `UniformColorQuantizationToNBits`).
* Added functions:
* `augmenters.color.quantize_uniform_()`, the in-place
version of `quantize_uniform()`.
* `augmenters.color.quantize_uniform_to_n_bits()`.
* `augmenters.color.quantize_uniform_to_n_bits_()`.
* `augmenters.color.posterize()`, an alias of
`quantize_uniform_to_n_bits()` that produces the same outputs as
`PIL.ImageOps.posterize()`.
* Added parameters:
* Added `to_bin_centers=True` to `quantize_uniform()`, controling
whether each bin `(a, b)` should be quantized to `a + (b-a)/2` or `a`.
* Deprecated:
* Renamed `imgaug.augmenters.color.quantize_colors_uniform(image, n_colors)`
to `imgaug.augmenters.color.quantize_uniform(arr, nb_bins)`. The old name
is now deprecated.
* Renamed `imgaug.augmenters.color.quantize_colors_kmeans(image, n_colors)`
to `imgaug.augmenters.color.quantize_kmeans(arr, nb_clusters)`. The old
name is now deprecated.
* Other changes:
* Improved performance of `quantize_uniform()` by roughly 10x (small images
around 64x64) to 100x (large images around 1024x1024). This also affects
`UniformColorQuantization`.
* Improved performance of `UniformColorQuantization` by using more in-place
functions.
* Fixed:
* Fixed `quantize_uniform()` producing wrong outputs for non-contiguous
arrays.
## Improved Invert [#469](https://github.com/aleju/imgaug/pull/469)
Added thresholds to `Invert` and the corresponding functions. This enables
solarization (inversion with thresholds). The patch also added
a corresponding `Solarize` augmenter and two solarization functions.
* Added augmenter `imgaug.augmenters.Solarize`, a wrapper around `Invert`.
* Added function `imgaug.augmenters.arithmetic.solarize()`, a wrapper around
`solarize_()`.
* Added function `imgaug.augmenters.arithmetic.solarize_()`, a wrapper around
`invert_()`.
* Added function `imgaug.augmenters.arithmetic.invert_()`, an in-place version
of `imgaug.augmenters.arithmetic.invert()`.
* Added parameters `threshold` and `invert_above_threshold` to
`imgaug.augmenters.arithmetic.invert()`
* Added parameters `threshold` and `invert_above_threshold` to
`imgaug.augmenters.arithmetic.Invert`.
* Improved performance of `imgaug.augmenters.arithmetic.invert()` and
`imgaug.augmenters.arithmetic.Invert` for `uint8` images.
## All Augmenters are now Pickle-able [#493](https://github.com/aleju/imgaug/pull/493) [#575](https://github.com/aleju/imgaug/pull/575)
Ensured that all augmenters can be pickled and un-pickled without errors.
* Added function `imgaug.testutils.runtest_pickleable_uint8_img()`.
* Fixed `imgaug.augmenters.blur.MotionBlur` not being pickle-able.
* Fixed `imgaug.augmenters.meta.AssertLambda` not being pickle-able.
* Fixed `imgaug.augmenters.meta.AssertShape` not being pickle-able.
* Fixed `imgaug.augmenters.color.MultiplyHueAndSaturation` not supporting
all standard RNG datatypes for `random_state`.
## Extended Cropping and Padding Augmenters [#459](https://github.com/aleju/imgaug/pull/459)
This patch extended the cropping and padding augmenters. It added
augmenters that crop/pad towards multiples of values (e.g. crop the
width until it is a multiple of `2`), towards powers of values (e.g.
crop the width until it is one of `1, 2, 4, 8, 16, ...`), towards
an aspect ratio (crop the width or height until `width/height = 2.0`) or
towards a squared size (e.g. crop the width or height until they are equal).
These augmenters are wrappers around `CropToFixedSize` and
`PadToFixedSize`. All `*FixedSize` augmenters also have now corresponding
`Center*ToFixedSize` aliases, e.g. `CenterCropToPowersOf`. These
are equivalent to using `position="center"`, e.g. `CenterCropToPowersOf`
is equivalent to `CropToPowersOf(..., position="center")`.
The following functions were moved. Their old names are now deprecated.
* Moved `imgaug.imgaug.pad` to `imgaug.augmenters.size.pad`
* Moved `imgaug.imgaug.pad_to_aspect_ratio` to
`imgaug.augmenters.size.pad_to_aspect_ratio`.
* Moved `imgaug.imgaug.pad_to_multiples_of` to
`imgaug.augmenters.size.pad_to_multiples_of`.
* Moved `imgaug.imgaug.compute_paddings_for_aspect_ratio` to
`imgaug.augmenters.size.compute_paddings_to_reach_aspect_ratio`.
* Moved `imgaug.imgaug.compute_paddings_to_reach_multiples_of`
to `imgaug.augmenters.size.compute_paddings_to_reach_multiples_of`.
The following augmenters were added:
* Added augmenter `CenterCropToFixedSize`.
* Added augmenter `CenterPadToFixedSize`.
* Added augmenter `CropToMultiplesOf`.
* Added augmenter `CenterCropToMultiplesOf`.
* Added augmenter `PadToMultiplesOf`.
* Added augmenter `CenterPadToMultiplesOf`.
* Added augmenter `CropToPowersOf`.
* Added augmenter `CenterCropToPowersOf`.
* Added augmenter `PadToPowersOf`.
* Added augmenter `CenterPadToPowersOf`.
* Added augmenter `CropToAspectRatio`.
* Added augmenter `CenterCropToAspectRatio`.
* Added augmenter `PadToAspectRatio`.
* Added augmenter `CenterPadToAspectRatio`.
* Added augmenter `PadToSquare`.
* Added augmenter `CenterPadToSquare`.
* Added augmenter `CropToSquare`.
* Added augmenter `CenterCropToSquare`.
All `Center` augmenters are wrappers around `` with parameter
`position="center"`.
Added functions:
* Added function
`imgaug.augmenters.size.compute_croppings_to_reach_aspect_ratio()`.
* Added function
`imgaug.augmenters.size.compute_croppings_to_reach_multiples_of()`.
* Added function
`imgaug.augmenters.size.compute_croppings_to_reach_powers_of()`.
* Added function
`imgaug.augmenters.size.compute_paddings_to_reach_powers_of()`.
Other changes:
* Extended augmenter `CropToFixedSize` to support `height` and/or `width`
parameters to be `None`, in which case the respective axis is not changed.
* Extended augmenter `PadToFixedSize` to support `height` and/or `width`
parameters to be `None`, in which case the respective axis is not changed.
* [rarely breaking] Changed `CropToFixedSize.get_parameters()` to also
return the `height` and `width` values.
* [rarely breaking] Changed `PadToFixedSize.get_parameters()` to also
return the `height` and `width` values.
* [rarely breaking] Changed the order of parameters returned by
`PadToFixedSize.get_parameters()` to match the order in
`PadToFixedSize.__init__()`
* Changed `PadToFixedSize` to prefer padding the right side over the left side
and the bottom side over the top side. E.g. if using a center pad and
`3` columns have to be padded, it will pad `1` on the left and `2` on the
right. Previously it was the other way round. This was changed to establish
more consistency with the various other pad and crop methods.
* Changed the projection of pad/crop values between images and non-images
to make the behaviour slightly more accurate in fringe cases.
* Improved behaviour of function
`imgaug.augmenters.size.compute_paddings_for_aspect_ratio()` for zero-sized
axes.
* Changed function `imgaug.augmenters.size.compute_paddings_for_aspect_ratio()`
to also support shape tuples instead of only ndarrays.
* Changed function
`imgaug.augmenters.size.compute_paddings_to_reach_multiples_of()`
to also support shape tuples instead of only ndarrays.
Fixes:
* Fixed a formatting error in an error message of
`compute_paddings_to_reach_multiples_of()`.
## More Choices for Image Blending [#462](https://github.com/aleju/imgaug/pull/462) [#556](https://github.com/aleju/imgaug/pull/556)
The available augmenters for alpha-blending of images were
significantly extended. There are now new blending
augmenters available to alpha-blend acoording to:
* Some randomly chosen colors. (`BlendAlphaSomeColors`)
* Linear gradients. (`BlendAlphaHorizontalLinearGradient`,
`BlendAlphaVerticalLinearGradient`)
* Regular grids and checkerboard patterns. (`BlendAlphaRegularGrid`,
`BlendAlphaCheckerboard`)
* Only at locations that overlap with specific segmentation class
IDs (or the inverse of that). (`BlendAlphaSegMapClassIds`)
* Only within bounding boxes with specific labels (or the inverse
of that). (`BlendAlphaBoundingBoxes`)
This allows to e.g. randomly remove some colors while leaving
other colors unchanged (`BlendAlphaSomeColors(Grayscale(1.0))`),
to change the color of some objects
(`BlendAlphaSegMapClassIds(AddToHue((-256, 256)))`), to add
cloud-patterns only to the top of images
(`BlendAlphaVerticalLinearGradient(Clouds())`) or to apply
augmenters in some coarse rectangular areas (e.g.
`BlendAlphaRegularGrid(Multiply(0.0))` to achieve a similar
effect to `CoarseDropout` or
`BlendAlphaRegularGrid(AveragePooling(8))` to pool in equally
coarse image sub-regions).
Other mask-based alpha blending techniques can be achieved by
subclassing `IBatchwiseMaskGenerator` and providing an
instance of such a class to `BlendAlphaMask`.
This patch also changes the naming of the blending augmenters
as follows:
* `Alpha` -> `BlendAlpha`
* `AlphaElementwise` -> `BlendAlphaElementwise`
* `SimplexNoiseAlpha` -> `BlendAlphaSimplexNoise`
* `FrequencyNoiseAlpha` -> `BlendAlphaFrequencyNoise`
The old names are now deprecated.
Furthermore, the parameters `first` and `second`, which were
used by all blending augmenters, have now the names `foreground`
and `background`.
List of changes:
* Added `imgaug.augmenters.blend.BlendAlphaMask`, which uses
a mask generator instance to generate per batch alpha masks and
then alpha-blends using these masks.
* Added `imgaug.augmenters.blend.BlendAlphaSomeColors`.
* Added `imgaug.augmenters.blend.BlendAlphaHorizontalLinearGradient`.
* Added `imgaug.augmenters.blend.BlendAlphaVerticalLinearGradient`.
* Added `imgaug.augmenters.blend.BlendAlphaRegularGrid`.
* Added `imgaug.augmenters.blend.BlendAlphaCheckerboard`.
* Added `imgaug.augmenters.blend.BlendAlphaSegMapClassIds`.
* Added `imgaug.augmenters.blend.BlendAlphaBoundingBoxes`.
* Added `imgaug.augmenters.blend.IBatchwiseMaskGenerator`,
an interface for classes generating masks on a batch-by-batch
basis.
* Added `imgaug.augmenters.blend.StochasticParameterMaskGen`,
a helper to generate masks from `StochasticParameter` instances.
* Added `imgaug.augmenters.blend.SomeColorsMaskGen`, a generator
that produces masks marking randomly chosen colors in images.
* Added `imgaug.augmenters.blend.HorizontalLinearGradientMaskGen`,
a linear gradient mask generator.
* Added `imgaug.augmenters.blend.VerticalLinearGradientMaskGen`,
a linear gradient mask generator.
* Added `imgaug.augmenters.blend.RegularGridMaskGen`,
a checkerboard-like mask generator where every grid cell has
a random alpha value.
* Added `imgaug.augmenters.blend.CheckerboardMaskGen`,
a checkerboard-like mask generator where every grid cell has
the opposite alpha value of its 4-neighbours.
* Added `imgaug.augmenters.blend.SegMapClassIdsMaskGen`, a
segmentation map-based mask generator.
* Added `imgaug.augmenters.blend.BoundingBoxesMaskGen`, a bounding
box-based mask generator.
* Added `imgaug.augmenters.blend.InvertMaskGen`, an mask generator
that inverts masks produces by child generators.
* Changed `imgaug.parameters.SimplexNoise` and
`imgaug.parameters.FrequencyNoise` to also accept `(H, W, C)`
sampling shapes, instead of only `(H, W)`.
* Refactored `AlphaElementwise` to be a wrapper around
`BlendAlphaMask`.
* Renamed `Alpha` to `BlendAlpha`.
`Alpha` is now deprecated.
* Renamed `AlphaElementwise` to `BlendAlphaElementwise`.
`AlphaElementwise` is now deprecated.
* Renamed `SimplexNoiseAlpha` to `BlendAlphaSimplexNoise`.
`SimplexNoiseAlpha` is now deprecated.
* Renamed `FrequencyNoiseAlpha` to `BlendAlphaFrequencyNoise`.
`FrequencyNoiseAlpha` is now deprecated.
* Renamed arguments `first` and `second` to `foreground` and `background`
in `BlendAlpha`, `BlendAlphaElementwise`, `BlendAlphaSimplexNoise` and
`BlendAlphaFrequencyNoise`.
* Changed `imgaug.parameters.handle_categorical_string_param()` to allow
parameter `valid_values` to be `None`.
* Fixed a wrong error message in
`imgaug.augmenters.color.change_colorspace_()`.
# Added
## Unwrapped Bounding Box Augmentation [#446](https://github.com/aleju/imgaug/pull/446)
The bounding box augmentation was previously a wrapper around keypoint
augmentation. Bounding Boxes were simply converted to keypoints at the
start of the augmentation and then augmented as keypoints by all called
augmenters. This was now changed so that all augmenters receive
bounding boxes and can then chose how to augment them. This enables
augmentations specific to bounding boxes.
* Added property `coords` to `BoundingBox`. The property returns an `(N,2)`
numpy array containing the coordinates of the top-left and bottom-right
bounding box corners.
* Added method `BoundingBox.coords_almost_equals(other)`.
* Added method `BoundingBox.almost_equals(other)`.
* Changed method `Polygon.almost_equals(other)` to no longer verify the
datatype. It is assumed now that the input is a Polygon.
* Added property `items` to `KeypointsOnImage`, `BoundingBoxesOnImage`,
`PolygonsOnImage`, `LineStringsOnImage`. The property returns the
keypoints/BBs/polygons/LineStrings contained by that instance.
* Added method `Polygon.coords_almost_equals(other)`. Alias for
`Polygon.exterior_almost_equals(other)`.
* Added property `Polygon.coords`. Alias for `Polygon.exterior`.
* Added property `Keypoint.coords`.
* Added method `Keypoint.coords_almost_equals(other)`.
* Added method `Keypoint.almost_equals(other)`.
* Added method `imgaug.testutils.assert_cbaois_equal()`.
* Added method `imgaug.testutils.shift_cbaoi()`.
* Added internal `_augment_bounding_boxes()` methods to various augmenters.
This allows to individually control how bounding boxes are supposed to
be augmented. Previously, the bounding box augmentation was a wrapper around
keypoint augmentation that did not allow such control.
* **[breaking]** Added parameter `parents` to
`Augmenter.augment_bounding_boxes()`.
This breaks if `hooks` was used as a *positional* argument in connection with
that method.
* [rarely breaking] Added parameter `func_bounding_boxes` to `Lambda`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
* [rarely breaking] Added parameter `func_bounding_boxes` to `AssertLambda`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
* [rarely breaking] Added parameter `check_bounding_boxes` to `AssertShape`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
## Unwrapped Line String Augmentation [#450](https://github.com/aleju/imgaug/pull/450)
This patch is the same as the bounding box unwrapping above, only applied to
line strings.
* Added internal `_augment_line_strings()` methods to various augmenters.
This allows to individually control how line strings are supposed to
be augmented. Previously, the line string augmentation was a wrapper around
keypoint augmentation that did not allow such control.
* [rarely breaking] Added parameter `func_line_strings` to `Lambda`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
* [rarely breaking] Added parameter `func_line_strings` to `AssertLambda`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
* [rarely breaking] Added parameter `check_line_strings` to `AssertShape`.
This breaks if one relied on the order of the augmenter's parameters instead
of their names.
## Added fit_output to PerspectiveTransform [#452](https://github.com/aleju/imgaug/pull/452) [#456](https://github.com/aleju/imgaug/pull/456)
This patch added `fit_output` to `PerspectiveTransform`.
* [rarely breaking] PerspectiveTransform has now a `fit_output` parameter,
similar to `Affine`. This change may break code that relied on the order of
arguments to `__init__`.
* The sampling code of `PerspectiveTransform` was reworked and should now
be faster.
# Added `ChangeColorTemperature` Augmenter [#454](https://github.com/aleju/imgaug/pull/454)
This patch added an augmenter and corresponding function to change the
color temperature of images. This adds e.g. red, orange or blue tints.
* Added augmenter `imgaug.augmenters.color.ChangeColorTemperature`.
* Added function `imgaug.augmenters.color.change_color_temperatures_()`.
* Added function `imgaug.augmenters.color.change_color_temperature_()`.
## Added Brightness Augmenters [#455](https://github.com/aleju/imgaug/pull/455)
This patch added brightness-related augmenters. At the core is
`WithBrightnessChannels`, which converts images to a choice of
colorspaces that have brightness-related channels, extracts these
channels and applies child augmenters to them. E.g. it might
transform to `L*a*b*` colorspace and extract `L`, then apply
a child augmenter and convert the modified `L*a*b*` back to `RGB`.
* Added augmenter `imgaug.augmenters.color.WithBrightnessChannels`.
* Added augmenter `imgaug.augmenters.color.MultiplyAndAddToBrightness`.
* Added augmenter `imgaug.augmenters.color.MultiplyBrightness`.
* Added augmenter `imgaug.augmenters.color.AddToBrightness`.
* Added method `imgaug.parameters.handle_categorical_string_param()`.
* Changed `change_colorspaces_()` to accept any iterable of `str` for
argument `to_colorspaces`, not just `list`.
## Added More Dropout Augmenters [#458](https://github.com/aleju/imgaug/pull/458)
This patch added more dropout augmenters. `Dropout2d` randomly zeros
whole channels, while `TotalDropout` randomly zeros whole images.
The latter augmenter can sometimes be used in connection with
blending operations. (Note though that in these cases it should not be
used with coordinate-based input data, such as bounding boxes, because
it removes that data from examples affected by total dropout. That
breaks the blending operation, which requires the number of coordinates
to be unchanged.)
* Added a new augmenter `Dropout2d`, which drops channels in images with
a defineable probability `p`. Dropped channels will be filled with zeros.
By default, the augmenter keeps at least one channel in each image
unaltered (i.e. not dropped).
* Added new augmenter `TotalDropout`, which sets all components to zero
for `p` percent of all images. The augmenter should be used in connection
with e.g. blend augmenters.
## Added `RemoveSaturation` [#462](https://github.com/aleju/imgaug/pull/462)
* Added `RemoveSaturation`, a shortcut for `MultiplySaturation((0.0, 1.0))`
with outputs similar to `Grayscale((0.0, 1.0))`.
## Added `Cartoon` Augmenter [#463](https://github.com/aleju/imgaug/pull/463)
This patch added a filter to change the style of images to one that looks
more cartoon-ish. The filter used classical methods. As such it works well
on some images and badly on others. It seems to work better on images
that already have rather saturated colors and pronounced edges.
* Added module `imgaug.augmenters.artistic`.
* Added function `imgaug.augmenters.artistic.stylize_cartoon(image)`.
* Added augmenter `imgaug.augmenters.artistic.Cartoon`.
## Added `MeanShiftBlur` Augmenter [#466](https://github.com/aleju/imgaug/pull/466)
This patch added a mean shift-based blur filter. Note that it is very
slow when using the default parameters (high radius).
* Added function `imgaug.augmenters.blur.blur_mean_shift_(image)`.
* Added augmenter `imgaug.augmenters.blur.MeanShiftBlur`.
## Added `DeterministicList` Parameter [#475](https://github.com/aleju/imgaug/pull/475)
Added `imgaug.parameters.DeterministicList`. Upon a request to generate
samples of shape `S`, this parameter will create a new array of shape `S`
and fill it by cycling over its list of values repeatedly.
## Added `Jigsaw` Augmenter [#476](https://github.com/aleju/imgaug/pull/476) [#577](https://github.com/aleju/imgaug/pull/577)
This patch added a jigsaw puzzle augmenter and corresponding functions.
The augmenter splits each image into a regular grid of cells, then
randomly picks some cells and switches them with one of their
8-neighbours. The process is repeated for `N` steps per image.
Note: The augmenter will reject batches containing bounding boxes,
polygons or line strings.
* Added function `imgaug.augmenters.geometric.apply_jigsaw()`.
* Added function `imgaug.augmenters.geometric.apply_jigsaw_to_coords()`.
* Added function `imgaug.augmenters.geometric.generate_jigsaw_destinations()`.
## Added Wrappers around Package `PIL` [#479](https://github.com/aleju/imgaug/pull/479) [#480](https://github.com/aleju/imgaug/pull/480) [#538](https://github.com/aleju/imgaug/pull/538)
This patch added wrapper functions and augmenters around popular
`PIL` functions. The outputs of these functions and augmenters are
tested to be identical with the ones in `PIL`. They are intended
for research cases where papers have to be re-implemented as
accurately as possible.
* Added module `imgaug.augmenters.pillike`, which contains augmenters and
functions corresponding to commonly used PIL functions. Their outputs
are guaranteed to be identical to the PIL outputs.
* Added the following functions to the module:
* `imgaug.augmenters.pillike.equalize`
* `imgaug.augmenters.pillike.equalize_`
* `imgaug.augmenters.pillike.autocontrast`
* `imgaug.augmenters.pillike.autocontrast_`
* `imgaug.augmenters.pillike.solarize`
* `imgaug.augmenters.pillike.solarize_`
* `imgaug.augmenters.pillike.posterize`
* `imgaug.augmenters.pillike.posterize_`
* `imgaug.augmenters.pillike.enhance_color`
* `imgaug.augmenters.pillike.enhance_contrast`
* `imgaug.augmenters.pillike.enhance_brightness`
* `imgaug.augmenters.pillike.enhance_sharpness`
* `imgaug.augmenters.pillike.filter_blur`
* `imgaug.augmenters.pillike.filter_smooth`
* `imgaug.augmenters.pillike.filter_smooth_more`
* `imgaug.augmenters.pillike.filter_edge_enhance`
* `imgaug.augmenters.pillike.filter_edge_enhance_more`
* `imgaug.augmenters.pillike.filter_find_edges`
* `imgaug.augmenters.pillike.filter_contour`
* `imgaug.augmenters.pillike.filter_emboss`
* `imgaug.augmenters.pillike.filter_sharpen`
* `imgaug.augmenters.pillike.filter_detail`
* `imgaug.augmenters.pillike.warp_affine`
* Added the following augmenters to the module:
* `imgaug.augmenters.pillike.Solarize`
* `imgaug.augmenters.pillike.Posterize`.
(Currently alias for `imgaug.augmenters.color.Posterize`.)
* `imgaug.augmenters.pillike.Equalize`
* `imgaug.augmenters.pillike.Autocontrast`
* `imgaug.augmenters.pillike.EnhanceColor`
* `imgaug.augmenters.pillike.EnhanceContrast`
* `imgaug.augmenters.pillike.EnhanceBrightness`
* `imgaug.augmenters.pillike.EnhanceSharpness`
* `imgaug.augmenters.pillike.FilterBlur`
* `imgaug.augmenters.pillike.FilterSmooth`
* `imgaug.augmenters.pillike.FilterSmoothMore`
* `imgaug.augmenters.pillike.FilterEdgeEnhance`
* `imgaug.augmenters.pillike.FilterEdgeEnhanceMore`
* `imgaug.augmenters.pillike.FilterFindEdges`
* `imgaug.augmenters.pillike.FilterContour`
* `imgaug.augmenters.pillike.FilterEmboss`
* `imgaug.augmenters.pillike.FilterSharpen`
* `imgaug.augmenters.pillike.FilterDetail`
* `imgaug.augmenters.pillike.Affine`
## Added `Identity` [#481](https://github.com/aleju/imgaug/pull/481)
This patch added an identity function augmenter (`Identity`), which is
the same as `Noop` and will replace the latter one in the long run.
* [rarely breaking] Added `imgaug.augmenters.meta.Identity`, an alias of
`Noop`. `Identity` is now the recommended augmenter for identity
transformations. This change can break code that explicitly relied on
exactly `Noop` being used, e.g. via `isinstance` checks.
* Renamed parameter `noop_if_topmost` to `identity_if_topmost` in
method `imgaug.augmenters.meta.Augmenter.remove_augmenters()`. The old name
is now deprecated.
## Added Shearing on the Y-Axis to `Affine` [#482](https://github.com/aleju/imgaug/pull/482)
`Affine` was changed to now also support shearing on the y-axis.
Previously, only the x-axis was supported. Use e.g.
`Affine(shear={"y": (-20, 20))` now.
* [rarely breaking] Extended `Affine` to also support shearing on the
y-axis (previously, only x-axis was possible). This feature can be used
via e.g. ``Affine(shear={"x": (-30, 30), "y": (-10, 10)})``. If instead
a single number is used (e.g. ``Affine(shear=15)``), shearing will be done
only on the x-axis. If a single ``tuple``, ``list`` or
``StochasticParameter`` is used, the generated samples will be used
identically for both the x-axis and y-axis (this is consistent with
translation and scaling). To get independent random samples per axis use
the dictionary form.
## Added Wrappers around `Affine` [#484](https://github.com/aleju/imgaug/pull/484)
This patch added a few convenience wrappers around `Affine`.
* Added `imgaug.augmenters.geometric.ScaleX`.
* Added `imgaug.augmenters.geometric.ScaleY`.
* Added `imgaug.augmenters.geometric.TranslateX`.
* Added `imgaug.augmenters.geometric.TranslateY`.
* Added `imgaug.augmenters.geometric.Rotate`.
* Added `imgaug.augmenters.geometric.ShearX`.
* Added `imgaug.augmenters.geometric.ShearY`.
## Added More Methods to Remove Out-of-Image Augmentables [#487](https://github.com/aleju/imgaug/pull/487)
This patch extended the methods to handle coordinate-based augmentables,
e.g. bounding boxes, that are partially/fully outside of the image plane.
They can now more easily be dropped if more than `p%` of their areas is
outside of the image plane.
The patch also adds augmenters to remove and clip coordinate-based
augmentables that are outside of the image plane.
* Added `Keypoint.is_out_of_image()`.
* Added `BoundingBox.compute_out_of_image_area()`.
* Added `Polygon.compute_out_of_image_area()`.
* Added `Keypoint.compute_out_of_image_fraction()`
* Added `BoundingBox.compute_out_of_image_fraction()`.
* Added `Polygon.compute_out_of_image_fraction()`.
* Added `LineString.compute_out_of_image_fraction()`.
* Added `KeypointsOnImage.remove_out_of_image_fraction()`.
* Added `BoundingBoxesOnImage.remove_out_of_image_fraction()`.
* Added `PolygonsOnImage.remove_out_of_image_fraction()`.
* Added `LineStringsOnImage.remove_out_of_image_fraction()`.
* Added `KeypointsOnImage.clip_out_of_image()`.
* Added `imgaug.augmenters.meta.RemoveCBAsByOutOfImageFraction`.
Removes coordinate-based augmentables (e.g. BBs) that have at least a
specified fraction of their area outside of the image plane.
* Added `imgaug.augmenters.meta.ClipCBAsToImagePlanes`.
Clips off all parts from coordinate-based augmentables (e.g. BBs) that are
outside of the corresponding image.
* Changed `Polygon.area` to return `0.0` if the polygon contains less than
three points (previously: exception).
## Added Bounding Box to Polygon Conversion [#489](https://github.com/aleju/imgaug/pull/489)
* Added method `imgaug.augmentables.bbs.BoundingBox.to_polygon()`.
* Added method
`imgaug.augmentables.bbs.BoundingBoxesOnImage.to_polygons_on_image()`.
## Added Polygon Subdivision [#489](https://github.com/aleju/imgaug/pull/489)
* Added method `imgaug.augmentables.polys.Polygon.subdivide(N)`.
The method increases the polygon's corner point count by interpolating
`N` points on each edge with regular distance.
* Added method `imgaug.augmentables.polys.PolygonsOnImage.subdivide(N)`.
## Added `WithPolarWarping` Augmenter [#489](https://github.com/aleju/imgaug/pull/489)
This patch added an augmenter to transform images to polar coordinates
and apply child augmenters within that space. This leads to interesting
effects in combination with augmenters that affect pixel locations, such
as cropping or affine transformations.
* Added augmenter `imgaug.augmenters.geometric.WithPolarWarping`, an
augmenter that applies child augmenters in a polar representation of the
image.
## Added Convenient Access Methods to Coordinate-Based Augmentables [#495](https://github.com/aleju/imgaug/pull/495) [#541](https://github.com/aleju/imgaug/pull/541)
This patch added various magic functions to coordinate-based
augmentables that make their usage more convenient. Example:
```python
import imgaug as ia
bb1 = ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
bb2 = ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2])
print(bbsoi[0]) # prints now str(bb1)
print(len(bbsoi)) # prints now 2
for bb in bbsoi: # looping is now supported
print(bb)
```
* Added module `imgaug.augmentables.base`.
* Added interface `imgaug.augmentables.base.IAugmentable`, implemented by
`HeatmapsOnImage`, `SegmentationMapsOnImage`, `KeypointsOnImage`,
`BoundingBoxesOnImage`, `PolygonsOnImage` and `LineStringsOnImage`.
* Added ability to iterate over coordinate-based `*OnImage` instances
(keypoints, bounding boxes, polygons, line strings), e.g.
`bbsoi = BoundingBoxesOnImage(bbs, shape=...); for bb in bbsoi: ...`.
would iterate now over `bbs`.
* Added implementations of `__len__` methods to coordinate-based `*OnImage`
instances, e.g.
`bbsoi = BoundingBoxesOnImage(bbs, shape=...); print(len(bbsoi))`
would now print the number of bounding boxes in `bbsoi`.
* Added ability to iterate over coordinates of `BoundingBox` (top-left,
bottom-right), `Polygon` and `LineString` via `for xy in obj: ...`.
* Added ability to access coordinates of `BoundingBox`, `Polygon` and
`LineString` using indices or slices, e.g. `line_string[1:]` to get an
array of all coordinates except the first one.
* Added property `Keypoint.xy`.
* Added property `Keypoint.xy_int`.
## Added `SaveDebugImageEveryNBatches` Augmenter [#502](https://github.com/aleju/imgaug/pull/502)
This patch added a debug augmenter `SaveDebugImageEveryNBatches` that
visualizes a whole batch and saves the corresponding image to a directory.
The visualization happens at every `Nth` batch. The plot contains
a visualization of all images within the batch, as well as all additional
input data (e.g. segmentation maps or bounding boxes overlayed with
images). The plot also contains various additional information, such as
observed value ranges (min/max values) of images, observed labels of
bounding boxes or observed segmentation classes.
The augmenter can be used during training to evaluate the strength of
augmentations, whether all data is still aligned (e.g. bounding box
positions match object positions) and whether the data statistics match
the expectations (e.g. no segmentation map classes missing).
The augmenter might be useful even if no augmentation is actually performed.
* Added module `imgaug.augmenters.debug`.
* Added function `imgaug.augmenters.debug.draw_debug_image()`. The function
draws an image containing debugging information for a provided set of
images and non-image data (e.g. segmentation maps, bounding boxes)
corresponding to a single batch. The debug image visualizes these
informations (e.g. bounding boxes drawn on images) and offers relevant
information (e.g. actual value ranges of images, labels of bounding
boxes and their counts, etc.).
* Added augmenter `imgaug.augmenters.debug.SaveDebugImageEveryNBatches`.
Augmenter corresponding to `draw_debug_image()`. Saves an image at every
n-th batch into a provided folder.
## Added Multi-Channel cvals in `pad()` [#502](https://github.com/aleju/imgaug/pull/502)
Improved `imgaug.augmenters.size.pad()` to support multi-channel values
for the `cval` parameter (e.g. RGB colors).
## Added Wrappers around Package `imagecorruptions` [#530](https://github.com/aleju/imgaug/pull/530)
Added wrappers around the functions from package
[bethgelab/imagecorruptions](https://github.com/bethgelab/imagecorruptions).
The functions in that package were used in some recent papers and are added
here for convenience.
The wrappers produce arrays containing values identical to the output
arrays from the corresponding `imagecorruptions` functions when called
via the `imagecorruptions.corrupt()` (verified via unittests).
The interfaces of the wrapper functions are identical to the
`imagecorruptions` functions, with the only difference of also supporting
`seed` parameters.
* Added module `imgaug.augmenters.imgcorruptlike`. The `like` signals that
the augmentation functions do not *have* to wrap `imagecorruptions`
internally. They merely have to produce the same outputs.
* Added the following functions to module `imgaug.augmenters.imgcorruptlike`:
* `apply_gaussian_noise()`
* `apply_shot_noise()`
* `apply_impulse_noise()`
* `apply_speckle_noise()`
* `apply_gaussian_blur()`
* `apply_glass_blur()` (improved performance over original function)
* `apply_defocus_blur()`
* `apply_motion_blur()`
* `apply_zoom_blur()`
* `apply_fog()`
* `apply_snow()`
* `apply_spatter()`
* `apply_contrast()`
* `apply_brightness()`
* `apply_saturate()`
* `apply_jpeg_compression()`
* `apply_pixelate()`
* `apply_elastic_transform()`
* Added function
`imgaug.augmenters.imgcorruptlike.get_corruption_names(subset)`.
Similar to `imagecorruptions.get_corruption_names(subset)`, but returns a
tuple
`(list of corruption method names, list of corruption method functions)`,
instead of only the names.
* Added the following augmenters to module `imgaug.augmenters.imgcorruptlike`:
* `GaussianNoise`
* `ShotNoise`
* `ImpulseNoise`
* `SpeckleNoise`
* `GaussianBlur`
* `GlassBlur`
* `DefocusBlur`
* `MotionBlur`
* `ZoomBlur`
* `Fog`
* `Frost`
* `Snow`
* `Spatter`
* `Contrast`
* `Brightness`
* `Saturate`
* `JpegCompression`
* `Pixelate`
* `ElasticTransform`
* Added context `imgaug.random.temporary_numpy_seed()`.
## Added `Cutout` Augmenter [#531](https://github.com/aleju/imgaug/pull/531) [#570](https://github.com/aleju/imgaug/pull/570)
This patch added Cutout augmentation, similar to the paper proposal.
The augmetner has some similarity with `CoarseDropout`.
* Added `imgaug.augmenters.arithmetic.apply_cutout_()`, which replaces
in-place a single rectangular area with a constant intensity value or a
constant color or gaussian noise.
See also the [paper](https://arxiv.org/abs/1708.04552) about Cutout.
* Added `imgaug.augmenters.arithmetic.apply_cutout()`. Same as
`apply_cutout_()`, but copies the input images before applying cutout.
* Added `imgaug.augmenters.arithmetic.Cutout`.
## Added in-place Methods for Coordinate-based Augmentables [#532](https://github.com/aleju/imgaug/pull/532)
This patch added for many already existing methods corresponding in-place
variations. They are now used throughout the library, improving the
performance of augmentation in the case of e.g. bounding boxes.
* Added `Keypoint.project_()`.
* Added `Keypoint.shift_()`.
* Added `KeypointsOnImage.on_()`.
* Added setter for `KeypontsOnImage.items`.
* Added setter for `BoundingBoxesOnImage.items`.
* Added setter for `LineStringsOnImage.items`.
* Added setter for `PolygonsOnImage.items`.
* Added `KeypointsOnImage.remove_out_of_image_fraction_()`.
* Added `KeypointsOnImage.clip_out_of_image_fraction_()`.
* Added `KeypointsOnImage.shift_()`.
* Added `BoundingBox.project_()`.
* Added `BoundingBox.extend_()`.
* Added `BoundingBox.clip_out_of_image_()`.
* Added `BoundingBox.shift_()`.
* Added `BoundingBoxesOnImage.on_()`.
* Added `BoundingBoxesOnImage.clip_out_of_image_()`.
* Added `BoundingBoxesOnImage.remove_out_of_image_()`.
* Added `BoundingBoxesOnImage.remove_out_of_image_fraction_()`.
* Added `BoundingBoxesOnImage.shift_()`.
* Added `imgaug.augmentables.utils.project_coords_()`.
* Added `LineString.project_()`.
* Added `LineString.shift_()`.
* Added `LineStringsOnImage.on_()`.
* Added `LineStringsOnImage.remove_out_of_image_()`.
* Added `LineStringsOnImage.remove_out_of_image_fraction_()`.
* Added `LineStringsOnImage.clip_out_of_image_()`.
* Added `LineStringsOnImage.shift_()`.
* Added `Polygon.project_()`.
* Added `Polygon.shift_()`.
* Added `Polygon.on_()`.
* Added `Polygon.subdivide_()`.
* Added `PolygonsOnImage.remove_out_of_image_()`.
* Added `PolygonsOnImage.remove_out_of_image_fraction_()`.
* Added `PolygonsOnImage.clip_out_of_image_()`.
* Added `PolygonsOnImage.shift_()`.
* Added `PolygonsOnImage.subdivide_()`.
* Switched `BoundingBoxesOnImage.copy()` to a custom copy operation (away
from module `copy` module).
* Added parameters `bounding_boxes` and `shape` to
BoundingBoxesOnImage.copy()`.
* Added parameters `bounding_boxes` and `shape` to
BoundingBoxesOnImage.deepcopy()`.
* Switched `KeypointsOnImage.copy()` to a custom copy operation (away
from module `copy` module).
* Switched `PolygonsOnImage.copy()` to a custom copy operation (away
from module `copy` module).
* Added parameters `polygons` and `shape` to
PolygonsOnImage.copy()`.
* Added parameters `polygons` and `shape` to
PolygonsOnImage.deepcopy()`.
* Switched augmenters to use in-place functions for keypoints,
bounding boxes, line strings and polygons.
## Added Standardized LUT Methods [#542](https://github.com/aleju/imgaug/pull/542)
This patch standardized the handling of lookup tables throughout the library.
* Added `imgaug.imgaug.apply_lut()`, which applies a lookup table to an image.
* Added `imgaug.imgaug.apply_lut_()`. In-place version of `apply_lut()`.
* Refactored all augmenters to use these new LUT functions.
This likely fixed some so-far undiscovered bugs in augmenters using LUT
tables.
## Added Drawing of Bounding Box Labels [#545](https://github.com/aleju/imgaug/pull/545)
When drawing bounding boxes on images via `BoundingBox.draw_on_image()`
or `BoundingBoxesOnImage.draw_on_image()`, a box containing the label will now
be drawn over each bounding box's rectangle. If the bounding box's label is
set to `None`, the label box will not be drawn. For more detailed control,
use `BoundingBox.draw_label_on_image()`.
* Added method `imgaug.augmentables.BoundingBox.draw_label_on_image()`.
* Added method `imgaug.augmentables.BoundingBox.draw_box_on_image()`.
* Changed method `imgaug.augmentables.BoundingBox.draw_on_image()`
to automatically draw a bounding box's label.
## Added Index-based Access to Coordinate-based `*OnImage` Instances [#547](https://github.com/aleju/imgaug/pull/547)
Enabled index-based access to coordinate-based `*OnImage` instances, i.e. to
`KeypointsOnImage`, `BoundingBoxesOnImage`, `LineStringsOnImage` and
`PolygonsOnImage`. This allows to do things like
`bbsoi = BoundingBoxesOnImage(...); bbs = bbsoi[0:2];`.
* Added `imgaug.augmentables.kps.KeypointsOnImage.__getitem__()`.
* Added `imgaug.augmentables.bbs.BoundingBoxesOnImage.__getitem__()`.
* Added `imgaug.augmentables.lines.LineStringsOnImage.__getitem__()`.
* Added `imgaug.augmentables.polys.PolygonsOnImage.__getitem__()`.
## Added `Rain` and `RainLayer` Augmenters [#551](https://github.com/aleju/imgaug/pull/551)
Added augmenter(s) to create fake rain effects. They currently seem to work
best at around medium-sized images (~224px).
* Added `imgaug.augmenters.weather.Rain`.
* Added `imgaug.augmenters.weather.RainLayer`.
## Added `round` Parameter to `Discretize` [#553](https://github.com/aleju/imgaug/pull/553)
Added the parameter `round` to `imgaug.parameters.Discretize`. The parameter
defaults to `True`, i.e. the default behaviour of `Discretize` did not change.
## Added `RandAugment` Augmenter [#553](https://github.com/aleju/imgaug/pull/553)
Added a RandAugment augmenter, similar to the one described in the paper
"RandAugment: Practical automated data augmentation with a reduced
search space".
Note: This implementation makes a best guess about some hyperparameters
that were neither in the paper nor in the code repsitory clearly
defined.
Note: This augmenter differs from the paper implementation by applying
a fix to their color augmentations. The ones in the paper's implementation
seemed to increase in strength as the magnitude was decreased below
a threshold.
Note: This augmenter currently only accepts image inputs. Other input
types (e.g. bounding boxes) will be rejected.
* Added module `imgaug.augmenters.collections`
* Added augmenter `imgaug.augmenters.collections.RandAugment`.
## Added and Improved Warnings for Probably-Wrong Image Inputs [#594](https://github.com/aleju/imgaug/pull/594)
Improved the errors and warnings on image augmentation calls.
`augment_image()` will now produce a more self-explanatory error
message when calling it as in `augment_image(list of images)`.
Calls of single-image augmentation functions (e.g.
`augment(image=...)`) with inputs that look like multiple images
will now produce warnings. This is the case for `(H, W, C)`
inputs when `C>=32` (as that indicates that `(N, H, W)` was
actually provided).
Calls of multi-image augmentation functions (e.g.
`augment(images=...)`) with inputs that look like single images
will now produce warnings. This is the case for `(N, H, W)`
inputs when `W=1` or `W=3` (as that indicates that `(H, W, C)`
was actually provided.)
* Added an assert in `augment_image()` to verify that inputs are
arrays.
* Added warnings for probably-wrong image inputs in
`augment_image()`, `augment_images()`, `augment()` (and its
alias `__call__()`).
* Added module `imgaug.augmenters.base`.
* Added warning
`imgaug.augmenters.base.SuspiciousMultiImageShapeWarning`.
* Added warning
`imgaug.augmenters.base.SuspiciousSingleImageShapeWarning`.
* Added `imgaug.testutils.assertWarns`, similar to `unittest`'s
`assertWarns`, but available in python <3.2.
# Changed
## Improved RNG Handling during Polygon Augmentation [#447](https://github.com/aleju/imgaug/pull/447)
Changed `Augmenter.augment_polygons()` to copy the augmenter's RNG
before starting concave polygon recovery. This is done for cleanliness and
should not have any effects for users.
Also removed RNG copies in `_ConcavePolygonRecoverer` to improve performance.
## Pooling Augmenters now Affect Maps [#457](https://github.com/aleju/imgaug/pull/457)
Pooling augmenters were previously implemented so that they did not pool
the arrays of maps (i.e. heatmap arrays, segmentation map arrays). Only
the image shape saved within `HeatmapsOnImage.shape` and
`SegmentationMapsOnImage.shape` were updated. That was done because the library
can handle map arrays that are larger than the corresponding images and hence
no pooling was necessary for the augmentation to work correctly. This was now
changed and pooling augmenters will also pool map arrays
(if `keep_size=False`). The motiviation for this change is that the old
behaviour was unintuitive and inconsistent with other augmenters (e.g. `Crop`).
## Affine Translation Precision [#489](https://github.com/aleju/imgaug/pull/489)
Removed a rounding operation in `Affine` translation that would unnecessarily
round floats to integers. This should make coordinate augmentation overall
more accurate.
## `Affine.get_parameters()` and `translate_px`/`translate_percent` [#508](https://github.com/aleju/imgaug/pull/508)
Changed `Affine.get_parameters()` to always return a tuple `(x, y, mode)`
for translation, where `mode` is either `px` or `percent`,
and `x` and `y` are stochastic parameters. `y` may be `None` if the same
parameter (and hence samples) are used for both axes.
## Removed Outdated "Don't Import from this Module" Messages [#539](https://github.com/aleju/imgaug/pull/539)
The docstring of each module in ``imgaug.augmenters`` previously included a
suggestion to not directly import from that module, but instead use
``imgaug.augmenters.``. That was due to the categorization
still being unstable. As the categorization has now been fairly stable
for a long time, the suggestion was removed from all modules. Calling
``imgaug.augmenters.`` instead of
``imgaug.augmenters..`` is however still the
preferred way.
## Standardized `shift()` Interfaces of Coordinate-Based Augmentables [#548](https://github.com/aleju/imgaug/pull/548)
The interfaces for shift operations of all coordinate-based
augmentables (Keypoints, BoundingBoxes, LineStrings, Polygons)
were standardized. All of these augmentables have now the same
interface for shift operations. Previously, Keypoints used
a different interface (using `x` and `y` arguments) than the
other augmentables (using `top`, `right`, `bottom`, `left`
arguments). All augmentables use now the interface of Keypoints
as that is simpler and less ambiguous. Old arguments are still
accepted, but will produce deprecation warnings. Change the
arguments to `x` and `y` following `x=left-right` and
`y=top-bottom`.
**[breaking]** This breaks if one relied on calling `shift()` functions of
`BoundingBox`, `LineString`, `Polygon`, `BoundingBoxesOnImage`,
`LineStringsOnImage` or `PolygonsOnImage` without named arguments.
E.g. `bb = BoundingBox(...); bb_shifted = bb.shift(1, 2, 3, 4);`
will produce unexpected outputs now (equivalent to
`shift(x=1, y=2, top=3, right=4, bottom=0, left=0)`),
while `bb_shifted = bb.shift(top=1, right=2, bottom=3, left=4)` will still
work as expected.
* Added arguments `x`, `y` to `BoundingBox.shift()`, `LineString.shift()`
and `Polygon.shift()`.
* Added arguments `x`, `y` to `BoundingBoxesOnImage.shift()`,
`LineStringsOnImage.shift()` and `PolygonsOnImage.shift()`.
* Marked arguments `top`, `right`, `bottom`, `left` in
`BoundingBox.shift()`, `LineString.shift()` and `Polygon.shift()`
as deprecated. This also affects the corresponding `*OnImage`
classes.
* Added function `testutils.wrap_shift_deprecation()`.
## Simplified Standard Parameters of Augmenters [#567](https://github.com/aleju/imgaug/pull/567) [#595](https://github.com/aleju/imgaug/pull/595)
The patch changed the standard parameters shared by all augmenters to a
reduced and more self-explanatory set. Previously, all augmenters
shared the parameters `name`, `random_state` and `deterministic`.
The new parameters are `seed` and `name`.
`deterministic` was removed as it was hardly ever used and because
it caused frequently confusion with regards to its meaning. The
parameter is still accepted but will now produce a deprecation
warning. Use `.to_deterministic()` instead.
Reminder: `to_deterministic()` is necessary if you want to get
the same samples in *consecutive* augmentation calls. It is *not*
necessary if you want your generated samples to be dependent on
an initial seed or random state as that is *always* the case
anyways. To use non-random initial seeds, use either
the `seed` parameter (augmenter-specific seeding) or
`imgaug.random.seed()` (global seeding, affects only augmenters
for which the `seed` parameter was not explicitly provided).
`random_state` was renamed to `seed` as providing a seed value
is the more common use case compared to providing a random state.
Many users also seemed to be unaware that `random_state` accepted
seed values. The new name should make this more clear.
The old parameter `random_state` is still accepted, but will
likely be deprecated in the future.
**[breaking]** This patch breaks if one relied on the order of
`name`, `random_state` and `deterministic`. The new order is now
`seed=..., name=..., random_state=..., deterministic=...` (with the
latter two parameters being outdated or deprecated)
as opposed to previously
`name=..., deterministic=..., random_state=...`.
## Improved Default Values of Augmenters [#582](https://github.com/aleju/imgaug/pull/582)
**[breaking]** Most augmenters had previously default values that
made them equivalent to identity functions. Users had to explicitly
change the defaults to proper values in order to "activate"
augmentations. To simplify the usage of the library, the default
values of most augmenters were changed to medium-strength
augmentations. E.g.
`Sequential([Affine(), UniformVoronoi(), CoarseDropout()])`
should now produce decent augmentations.
A few augmenters were set to always-on, maximum-strength
augmentations. This is the case for:
* `Grayscale` (always fully grayscales images, use
`Grayscale((0.0, 1.0))` for random strengths)
* `RemoveSaturation` (same as `Grayscale`)
* `Fliplr` (always flips images, use `Fliplr(0.5)` for 50%
probability)
* `Flipud` (same as `Fliplr`)
* `TotalDropout` (always drops everything, use
`TotalDropout(0.1)` to drop everything for 10% of all images)
* `Invert` (always inverts images, use `Invert(0.1)` to invert
10% of all images)
* `Rot90` (always rotates exactly once clockwise by 90 degrees,
use `Rot90((0, 3))` for any rotation)
These settings seemed to better match user-expectations.
Such maximum-strength settings however were not chosen for all
augmenters where one might expect them. The defaults are set to
varying strengths for, e.g. `Superpixels` (replaces only some
superpixels with cellwise average colors), `UniformVoronoi` (also
only replaces some cells), `Sharpen` (alpha-blends with variable
strength, the same is the case for `Emboss`, `EdgeDetect` and
`DirectedEdgeDetect`) and `CLAHE` (variable clip limits).
*Note*: Some of the new default values will cause issues with
non-`uint8` inputs.
*Note*: The defaults for `per_channel` and `keep_size` were not
adjusted. It is currently still the default behaviour of all
augmenters to affect all channels in the same way and to resize
their outputs back to the input sizes.
The exact changes to default values are listed below.
**imgaug.arithmetic**
* `Add`
* `value`: `0` -> `(-20, 20)`
* `AddElementwise`
* `value`: `0` -> `(-20, 20)`
* `AdditiveGaussianNoise`
* `scale`: `0` -> `(0, 15)`
* `AdditiveLaplaceNoise`
* `scale`: `0` -> `(0, 15)`
* `AdditivePoissonNoise`
* `scale`: `0` -> `(0, 15)`
* `Multiply`
* `mul`: `1.0` -> `(0.8, 1.2)`
* `MultiplyElementwise`:
* `mul`: `1.0` -> `(0.8, 1.2)`
* `Dropout`:
* `p`: `0.0` -> `(0.0, 0.05)`
* `CoarseDropout`:
* `p`: `0.0` -> `(0.02, 0.1)`
* `size_px`: `None` -> `(3, 8)`
* `min_size`: `4` -> `3`
* Default for `size_px` is only used if neither `size_percent`
nor `size_px` is provided by the user.
* `CoarseSaltAndPepper`:
* `p`: `0.0` -> `(0.02, 0.1)`
* `size_px`: `None` -> `(3, 8)`
* `min_size`: `4` -> `3`
* Default for `size_px` is only used if neither `size_percent`
nor `size_px` is provided by the user.
* `CoarseSalt`:
* `p`: `0.0` -> `(0.02, 0.1)`
* `size_px`: `None` -> `(3, 8)`
* `min_size`: `4` -> `3`
* Default for `size_px` is only used if neither `size_percent`
nor `size_px` is provided by the user.
* `CoarsePepper`:
* `p`: `0.0` -> `(0.02, 0.1)`
* `size_px`: `None` -> `(3, 8)`
* `min_size`: `4` -> `3`
* Default for `size_px` is only used if neither `size_percent`
nor `size_px` is provided by the user.
* `SaltAndPepper`:
* `p`: `0.0` -> `(0.0, 0.03)`
* `Salt`:
* `p`: `0.0` -> `(0.0, 0.03)`
* `Pepper`:
* `p`: `0.0` -> `(0.0, 0.05)`
* `ImpulseNoise`:
* `p`: `0.0` -> `(0.0, 0.03)`
* `Invert`:
* `p`: `0` -> `1`
* `JpegCompression`:
* `compression`: `50` -> `(0, 100)`
**imgaug.blend**
* `BlendAlpha`
* `factor`: `0` -> `(0.0, 1.0)`
* `BlendAlphaElementwise`
* `factor`: `0` -> `(0.0, 1.0)`
**imgaug.blur**
* `GaussianBlur`:
* `sigma`: `0` -> `(0.0, 3.0)`
* `AverageBlur`:
* `k`: `1` -> `(1, 7)`
* `MedianBlur`:
* `k`: `1` -> `(1, 7)`
* `BilateralBlur`:
* `d`: `1` -> `(1, 9)`
* `MotionBlur`:
* `k`: `5` -> `(3, 7)`
**imgaug.color**
* `MultiplyHueAndSaturation`:
* `mul_hue`: `None` -> `(0.5, 1.5)`
* `mul_saturation`: `None` -> `(0.0, 1.7)`
* These defaults are only used if the user provided neither
`mul` nor `mul_hue` nor `mul_saturation`.
* `MultiplyHue`:
* `mul`: `(-1.0, 1.0)` -> `(-3.0, 3.0)`
* `AddToHueAndSaturation`:
* `value_hue`: `None` -> `(-40, 40)`
* `value_saturation`: `None` -> `(-40, 40)`
* These defaults are only used if the user provided neither
`value` nor `value_hue` nor `value_saturation`.
* `Grayscale`:
* `alpha`: `0` -> `1`
**imgaug.contrast**
* `GammaContrast`:
* `gamma`: `1` -> `(0.7, 1.7)`
* `SigmoidContrast`:
* `gain`: `10` -> `(5, 6)`
* `cutoff`: `0.5` -> `(0.3, 0.6)`
* `LogContrast`:
* `gain`: `1` -> `(0.4, 1.6)`
* `LinearContrast`:
* `alpha`: `1` -> `(0.6, 1.4)`
* `AllChannelsCLAHE`:
* `clip_limit`: `40` -> `(0.1, 8)`
* `tile_grid_size_px`: `8` -> `(3, 12)`
* `CLAHE`:
* `clip_limit`: `40` -> `(0.1, 8)`
* `tile_grid_size_px`: `8` -> `(3, 12)`
**convolutional**
* `Sharpen`:
* `alpha`: `0` -> `(0.0, 0.2)`
* `lightness`: `1` -> `(0.8, 1.2)`
* `Emboss`:
* `alpha`: `0` -> `(0.0, 1.0)`
* `strength`: `1` -> `(0.25, 1.0)`
* `EdgeDetect`:
* `alpha`: `0` -> `(0.0, 0.75)`
* `DirectedEdgeDetect`:
* `alpha`: `0` -> `(0.0, 0.75)`
**imgaug.flip**
* `Fliplr`:
* `p`: `0` -> `1`
* `Flipud`:
* `p`: `0` -> `1`
**imgaug.geometric**
* `Affine`:
* `scale`: `1` -> `{"x": (0.9, 1.1), "y": (0.9, 1.1)}`
* `translate_percent`: None -> `{"x": (-0.1, 0.1), "y": (-0.1, 0.1)}`
* `rotate`: `0` -> `(-15, 15)`
* `shear`: `0` -> `shear={"x": (-10, 10), "y": (-10, 10)}`
* These defaults are only used if no affine transformation
parameter was set by the user. Otherwise the not-set
parameters default again towards the identity function.
* `PiecewiseAffine`:
* `scale`: `0` -> `(0.0, 0.04)`
* `nb_rows`: `4` -> `(2, 4)`
* `nb_cols`: `4` -> `(2, 4)`
* `PerspectiveTransform`:
* `scale`: `0` -> `(0.0, 0.06)`
* `ElasticTransformation`:
* `alpha`: `0` -> `(0.0, 40.0)`
* `sigma`: `0` -> `(4.0, 8.0)`
* `Rot90`:
* `k`: `(no default)` -> `k=1`
**imgaug.pooling**
* `AveragePooling`:
* `k`: `(no default)` -> `(1, 5)`
* `MaxPooling`:
* `k`: `(no default)` -> `(1, 5)`
* `MinPooling`:
* `k`: `(no default)` -> `(1, 5)`
* `MedianPooling`:
* `k`: `(no default)` -> `(1, 5)`
**imgaug.segmentation**
* `Superpixels`:
* `p_replace`: `0.0` -> `(0.5, 1.0)`
* `n_segments`: `100` -> `(50, 120)`
* `UniformVoronoi`:
* `n_points`: `(no default)` -> `(50, 500)`
* `p_replace`: `1.0` -> `(0.5, 1.0)`.
* `RegularGridVoronoi`:
* `n_rows`: `(no default)` -> `(10, 30)`
* `n_cols`: `(no default)` -> `(10, 30)`
* `p_drop_points`: `0.4` -> `(0.0, 0.5)`
* `p_replace`: `1.0` -> `(0.5, 1.0)`
* `RelativeRegularGridVoronoi`: Changed defaults from
* `n_rows_frac`: `(no default)` -> `(0.05, 0.15)`
* `n_cols_frac`: `(no default)` -> `(0.05, 0.15)`
* `p_drop_points`: `0.4` -> `(0.0, 0.5)`
* `p_replace`: `1.0` -> `(0.5, 1.0)`
**imgaug.size**
* `CropAndPad`:
* `percent`: `None` -> `(-0.1, 0.1)`
* This default is only used if the user has provided
neither `px` nor `percent`.
* `Pad`:
* `percent`: `None` -> `(0.0, 0.1)`
* This default is only used if the user has provided
neither `px` nor `percent`.
* `Crop`:
* `percent`: `None` -> `(0.0, 0.1)`
* This default is only used if the user has provided
neither `px` nor `percent`.
## `setup.py` Now Accepts any `opencv-*` Installation [#586](https://github.com/aleju/imgaug/pull/586)
`setup.py` was changed so that it now accepts `opencv-python`,
`opencv-python-headless`, `opencv-contrib-python` and
`opencv-contrib-python-headless` as valid OpenCV installations.
Previously, only `opencv-python-headless` was accepted, which
could easily cause conflicts when another one of the mentioned
libraries was already installed.
If none of the mentioned libraries is installed, `setup.py`
will default to adding `opencv-python` as a requirement.
Note that this may still cause issues if a single installation
call installs multiple libraries and the order is random.
`imgaug` will then currently request `opencv-python-headless`
to be installed, which may differ from what a later installed
library requests. Try to ensure that the other library is installed
first in these cases.
## Unified OpenCV Input Normalization [#565](https://github.com/aleju/imgaug/pull/565)
Changed various augmenters to use the same normalization for OpenCV
inputs. This probably fixes some previously undiscovered bugs.
## Renamed In-place Methods [#444](https://github.com/aleju/imgaug/pull/444)
* Renamed `Augmenter.reseed()` to `Augmenter.seed_()`. The old name is
now deprecated.
* Renamed `Augmenter.remove_augmenters_inplace()` to
`Augmenter.remove_augmenters_()`. The old name is now deprecated.
## Deprecated `AffineCv2` [#540](https://github.com/aleju/imgaug/pull/540)
Deprecated `imgaug.augmenters.geometric.AffineCv2`.
Use `imgaug.augmenters.geometric.Affine` instead. [#540](https://github.com/aleju/imgaug/pull/540)
# Refactored
## Refactored According to pylint Requirements [#504](https://github.com/aleju/imgaug/pull/504)
* Refactored all core library files to fulfill (most) pylint requirements.
* [rarely breaking] Renamed
`imgaug.augmenters.size.KeepSizeByResize.get_shapes()` to `_get_shapes()`.
* Added a project-specific pylint configuration.
# Fixed
* Fixed `Resize` always returning an `uint8` array during image augmentation
if the input was a single numpy array and all augmented images had the
same shape. [#442](https://github.com/aleju/imgaug/pull/442) [#443](https://github.com/aleju/imgaug/pull/443)
* Fixed `Affine` coordinate-based augmentation applying wrong offset
when shifting images to/from top-left corner. This would lead to an error
of around 0.5 to 1.0 pixels. [#446](https://github.com/aleju/imgaug/pull/446)
* Fixed keypoint augmentation in `PiecewiseAffine` potentially being
unaligned if a `KeypointsOnImage` instance contained no keypoints. [#446](https://github.com/aleju/imgaug/pull/446)
* Fixed `imgaug.validation.convert_iterable_to_string_of_types()` crashing due
to not converting types to strings before joining them. [#446](https://github.com/aleju/imgaug/pull/446)
* Fixed `imgaug.validation.assert_is_iterable_of()` producing a not
well-designed error if the input was not an iterable. [#446](https://github.com/aleju/imgaug/pull/446)
* Fixed image normalization crashing when an input ndarray of multiple images
was changed during augmentation to a list of multiple images with different
shapes *and* the original input ndarray represented a single image or
a collection of 2D `(H,W)` images. This problem affected `augment()`,
`augment_batch()` and `augment_batches()`.
* Fixed a typo in an image normalization error message. [#451](https://github.com/aleju/imgaug/pull/451)
* Fixed a problem in `WithChannels` that could lead random sampling in child
augmenters being unaligned between images and corresponding non-image
data. [#451](https://github.com/aleju/imgaug/pull/451)
* Added aliases to `imgaug.random.RNG` for some outdated numpy random number
sampling methods that existed in `numpy.random.RandomState` but not in
numpy's new RNG system (1.17+). These old methods are not used in `imgaug`,
but some custom augmenters and `Lambda` calls may require them when
interacting with the provided `random_state` instances. [#486](https://github.com/aleju/imgaug/pull/486)
* Fixed `Affine` producing unaligned augmentations between images and
segmentation maps or heatmaps when using `translate_px` and the segmentation
map or heatmap had a different height/width than corresponding image. [#489](https://github.com/aleju/imgaug/pull/489)
* Fixed a crash in `SnowflakesLayer` that could occur when using values
close to `1.0` for `flake_size`. [#471](https://github.com/aleju/imgaug/pull/471)
* Fixed `MultiplyHueAndSaturation` crashing if the RNG provided via
`random_state` was not `None` or `imgaug.random.RNG`. [#493](https://github.com/aleju/imgaug/pull/493)
* Fixed `CloudLayer.draw_on_image()` producing tuples instead of arrays
as output for `float` input images. [#540](https://github.com/aleju/imgaug/pull/540)
* Fixed `Affine` parameter `translate_px` behaving like `translate_percent`
if a continuous stochastic parameter was provided.
Analogously `translate_percent` would behave like `translate_px` if
a discrete stochastic parameter was provided. [#508](https://github.com/aleju/imgaug/pull/508)
* Fixed code hanging indefinitely when using multicore augmentation
on NixOS. [#414](https://github.com/aleju/imgaug/issues/414) [#510](https://github.com/aleju/imgaug/pull/510)
* Fixed a deprecation warning and potential crash in python 3.8
related to the use of `collections` instead of `collections.abc`. [#527](https://github.com/aleju/imgaug/pull/527)
* Fixed deprecated `scipy.fromfunction()` being called. [#529](https://github.com/aleju/imgaug/pull/529)
* Fixed `imgaug.random.normalize_generator()` crashing in numpy 1.18.
The function relied on `numpy.random.bit_generator.BitGenerator`, which
was moved in numpy 1.18 to `numpy.random.BitGenerator` without a
deprecation period for the old name. [#534](https://github.com/aleju/imgaug/pull/534)
* Fixed an issue that could lead to endlessly hanging programs on some OS
when using multicore augmentation (e.g. via pool) and augmenters using
OpenCV. [#535](https://github.com/aleju/imgaug/pull/535)
* Fixed `imgaug.random.seed()` not seeding the global `RNG` in-place
in numpy 1.17+. The (unfixed) function instead created a new
global `RNG` with the given seed. This set the seed of augmenters
created *after* the `seed()` call, but not of augmenters created
*before* the `seed()` call as they would continue to use the old
global RNG. [#557](https://github.com/aleju/imgaug/pull/557)
* Fixed `cval` in `ElasticTransformation` resulting in new pixels in RGB images
being filled with `(cval, 0, 0)` instead of `(cval, cval, cval)`. [#561](https://github.com/aleju/imgaug/pull/561) [#562](https://github.com/aleju/imgaug/pull/562)
* Fixed some augmenters in module `weather` not transferring seed values
or random states that were provided upon creation to child augmenters. [#568](https://github.com/aleju/imgaug/pull/568)
* Fixed an inaccuracy in `PerspectiveTransform` that could lead to slightly
misaligned transformations between images and coordinate-based
augmentables (e.g. bounding boxes). The problem was more significant the
smaller the images and larger the `scale` values were. It was also
worsened by using `fit_output`. [#585](https://github.com/aleju/imgaug/pull/585)
* Fixed `KeepSizeByResize` potentially crashing if a single numpy array
was provided as the input for an iterable of images (as opposed to
a list of numpy arrays). [#590](https://github.com/aleju/imgaug/pull/590)
================================================
FILE: checks/README.md
================================================
These are checks for the library.
In contrast to tests they are expected to be executed by hand.
Their results have to be manually investigated (usually by looking at images).
================================================
FILE: checks/check_add_to_hue_and_saturation.py
================================================
from __future__ import print_function, division
import numpy as np
from skimage import data
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
VAL_PER_STEP = 1
TIME_PER_STEP = 10
def main():
image = data.astronaut()
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.imshow("aug", image)
cv2.waitKey(TIME_PER_STEP)
# for value in cycle(np.arange(-255, 255, VAL_PER_STEP)):
for value in np.arange(-255, 255, VAL_PER_STEP):
aug = iaa.AddToHueAndSaturation(value=value)
img_aug = aug.augment_image(image)
img_aug = iaa.pad(img_aug, bottom=40)
img_aug = ia.draw_text(img_aug, x=0, y=img_aug.shape[0]-38, text="value=%d" % (value,), size=30)
cv2.imshow("aug", img_aug)
cv2.waitKey(TIME_PER_STEP)
images_aug = iaa.AddToHueAndSaturation(value=(-255, 255), per_channel=True).augment_images([image] * 64)
ia.imshow(ia.draw_grid(images_aug))
image = ia.quokka_square((128, 128))
images_aug = []
images_aug.extend(iaa.AddToHue().augment_images([image] * 10))
images_aug.extend(iaa.AddToSaturation().augment_images([image] * 10))
ia.imshow(ia.draw_grid(images_aug, rows=2))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_affine.py
================================================
from __future__ import print_function, division
import imageio
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
NB_ROWS = 10
NB_COLS = 10
HEIGHT = 200
WIDTH = 256
BB_X1 = 64
BB_X2 = WIDTH - 64
BB_Y1 = 64
BB_Y2 = HEIGHT - 64
def main():
image = data.astronaut()
image = ia.imresize_single_image(image, (HEIGHT, WIDTH))
# testing new shear on x-/y-axis
shear_x = [iaa.Affine(shear=shear)(image=image)
for shear in np.linspace(0, 45, 10)]
shear_y = [iaa.Affine(shear={"y": shear})(image=image)
for shear in np.linspace(0, 45, 10)]
ia.imshow(
ia.draw_grid(shear_x + shear_y, cols=10, rows=2)
)
ia.imshow(
ia.draw_grid(
iaa.Affine(shear=(-45, 45))(images=[image]*16)
)
)
ia.imshow(
ia.draw_grid(
iaa.Affine(shear=[-45, -20, 0, 20, 45])(images=[image]*16)
)
)
ia.imshow(
ia.draw_grid(
iaa.Affine(shear={"y": (-45, 45)})(images=[image]*16)
)
)
kps = []
for y in range(NB_ROWS):
ycoord = BB_Y1 + int(y * (BB_Y2 - BB_Y1) / (NB_COLS - 1))
for x in range(NB_COLS):
xcoord = BB_X1 + int(x * (BB_X2 - BB_X1) / (NB_ROWS - 1))
kp = (xcoord, ycoord)
kps.append(kp)
kps = set(kps)
kps = [ia.Keypoint(x=xcoord, y=ycoord) for (xcoord, ycoord) in kps]
kps = ia.KeypointsOnImage(kps, shape=image.shape)
bb = ia.BoundingBox(x1=BB_X1, x2=BB_X2, y1=BB_Y1, y2=BB_Y2)
bbs = ia.BoundingBoxesOnImage([bb], shape=image.shape)
pairs = []
params = [
{"rotate": 45},
{"translate_px": 20},
{"translate_percent": 0.1},
{"scale": 1.2},
{"scale": 0.8},
{"shear": 45},
{"rotate": 45, "cval": 255},
{"translate_px": 20, "mode": "constant"},
{"translate_px": 20, "mode": "edge"},
{"translate_px": 20, "mode": "symmetric"},
{"translate_px": 20, "mode": "reflect"},
{"translate_px": 20, "mode": "wrap"},
{"scale": 0.5, "order": 0},
{"scale": 0.5, "order": 1},
{"scale": 0.5, "order": 2},
{"scale": 0.5, "order": 3},
{"scale": 0.5, "order": 4},
{"scale": 0.5, "order": 5},
{"rotate": 45, "translate_px": 20, "scale": 1.2},
{"rotate": 45, "translate_px": 20, "scale": 0.8},
{"rotate": (-45, 45), "translate_px": (-20, 20), "scale": (0.8, 1.2), "order": ia.ALL,
"mode": ia.ALL, "cval": ia.ALL},
{"rotate": (-45, 45), "translate_px": (-20, 20), "scale": (0.8, 1.2), "order": ia.ALL,
"mode": ia.ALL, "cval": ia.ALL},
{"rotate": (-45, 45), "translate_px": (-20, 20), "scale": (0.8, 1.2), "order": ia.ALL,
"mode": ia.ALL, "cval": ia.ALL},
{"rotate": (-45, 45), "translate_px": (-20, 20), "scale": (0.8, 1.2), "order": ia.ALL,
"mode": ia.ALL, "cval": ia.ALL}
]
seqs_skimage = [iaa.Affine(backend="skimage", **p) for p in params]
seqs_cv2 = [iaa.Affine(backend="auto", **p) for p in params]
for seq_skimage, seq_cv2 in zip(seqs_skimage, seqs_cv2):
seq_skimage_det = seq_skimage.to_deterministic()
seq_cv2_det = seq_cv2.to_deterministic()
seq_cv2_det.copy_random_state_(seq_skimage_det)
image_aug_skimage = seq_skimage_det.augment_image(image)
image_aug_cv2 = seq_cv2_det.augment_image(image)
kps_aug_skimage = seq_skimage_det.augment_keypoints([kps])[0]
kps_aug_cv2 = seq_cv2_det.augment_keypoints([kps])[0]
bbs_aug_skimage = seq_skimage_det.augment_bounding_boxes([bbs])[0]
bbs_aug_cv2 = seq_cv2_det.augment_bounding_boxes([bbs])[0]
image_before_skimage = np.copy(image)
image_before_cv2 = np.copy(image)
image_before_skimage = kps.draw_on_image(image_before_skimage)
image_before_cv2 = kps.draw_on_image(image_before_cv2)
image_before_skimage = bbs.draw_on_image(image_before_skimage)
image_before_cv2 = bbs.draw_on_image(image_before_cv2)
image_after_skimage = np.copy(image_aug_skimage)
image_after_cv2 = np.copy(image_aug_cv2)
image_after_skimage = kps_aug_skimage.draw_on_image(image_after_skimage)
image_after_cv2 = kps_aug_cv2.draw_on_image(image_after_cv2)
image_after_skimage = bbs_aug_skimage.draw_on_image(image_after_skimage)
image_after_cv2 = bbs_aug_cv2.draw_on_image(image_after_cv2)
pairs.append(np.hstack((image_before_skimage, image_after_skimage, image_after_cv2)))
ia.imshow(np.vstack(pairs))
imageio.imwrite("affine.jpg", np.vstack(pairs))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_affinecv2.py
================================================
from __future__ import print_function, division
import imgaug as ia
from imgaug import augmenters as iaa
import imageio
import numpy as np
from skimage import data
import cv2
NB_ROWS = 10
NB_COLS = 10
HEIGHT = 200
WIDTH = 256
BB_X1 = 64
BB_X2 = WIDTH - 64
BB_Y1 = 64
BB_Y2 = HEIGHT - 64
def main():
image = data.astronaut()
image = ia.imresize_single_image(image, (HEIGHT, WIDTH))
kps = []
for y in range(NB_ROWS):
ycoord = BB_Y1 + int(y * (BB_Y2 - BB_Y1) / (NB_COLS - 1))
for x in range(NB_COLS):
xcoord = BB_X1 + int(x * (BB_X2 - BB_X1) / (NB_ROWS - 1))
kp = (xcoord, ycoord)
kps.append(kp)
kps = set(kps)
kps = [ia.Keypoint(x=xcoord, y=ycoord) for (xcoord, ycoord) in kps]
kps = ia.KeypointsOnImage(kps, shape=image.shape)
bb = ia.BoundingBox(x1=BB_X1, x2=BB_X2, y1=BB_Y1, y2=BB_Y2)
bbs = ia.BoundingBoxesOnImage([bb], shape=image.shape)
pairs = []
seqs = [
iaa.AffineCv2(rotate=45),
iaa.AffineCv2(translate_px=20),
iaa.AffineCv2(translate_percent=0.1),
iaa.AffineCv2(scale=1.2),
iaa.AffineCv2(scale=0.8),
iaa.AffineCv2(shear=45),
iaa.AffineCv2(rotate=45, cval=256),
iaa.AffineCv2(translate_px=20, mode=cv2.BORDER_CONSTANT),
iaa.AffineCv2(translate_px=20, mode=cv2.BORDER_REPLICATE),
iaa.AffineCv2(translate_px=20, mode=cv2.BORDER_REFLECT),
iaa.AffineCv2(translate_px=20, mode=cv2.BORDER_REFLECT_101),
iaa.AffineCv2(translate_px=20, mode=cv2.BORDER_WRAP),
iaa.AffineCv2(translate_px=20, mode="constant"),
iaa.AffineCv2(translate_px=20, mode="replicate"),
iaa.AffineCv2(translate_px=20, mode="reflect"),
iaa.AffineCv2(translate_px=20, mode="reflect_101"),
iaa.AffineCv2(translate_px=20, mode="wrap"),
iaa.AffineCv2(scale=0.5, order=cv2.INTER_NEAREST),
iaa.AffineCv2(scale=0.5, order=cv2.INTER_LINEAR),
iaa.AffineCv2(scale=0.5, order=cv2.INTER_CUBIC),
iaa.AffineCv2(scale=0.5, order=cv2.INTER_LANCZOS4),
iaa.AffineCv2(scale=0.5, order="nearest"),
iaa.AffineCv2(scale=0.5, order="linear"),
iaa.AffineCv2(scale=0.5, order="cubic"),
iaa.AffineCv2(scale=0.5, order="lanczos4"),
iaa.AffineCv2(rotate=45, translate_px=20, scale=1.2),
iaa.AffineCv2(rotate=45, translate_px=20, scale=0.8),
iaa.AffineCv2(rotate=(-45, 45), translate_px=(-20, 20), scale=(0.8, 1.2), order=ia.ALL, mode=ia.ALL, cval=ia.ALL),
iaa.AffineCv2(rotate=(-45, 45), translate_px=(-20, 20), scale=(0.8, 1.2), order=ia.ALL, mode=ia.ALL, cval=ia.ALL),
iaa.AffineCv2(rotate=(-45, 45), translate_px=(-20, 20), scale=(0.8, 1.2), order=ia.ALL, mode=ia.ALL, cval=ia.ALL),
iaa.AffineCv2(rotate=(-45, 45), translate_px=(-20, 20), scale=(0.8, 1.2), order=ia.ALL, mode=ia.ALL, cval=ia.ALL)
]
for seq in seqs:
seq_det = seq.to_deterministic()
image_aug = seq_det.augment_image(image)
#print(image_aug.dtype, np.min(image_aug), np.max(image_aug))
kps_aug = seq_det.augment_keypoints([kps])[0]
bbs_aug = seq_det.augment_bounding_boxes([bbs])[0]
image_before = np.copy(image)
image_before = kps.draw_on_image(image_before)
image_before = bbs.draw_on_image(image_before)
image_after = np.copy(image_aug)
image_after = kps_aug.draw_on_image(image_after)
image_after = bbs_aug.draw_on_image(image_after)
pairs.append(np.hstack((image_before, image_after)))
ia.imshow(np.vstack(pairs))
imageio.imwrite("affinecv2.jpg", np.vstack(pairs))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_average_blur.py
================================================
from __future__ import print_function, division
import numpy as np
from skimage import data
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
TIME_PER_STEP = 5000
NB_AUGS_PER_IMAGE = 10
def main():
image = data.astronaut()
image = ia.imresize_single_image(image, (64, 64))
print("image shape:", image.shape)
print("Press any key or wait %d ms to proceed to the next image." % (TIME_PER_STEP,))
k = [
1,
2,
4,
8,
16,
(8, 8),
(1, 8),
((1, 1), (8, 8)),
((1, 16), (1, 16)),
((1, 16), 1)
]
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.resizeWindow("aug", 64*NB_AUGS_PER_IMAGE, 64)
for ki in k:
aug = iaa.AverageBlur(k=ki)
img_aug = [aug.augment_image(image) for _ in range(NB_AUGS_PER_IMAGE)]
img_aug = np.hstack(img_aug)
print("dtype", img_aug.dtype, "averages", np.average(img_aug, axis=tuple(range(0, img_aug.ndim-1))))
title = "k=%s" % (str(ki),)
img_aug = ia.draw_text(img_aug, x=5, y=5, text=title)
cv2.imshow("aug", img_aug[..., ::-1]) # here with rgb2bgr
cv2.waitKey(TIME_PER_STEP)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_background_augmentation.py
================================================
from __future__ import print_function, division
import time
import numpy as np
from skimage import data
import imgaug as ia
import imgaug.multicore as multicore
from imgaug import augmenters as iaa
def main():
augseq = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.CoarseDropout(p=0.1, size_percent=0.1)
])
def func_images(images, random_state, parents, hooks):
time.sleep(0.2)
return images
def func_heatmaps(heatmaps, random_state, parents, hooks):
return heatmaps
def func_keypoints(keypoints_on_images, random_state, parents, hooks):
return keypoints_on_images
augseq_slow = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.Lambda(
func_images=func_images,
func_heatmaps=func_heatmaps,
func_keypoints=func_keypoints
)
])
print("------------------")
print("augseq.augment_batches(batches, background=True)")
print("------------------")
batches = list(load_images())
batches_aug = augseq.augment_batches(batches, background=True)
images_aug = []
keypoints_aug = []
for batch_aug in batches_aug:
images_aug.append(batch_aug.images_aug)
keypoints_aug.append(batch_aug.keypoints_aug)
ia.imshow(draw_grid(images_aug, keypoints_aug))
print("------------------")
print("augseq.augment_batches(batches, background=True) -> only images")
print("------------------")
batches = list(load_images())
batches = [batch.images_unaug for batch in batches]
batches_aug = augseq.augment_batches(batches, background=True)
images_aug = []
keypoints_aug = None
for batch_aug in batches_aug:
images_aug.append(batch_aug)
ia.imshow(draw_grid(images_aug, keypoints_aug))
print("------------------")
print("BackgroundAugmenter")
print("------------------")
batch_loader = multicore.BatchLoader(load_images)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq)
images_aug = []
keypoints_aug = []
while True:
print("Next batch...")
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished.")
break
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
ia.imshow(draw_grid(images_aug, keypoints_aug))
print("------------------")
print("BackgroundAugmenter with generator in BL")
print("------------------")
batch_loader = multicore.BatchLoader(load_images())
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq)
images_aug = []
keypoints_aug = []
while True:
print("Next batch...")
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished.")
break
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
ia.imshow(draw_grid(images_aug, keypoints_aug))
print("------------------")
print("Long running BackgroundAugmenter at BL-queue_size=12")
print("------------------")
batch_loader = multicore.BatchLoader(load_images(n_batches=1000), queue_size=12)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq)
i = 0
while True:
if i % 100 == 0:
print("batch=%d..." % (i,))
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished.")
break
i += 1
print("------------------")
print("Long running BackgroundAugmenter at BL-queue_size=2")
print("------------------")
batch_loader = multicore.BatchLoader(load_images(n_batches=1000), queue_size=2)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq)
i = 0
while True:
if i % 100 == 0:
print("batch=%d..." % (i,))
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished.")
break
i += 1
print("------------------")
print("Long running BackgroundAugmenter (slow loading)")
print("------------------")
batch_loader = multicore.BatchLoader(load_images(n_batches=100, sleep=0.2))
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq)
i = 0
while True:
if i % 10 == 0:
print("batch=%d..." % (i,))
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished.")
break
i += 1
print("------------------")
print("Long running BackgroundAugmenter (slow aug) at BL-queue_size=12")
print("------------------")
batch_loader = multicore.BatchLoader(load_images(n_batches=100), queue_size=12)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq_slow)
i = 0
while True:
if i % 10 == 0:
print("batch=%d..." % (i,))
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished.")
break
i += 1
print("------------------")
print("Long running BackgroundAugmenter (slow aug) at BL-queue_size=2")
print("------------------")
batch_loader = multicore.BatchLoader(load_images(n_batches=100), queue_size=2)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq_slow)
i = 0
while True:
if i % 10 == 0:
print("batch=%d..." % (i,))
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished.")
break
i += 1
for augseq_i in [augseq, augseq_slow]:
print("------------------")
print("Many very small runs (batches=1)")
print("------------------")
for i in range(100):
batch_loader = multicore.BatchLoader(load_images(n_batches=1), queue_size=100)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq_i)
while True:
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished (%d/%d)." % (i+1, 100))
break
print("------------------")
print("Many very small runs (batches=2)")
print("------------------")
for i in range(100):
batch_loader = multicore.BatchLoader(load_images(n_batches=2), queue_size=100)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq_i)
while True:
batch = bg_augmenter.get_batch()
if batch is None:
print("Finished (%d/%d)." % (i+1, 100))
break
print("------------------")
print("Many very small runs, separate function (batches=1)")
print("------------------")
def _augment_small_1():
batch_loader = multicore.BatchLoader(load_images(n_batches=1), queue_size=100)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq_i)
i = 0
while True:
batch = bg_augmenter.get_batch()
if batch is None:
break
i += 1
for i in range(100):
_augment_small_1()
print("Finished (%d/%d)." % (i+1, 100))
print("------------------")
print("Many very small runs, separate function (batches=2)")
print("------------------")
def _augment_small_2():
batch_loader = multicore.BatchLoader(load_images(n_batches=2), queue_size=100)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq_i)
i = 0
while True:
batch = bg_augmenter.get_batch()
if batch is None:
break
i += 1
for i in range(100):
_augment_small_2()
print("Finished (%d/%d)." % (i+1, 100))
print("------------------")
print("Many very small runs, separate function, incomplete fetching (batches=2)")
print("------------------")
def _augment_small_3():
batch_loader = multicore.BatchLoader(load_images(n_batches=2), queue_size=100)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq_i)
batch = bg_augmenter.get_batch()
for i in range(100):
_augment_small_3()
print("Finished (%d/%d)." % (i+1, 100))
#for augseq_i in [augseq, augseq_slow]:
print("------------------")
print("Many very small runs, separate function, incomplete fetching (batches=10)")
print("------------------")
def _augment_small_4():
batch_loader = multicore.BatchLoader(load_images(n_batches=10), queue_size=100)
bg_augmenter = multicore.BackgroundAugmenter(batch_loader, augseq_i)
batch = bg_augmenter.get_batch()
#bg_augmenter.terminate()
for i in range(100):
_augment_small_4()
print("Finished (%d/%d)." % (i+1, 100))
def load_images(n_batches=10, sleep=0.0):
batch_size = 4
astronaut = data.astronaut()
astronaut = ia.imresize_single_image(astronaut, (64, 64))
kps = ia.KeypointsOnImage([ia.Keypoint(x=15, y=25)], shape=astronaut.shape)
counter = 0
for i in range(n_batches):
batch_images = []
batch_kps = []
for b in range(batch_size):
astronaut_text = ia.draw_text(astronaut, x=0, y=0, text="%d" % (counter,), color=[0, 255, 0], size=16)
batch_images.append(astronaut_text)
batch_kps.append(kps)
counter += 1
batch = ia.Batch(
images=np.array(batch_images, dtype=np.uint8),
keypoints=batch_kps
)
yield batch
if sleep > 0:
time.sleep(sleep)
def draw_grid(images_aug, keypoints_aug):
if keypoints_aug is None:
keypoints_aug = []
for bidx in range(len(images_aug)):
keypoints_aug.append([None for image in images_aug[bidx]])
images_kps_batches = []
for bidx in range(len(images_aug)):
images_kps_batch = []
for image, kps in zip(images_aug[bidx], keypoints_aug[bidx]):
if kps is None:
image_kps = image
else:
image_kps = kps.draw_on_image(image, size=5, color=[255, 0, 0])
images_kps_batch.append(image_kps)
images_kps_batches.extend(images_kps_batch)
grid = ia.draw_grid(images_kps_batches, cols=len(images_aug[0]))
return grid
if __name__ == "__main__":
main()
================================================
FILE: checks/check_bb_augmentation.py
================================================
from __future__ import print_function, division
import imageio
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
NB_ROWS = 10
NB_COLS = 10
HEIGHT = 256
WIDTH = 256
BB_X1 = 64
BB_X2 = WIDTH - 64
BB_Y1 = 64
BB_Y2 = HEIGHT - 64
def main():
image = data.astronaut()
image = ia.imresize_single_image(image, (HEIGHT, WIDTH))
kps = []
for y in range(NB_ROWS):
ycoord = BB_Y1 + int(y * (BB_Y2 - BB_Y1) / (NB_COLS - 1))
for x in range(NB_COLS):
xcoord = BB_X1 + int(x * (BB_X2 - BB_X1) / (NB_ROWS - 1))
kp = (xcoord, ycoord)
kps.append(kp)
kps = set(kps)
kps = [ia.Keypoint(x=xcoord, y=ycoord) for (xcoord, ycoord) in kps]
kps = ia.KeypointsOnImage(kps, shape=image.shape)
bb = ia.BoundingBox(x1=BB_X1, x2=BB_X2, y1=BB_Y1, y2=BB_Y2)
bbs = ia.BoundingBoxesOnImage([bb], shape=image.shape)
seq = iaa.Affine(rotate=45)
seq_det = seq.to_deterministic()
image_aug = seq_det.augment_image(image)
kps_aug = seq_det.augment_keypoints([kps])[0]
bbs_aug = seq_det.augment_bounding_boxes([bbs])[0]
image_before = np.copy(image)
image_before = kps.draw_on_image(image_before)
image_before = bbs.draw_on_image(image_before)
image_after = np.copy(image_aug)
image_after = kps_aug.draw_on_image(image_after)
image_after = bbs_aug.draw_on_image(image_after)
ia.imshow(np.hstack([image_before, image_after]))
imageio.imwrite("bb_aug.jpg", np.hstack([image_before, image_after]))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_bilateral_blur.py
================================================
from __future__ import print_function, division
import imgaug as ia
from imgaug import augmenters as iaa
import numpy as np
from skimage import data
import cv2
TIME_PER_STEP = 5000
NB_AUGS_PER_IMAGE = 10
def main():
image = data.astronaut()
image = ia.imresize_single_image(image, (128, 128))
print("image shape:", image.shape)
print("Press any key or wait %d ms to proceed to the next image." % (TIME_PER_STEP,))
configs = [
(1, 75, 75),
(3, 75, 75),
(5, 75, 75),
(10, 75, 75),
(10, 25, 25),
(10, 250, 150),
(15, 75, 75),
(15, 150, 150),
(15, 250, 150),
(20, 75, 75),
(40, 150, 150),
((1, 5), 75, 75),
(5, (10, 250), 75),
(5, 75, (10, 250)),
(5, (10, 250), (10, 250)),
(10, (10, 250), (10, 250)),
]
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.resizeWindow("aug", 128*NB_AUGS_PER_IMAGE, 128)
for (d, sigma_color, sigma_space) in configs:
aug = iaa.BilateralBlur(d=d, sigma_color=sigma_color, sigma_space=sigma_space)
img_aug = [aug.augment_image(image) for _ in range(NB_AUGS_PER_IMAGE)]
img_aug = np.hstack(img_aug)
print("dtype", img_aug.dtype, "averages", np.average(img_aug, axis=tuple(range(0, img_aug.ndim-1))))
title = "d=%s, sc=%s, ss=%s" % (str(d), str(sigma_color), str(sigma_space))
img_aug = ia.draw_text(img_aug, x=5, y=5, text=title)
cv2.imshow("aug", img_aug[..., ::-1]) # here with rgb2bgr
cv2.waitKey(TIME_PER_STEP)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_blendalphasegmapclassids.py
================================================
from __future__ import print_function, division, absolute_import
import imageio
import imgaug as ia
import imgaug.augmenters as iaa
def main():
aug = iaa.BlendAlphaMask(
iaa.SegMapClassIdsMaskGen(1),
iaa.OneOf([
iaa.TotalDropout(1.0),
iaa.AveragePooling(8)
])
)
aug2 = iaa.BlendAlphaSegMapClassIds(
1, iaa.OneOf([
iaa.TotalDropout(1.0),
iaa.AveragePooling(8)
])
)
image = ia.data.quokka(0.25)
segmap = ia.data.quokka_segmentation_map(0.25)
images_aug, segmaps_aug = aug(images=[image]*25,
segmentation_maps=[segmap]*25)
ia.imshow(ia.draw_grid(images_aug, cols=5, rows=5))
images_aug, segmaps_aug = aug2(images=[image]*25,
segmentation_maps=[segmap]*25)
ia.imshow(ia.draw_grid(images_aug, cols=5, rows=5))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_blendalphasomecolors.py
================================================
from __future__ import print_function, division, absolute_import
import imageio
import imgaug as ia
import imgaug.augmenters as iaa
def main():
aug = iaa.BlendAlphaMask(
iaa.SomeColorsMaskGen(),
iaa.OneOf([
iaa.TotalDropout(1.0),
iaa.AveragePooling(8)
])
)
aug2 = iaa.BlendAlphaSomeColors(iaa.OneOf([
iaa.TotalDropout(1.0),
iaa.AveragePooling(8)
]))
urls = [
("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/"
"Sarcophilus_harrisii_taranna.jpg/"
"320px-Sarcophilus_harrisii_taranna.jpg"),
("https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/"
"Vincent_van_Gogh_-_Wheatfield_with_crows_-_Google_Art_Project.jpg/"
"320px-Vincent_van_Gogh_-_Wheatfield_with_crows_-_Google_Art_Project"
".jpg"),
("https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/"
"Galerella_sanguinea_Zoo_Praha_2011-2.jpg/207px-Galerella_sanguinea_"
"Zoo_Praha_2011-2.jpg"),
("https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/"
"Ambrosius_Bosschaert_the_Elder_%28Dutch_-_Flower_Still_Life_-_"
"Google_Art_Project.jpg/307px-Ambrosius_Bosschaert_the_Elder_%28"
"Dutch_-_Flower_Still_Life_-_Google_Art_Project.jpg")
]
for url in urls:
img = imageio.imread(url)
ia.imshow(ia.draw_grid(aug(images=[img]*25), cols=5, rows=5))
ia.imshow(ia.draw_grid(aug2(images=[img]*25), cols=5, rows=5))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_brightness.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
def main():
image = ia.quokka_square(size=(100, 100))
for cspace in iaa.WithBrightnessChannels._VALID_COLORSPACES:
print(cspace, "add")
images_aug = []
for add in np.linspace(-200, 200, 64):
aug = iaa.MultiplyAndAddToBrightness(add=add, mul=1.0,
to_colorspace=cspace)
images_aug.append(aug(image=image))
ia.imshow(ia.draw_grid(images_aug))
for cspace in iaa.WithBrightnessChannels._VALID_COLORSPACES:
print(cspace, "mul")
images_aug = []
for mul in np.linspace(0.5, 1.5, 64):
aug = iaa.MultiplyAndAddToBrightness(add=0, mul=mul,
to_colorspace=cspace)
images_aug.append(aug(image=image))
ia.imshow(ia.draw_grid(images_aug))
for cspace in iaa.WithBrightnessChannels._VALID_COLORSPACES:
print(cspace, "defaults")
aug = iaa.MultiplyAndAddToBrightness(to_colorspace=cspace)
images_aug = aug(images=[image] * 64)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_canny.py
================================================
from __future__ import print_function, division
import imgaug as ia
import imgaug.augmenters as iaa
def main():
black_and_white = iaa.RandomColorsBinaryImageColorizer(
color_true=255, color_false=0)
print("alpha=1.0, black and white")
image = ia.quokka_square((128, 128))
aug = iaa.Canny(alpha=1.0, colorizer=black_and_white)
ia.imshow(ia.draw_grid(aug(images=[image] * (5*5))))
print("alpha=1.0, random color")
image = ia.quokka_square((128, 128))
aug = iaa.Canny(alpha=1.0)
ia.imshow(ia.draw_grid(aug(images=[image] * (5*5))))
print("alpha=1.0, sobel ksize=[3, 13], black and white")
image = ia.quokka_square((128, 128))
aug = iaa.Canny(alpha=1.0, sobel_kernel_size=[3, 7],
colorizer=black_and_white)
ia.imshow(ia.draw_grid(aug(images=[image] * (5*5))))
print("alpha=1.0, sobel ksize=3, black and white")
image = ia.quokka_square((128, 128))
aug = iaa.Canny(alpha=1.0, sobel_kernel_size=3,
colorizer=black_and_white)
ia.imshow(ia.draw_grid(aug(images=[image] * (5*5))))
print("fully random")
image = ia.quokka_square((128, 128))
aug = iaa.Canny()
ia.imshow(ia.draw_grid(aug(images=[image] * (5*5))))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_cartoon.py
================================================
import imgaug as ia
import imgaug.augmenters as iaa
import imageio
import cv2
import numpy as np
def main():
urls_small = [
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/"
"Physicist_Stephen_Hawking_in_Zero_Gravity_NASA.jpg/"
"320px-Physicist_Stephen_Hawking_in_Zero_Gravity_NASA.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/"
"Barack_Obama_family_portrait_2011.jpg/320px-Barack_Obama_"
"family_portrait_2011.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/"
"Pahalgam_Valley.jpg/320px-Pahalgam_Valley.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/"
"Iglesia_de_Nuestra_Se%C3%B1ora_de_La_Blanca%2C_Cardej%C3%B3n%2C_"
"Espa%C3%B1a%2C_2012-09-01%2C_DD_02.JPG/320px-Iglesia_de_Nuestra_"
"Se%C3%B1ora_de_La_Blanca%2C_Cardej%C3%B3n%2C_Espa%C3%B1a%2C_"
"2012-09-01%2C_DD_02.JPG",
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/94/"
"Salad_platter.jpg/320px-Salad_platter.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/"
"Squirrel_posing.jpg/287px-Squirrel_posing.jpg"
]
urls_medium = [
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/"
"Physicist_Stephen_Hawking_in_Zero_Gravity_NASA.jpg/"
"640px-Physicist_Stephen_Hawking_in_Zero_Gravity_NASA.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/"
"Barack_Obama_family_portrait_2011.jpg/640px-Barack_Obama_"
"family_portrait_2011.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/"
"Pahalgam_Valley.jpg/640px-Pahalgam_Valley.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/"
"Iglesia_de_Nuestra_Se%C3%B1ora_de_La_Blanca%2C_Cardej%C3%B3n%2C_"
"Espa%C3%B1a%2C_2012-09-01%2C_DD_02.JPG/640px-Iglesia_de_Nuestra_"
"Se%C3%B1ora_de_La_Blanca%2C_Cardej%C3%B3n%2C_Espa%C3%B1a%2C_"
"2012-09-01%2C_DD_02.JPG",
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/94/"
"Salad_platter.jpg/640px-Salad_platter.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/"
"Squirrel_posing.jpg/574px-Squirrel_posing.jpg"
]
urls_large = [
"https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/"
"Physicist_Stephen_Hawking_in_Zero_Gravity_NASA.jpg/"
"1024px-Physicist_Stephen_Hawking_in_Zero_Gravity_NASA.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/"
"Barack_Obama_family_portrait_2011.jpg/1024px-Barack_Obama_"
"family_portrait_2011.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/f/f6/"
"Pahalgam_Valley.jpg/1024px-Pahalgam_Valley.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/a/a4/"
"Iglesia_de_Nuestra_Se%C3%B1ora_de_La_Blanca%2C_Cardej%C3%B3n%2C_"
"Espa%C3%B1a%2C_2012-09-01%2C_DD_02.JPG/1024px-Iglesia_de_"
"Nuestra_Se%C3%B1ora_de_La_Blanca%2C_Cardej%C3%B3n%2C_"
"Espa%C3%B1a%2C_2012-09-01%2C_DD_02.JPG",
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/94/"
"Salad_platter.jpg/1024px-Salad_platter.jpg",
"https://upload.wikimedia.org/wikipedia/commons/thumb/1/1c/"
"Squirrel_posing.jpg/574px-Squirrel_posing.jpg"
]
image = imageio.imread(urls_medium[1])
augs = [image] + iaa.Cartoon()(images=[image] * 15)
ia.imshow(ia.draw_grid(augs, 4, 4))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_channel_shuffle.py
================================================
from imgaug import augmenters as iaa
import imgaug as ia
import imgaug.random as iarandom
iarandom.seed(1)
def main():
img = ia.data.quokka(size=(128, 128), extract="square")
aug = iaa.ChannelShuffle()
imgs_aug = aug.augment_images([img] * 64)
ia.imshow(ia.draw_grid(imgs_aug))
aug = iaa.ChannelShuffle(p=0.1)
imgs_aug = aug.augment_images([img] * 64)
ia.imshow(ia.draw_grid(imgs_aug))
aug = iaa.ChannelShuffle(p=1.0, channels=[0, 1])
imgs_aug = aug.augment_images([img] * 64)
ia.imshow(ia.draw_grid(imgs_aug))
aug = iaa.ChannelShuffle(p=1.0, channels=[1, 2])
imgs_aug = aug.augment_images([img] * 64)
ia.imshow(ia.draw_grid(imgs_aug))
aug = iaa.ChannelShuffle(p=1.0, channels=[1, 1, 2])
imgs_aug = aug.augment_images([img] * 64)
ia.imshow(ia.draw_grid(imgs_aug))
aug = iaa.ChannelShuffle(p=1.0, channels=ia.ALL)
imgs_aug = aug.augment_images([img] * 64)
ia.imshow(ia.draw_grid(imgs_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_clouds.py
================================================
from __future__ import print_function, division
import imageio
import imgaug as ia
from imgaug import augmenters as iaa
def main():
for size in [0.1, 0.2, 1.0]:
image = imageio.imread("https://upload.wikimedia.org/wikipedia/commons/8/89/Kukle%2CCzech_Republic..jpg",
format="jpg")
image = ia.imresize_single_image(image, size, "cubic")
print(image.shape)
augs = [
("iaa.Clouds()", iaa.Clouds())
]
for descr, aug in augs:
print(descr)
images_aug = aug.augment_images([image] * 64)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_color_temperature.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
def main():
image = ia.quokka_square()
images_aug = []
for kelvin in np.linspace(1000, 10000, 64):
images_aug.append(iaa.ChangeColorTemperature(kelvin)(image=image))
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_contrast.py
================================================
from __future__ import print_function, division
import argparse
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
def main():
parser = argparse.ArgumentParser(description="Contrast check script")
parser.add_argument("--per_channel", dest="per_channel", action="store_true")
args = parser.parse_args()
augs = []
for p in [0.25, 0.5, 1.0, 2.0, (0.5, 1.5), [0.5, 1.0, 1.5]]:
augs.append(("GammaContrast " + str(p), iaa.GammaContrast(p, per_channel=args.per_channel)))
for cutoff in [0.25, 0.5, 0.75]:
for gain in [5, 10, 15, 20, 25]:
augs.append(("SigmoidContrast " + str(cutoff) + " " + str(gain), iaa.SigmoidContrast(gain, cutoff, per_channel=args.per_channel)))
for gain in [0.0, 0.25, 0.5, 1.0, 2.0, (0.5, 1.5), [0.5, 1.0, 1.5]]:
augs.append(("LogContrast " + str(gain), iaa.LogContrast(gain, per_channel=args.per_channel)))
for alpha in [-1.0, 0.5, 0, 0.5, 1.0, 2.0, (0.5, 1.5), [0.5, 1.0, 1.5]]:
augs.append(("LinearContrast " + str(alpha), iaa.LinearContrast(alpha, per_channel=args.per_channel)))
augs.append(("AllChannelsHistogramEqualization", iaa.AllChannelsHistogramEqualization()))
augs.append(("HistogramEqualization (Lab)", iaa.HistogramEqualization(to_colorspace=iaa.HistogramEqualization.Lab)))
augs.append(("HistogramEqualization (HSV)", iaa.HistogramEqualization(to_colorspace=iaa.HistogramEqualization.HSV)))
augs.append(("HistogramEqualization (HLS)", iaa.HistogramEqualization(to_colorspace=iaa.HistogramEqualization.HLS)))
for clip_limit in [0.1, 1, 5, 10]:
for tile_grid_size_px in [3, 7]:
augs.append(("AllChannelsCLAHE %d %dx%d" % (clip_limit, tile_grid_size_px, tile_grid_size_px),
iaa.AllChannelsCLAHE(clip_limit=clip_limit, tile_grid_size_px=tile_grid_size_px,
per_channel=args.per_channel)))
for clip_limit in [1, 5, 10, 100, 200]:
for tile_grid_size_px in [3, 7, 15]:
augs.append(("CLAHE %d %dx%d" % (clip_limit, tile_grid_size_px, tile_grid_size_px),
iaa.CLAHE(clip_limit=clip_limit, tile_grid_size_px=tile_grid_size_px)))
images = [data.astronaut()] * 16
images = ia.imresize_many_images(np.uint8(images), (128, 128))
for name, aug in augs:
print("-----------")
print(name)
print("-----------")
images_aug = aug.augment_images(images)
images_aug[0] = images[0]
grid = ia.draw_grid(images_aug, rows=4, cols=4)
ia.imshow(grid)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_crop_and_pad.py
================================================
from __future__ import print_function, division
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
import numpy as np
def main():
image = ia.data.quokka(size=0.5)
kps = [ia.KeypointsOnImage(
[ia.Keypoint(x=245, y=203), ia.Keypoint(x=365, y=195), ia.Keypoint(x=313, y=269)],
shape=(image.shape[0]*2, image.shape[1]*2)
)]
kps[0] = kps[0].on(image.shape)
print("image shape:", image.shape)
augs = [
iaa.CropAndPad(px=50, name="pad-by-50px"),
iaa.CropAndPad(px=(10, 20, 30, 40), name="pad-by-10-20-30-40px"),
iaa.CropAndPad(percent=0.1, name="pad-by-01percent"),
iaa.CropAndPad(percent=(0.01, 0.02, 0.03, 0.04), name="pad-by-001-002-003-004percent"),
iaa.CropAndPad(px=-20, name="crop-by-20px"),
iaa.CropAndPad(px=(-10, -20, -30, -40), name="crop-by-10-20-30-40px"),
iaa.CropAndPad(percent=-0.1, name="crop-by-01percent"),
iaa.CropAndPad(percent=(-0.01, -0.02, -0.03, -0.04), name="crop-by-001-002-003-004percent")
]
augs_many = [
iaa.Crop(px=(0, 50), name="native-crop-0-to-50px"),
iaa.Crop(px=iap.DiscreteUniform(0, 50), name="native-crop-0-to-50px-iap"),
iaa.Pad(px=(0, 50), pad_mode="linear_ramp", pad_cval=(0, 255), name="native-pad-0-to-50px-pad-modes"),
iaa.CropAndPad(px=(0, 50), sample_independently=False, name="pad-by-0-to-50px-same"),
iaa.CropAndPad(px=(0, 50), name="pad-by-0-to-50px"),
iaa.CropAndPad(px=(0, 50), pad_mode=ia.ALL, pad_cval=(0, 255), name="pad-by-0-to-50px-random-pad-modes-cvals"),
iaa.CropAndPad(px=((0, 50), (0, 50), (0, 50), (0, 50)), name="pad-by-0-to-50px-each"),
iaa.CropAndPad(percent=(0, 0.1), sample_independently=False, name="pad-by-0-to-01percent-same"),
iaa.CropAndPad(percent=(0, 0.1), name="pad-by-0-to-01percent"),
iaa.CropAndPad(percent=(0, 0.1), pad_mode=ia.ALL, pad_cval=(0, 255),
name="pad-by-0-to-01percent-random-pad-modes-cvals"),
iaa.CropAndPad(percent=((0, 0.1), (0, 0.1), (0, 0.1), (0, 0.1)), name="pad-by-0-to-01percent-each"),
iaa.CropAndPad(px=(-50, 0), name="crop-by-50-to-0px"),
iaa.CropAndPad(px=((-50, 0), (-50, 0), (-50, 0), (-50, 0)), name="crop-by-50-to-0px-each"),
iaa.CropAndPad(percent=(-0.1, 0), name="crop-by-01-to-0percent"),
iaa.CropAndPad(percent=((-0.1, 0), (-0.1, 0), (-0.1, 0), (-0.1, 0)), name="crop-by-01-to-0percent-each"),
iaa.CropAndPad(px=(-50, 50), name="pad-and-crop-by-50px")
]
print("original", image.shape)
ia.imshow(kps[0].draw_on_image(image))
print("-----------------")
print("Same aug per image")
print("-----------------")
for aug in augs:
img_aug = aug.augment_image(image)
kps_aug = aug.augment_keypoints(kps)[0]
img_aug_kps = kps_aug.draw_on_image(img_aug)
print(aug.name, img_aug_kps.shape, img_aug_kps.shape[1]/img_aug_kps.shape[0])
ia.imshow(img_aug_kps)
print("-----------------")
print("Random aug per image")
print("-----------------")
for aug in augs_many:
images_aug = []
for _ in range(64):
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(image)
kps_aug = aug_det.augment_keypoints(kps)[0]
img_aug_kps = kps_aug.draw_on_image(img_aug)
img_aug_kps = np.pad(img_aug_kps, ((1, 1), (1, 1), (0, 0)), mode="constant", constant_values=255)
images_aug.append(img_aug_kps)
print(aug.name)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_cutout.py
================================================
from __future__ import print_function, division, absolute_import
import imgaug as ia
import imgaug.augmenters as iaa
def main():
aug = iaa.Cutout(fill_mode=["gaussian", "constant"], cval=(0, 255),
fill_per_channel=0.5)
image = ia.data.quokka()
images_aug = aug(images=[image] * 16)
ia.imshow(ia.draw_grid(images_aug, cols=4, rows=4))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_deprecation_warning.py
================================================
from __future__ import print_function, absolute_import, division
import imgaug as ia
class Dummy1(object):
@ia.deprecated(alt_func="Foo")
def __init__(self):
pass
class Dummy2(object):
@ia.deprecated(alt_func="Foo", comment="Some example comment.")
def __init__(self):
pass
class Dummy3(object):
def __init__(self):
pass
@ia.deprecated(alt_func="bar()",
comment="Some example comment.")
def foo(self):
pass
@ia.deprecated(alt_func="bar()", comment="Some example comment.")
def foo():
pass
def main():
Dummy1()
Dummy2()
Dummy3()
foo()
if __name__ == "__main__":
main()
================================================
FILE: checks/check_directed_edge_detect.py
================================================
from __future__ import print_function, division
from itertools import cycle
import numpy as np
from skimage import data
import cv2
from imgaug import augmenters as iaa
POINT_SIZE = 5
DEG_PER_STEP = 1
TIME_PER_STEP = 10
def main():
image = data.astronaut()
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.imshow("aug", image)
cv2.waitKey(TIME_PER_STEP)
height, width = image.shape[0], image.shape[1]
center_x = width // 2
center_y = height // 2
r = int(min(image.shape[0], image.shape[1]) / 3)
for deg in cycle(np.arange(0, 360, DEG_PER_STEP)):
rad = np.deg2rad(deg-90)
point_x = int(center_x + r * np.cos(rad))
point_y = int(center_y + r * np.sin(rad))
direction = deg / 360
aug = iaa.DirectedEdgeDetect(alpha=1.0, direction=direction)
img_aug = aug.augment_image(image)
img_aug[point_y-POINT_SIZE:point_y+POINT_SIZE+1, point_x-POINT_SIZE:point_x+POINT_SIZE+1, :] =\
np.array([0, 255, 0])
cv2.imshow("aug", img_aug)
cv2.waitKey(TIME_PER_STEP)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_elastic_transformation.py
================================================
from __future__ import print_function, division
import imageio
import numpy as np
import six.moves as sm
from skimage import data
from scipy import ndimage
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug.augmenters import meta
import imgaug.random as iarandom
def main():
image = data.astronaut()
image = ia.imresize_single_image(image, (128, 128))
# check if scipy and cv2 remap similarly
rs = iarandom.RNG(1)
aug_scipy = ElasticTransformationScipy(alpha=30, sigma=3, random_state=rs, deterministic=True)
aug_cv2 = ElasticTransformationCv2(alpha=30, sigma=3, random_state=rs, deterministic=True)
augs_scipy = aug_scipy.augment_images([image] * 8)
augs_cv2 = aug_cv2.augment_images([image] * 8)
ia.imshow(ia.draw_grid(augs_scipy + augs_cv2, rows=2))
# check behaviour for multiple consecutive batches
aug = iaa.ElasticTransformation(alpha=(5, 100), sigma=(3, 5))
images1 = aug(images=[np.copy(image) for _ in range(10)])
images2 = aug(images=[np.copy(image) for _ in range(10)])
images3 = aug(images=[np.copy(image) for _ in range(10)])
ia.imshow(ia.draw_grid(images1 + images2 + images3, rows=3))
print("alpha=vary, sigma=0.25")
augs = [iaa.ElasticTransformation(alpha=alpha, sigma=0.25) for alpha in np.arange(0.0, 50.0, 0.1)]
images_aug = [aug.augment_image(image) for aug in augs]
ia.imshow(ia.draw_grid(images_aug, cols=10))
print("alpha=vary, sigma=1.0")
augs = [iaa.ElasticTransformation(alpha=alpha, sigma=1.0) for alpha in np.arange(0.0, 50.0, 0.1)]
images_aug = [aug.augment_image(image) for aug in augs]
ia.imshow(ia.draw_grid(images_aug, cols=10))
print("alpha=vary, sigma=3.0")
augs = [iaa.ElasticTransformation(alpha=alpha, sigma=3.0) for alpha in np.arange(0.0, 50.0, 0.1)]
images_aug = [aug.augment_image(image) for aug in augs]
ia.imshow(ia.draw_grid(images_aug, cols=10))
print("alpha=vary, sigma=5.0")
augs = [iaa.ElasticTransformation(alpha=alpha, sigma=5.0) for alpha in np.arange(0.0, 50.0, 0.1)]
images_aug = [aug.augment_image(image) for aug in augs]
ia.imshow(ia.draw_grid(images_aug, cols=10))
print("alpha=1.0, sigma=vary")
augs = [iaa.ElasticTransformation(alpha=1.0, sigma=sigma) for sigma in np.arange(0.0, 50.0, 0.1)]
images_aug = [aug.augment_image(image) for aug in augs]
ia.imshow(ia.draw_grid(images_aug, cols=10))
print("alpha=10.0, sigma=vary")
augs = [iaa.ElasticTransformation(alpha=10.0, sigma=sigma) for sigma in np.arange(0.0, 50.0, 0.1)]
images_aug = [aug.augment_image(image) for aug in augs]
ia.imshow(ia.draw_grid(images_aug, cols=10))
kps = ia.KeypointsOnImage(
[ia.Keypoint(x=1, y=1),
ia.Keypoint(x=50, y=24), ia.Keypoint(x=42, y=96), ia.Keypoint(x=88, y=106), ia.Keypoint(x=88, y=53),
ia.Keypoint(x=0, y=0), ia.Keypoint(x=128, y=128), ia.Keypoint(x=-20, y=30), ia.Keypoint(x=20, y=-30),
ia.Keypoint(x=-20, y=-30)],
shape=image.shape
)
images = []
params = [
(0.0, 0.0),
(0.2, 0.2),
(2.0, 0.25),
(0.25, 3.0),
(2.0, 3.0),
(6.0, 3.0),
(12.0, 3.0),
(50.0, 5.0),
(100.0, 5.0),
(100.0, 10.0)
]
for (alpha, sigma) in params:
images_row = []
seqs_row = [
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=0, order=0),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=128, order=0),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=255, order=0),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=0, order=1),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=128, order=1),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=255, order=1),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=0, order=3),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=128, order=3),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="constant", cval=255, order=3),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="nearest", order=0),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="nearest", order=1),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="nearest", order=2),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="nearest", order=3),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="reflect", order=0),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="reflect", order=1),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="reflect", order=2),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="reflect", order=3),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="wrap", order=0),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="wrap", order=1),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="wrap", order=2),
iaa.ElasticTransformation(alpha=alpha, sigma=sigma, mode="wrap", order=3)
]
for seq in seqs_row:
seq_det = seq.to_deterministic()
image_aug = seq_det.augment_image(image)
kps_aug = seq_det.augment_keypoints([kps])[0]
image_aug_kp = np.copy(image_aug)
image_aug_kp = kps_aug.draw_on_image(image_aug_kp, size=3)
images_row.append(image_aug_kp)
images.append(np.hstack(images_row))
ia.imshow(np.vstack(images))
imageio.imwrite("elastic_transformations.jpg", np.vstack(images))
#
# These classes are copies of ElasticTransformation and either always remap via scipy or always via cv2
# This way, the remapping can be compared to make sure that scipy and cv2 lead to similar remapping behaviours
# (important if two arrays with different dtypes are supposed to be remapped similarly)
#
class ElasticTransformationScipy(iaa.ElasticTransformation):
@classmethod
def map_coordinates(cls, image, dx, dy, order=1, cval=0, mode="constant"):
# small debug message to be sure that the right function is executed
print("map_coordinates() with scipy")
if order == 0 and image.dtype.name in ["uint64", "int64"]:
raise Exception(("dtypes uint64 and int64 are only supported in ElasticTransformation for order=0, "
+ "got order=%d with dtype=%s.") % (order, image.dtype.name))
input_dtype = image.dtype
if image.dtype.name == "bool":
image = image.astype(np.float32)
elif order == 1 and image.dtype.name in ["int8", "int16", "int32"]:
image = image.astype(np.float64)
elif order >= 2 and image.dtype.name == "int8":
image = image.astype(np.int16)
elif order >= 2 and image.dtype.name == "int32":
image = image.astype(np.float64)
shrt_max = 32767
backend = "cv2"
if order == 0:
bad_dtype_cv2 = (image.dtype.name in ["uint32", "uint64", "int64", "float128", "bool"])
elif order == 1:
bad_dtype_cv2 = (image.dtype.name in ["uint32", "uint64", "int8", "int16", "int32", "int64", "float128",
"bool"])
else:
bad_dtype_cv2 = (image.dtype.name in ["uint32", "uint64", "int8", "int32", "int64", "float128", "bool"])
bad_dx_shape_cv2 = (dx.shape[0] >= shrt_max or dx.shape[1] >= shrt_max)
bad_dy_shape_cv2 = (dy.shape[0] >= shrt_max or dy.shape[1] >= shrt_max)
if bad_dtype_cv2 or bad_dx_shape_cv2 or bad_dy_shape_cv2:
backend = "scipy"
ia.do_assert(image.ndim == 3)
result = np.copy(image)
height, width = image.shape[0:2]
# True was added here, only difference to usual code
if True or backend == "scipy":
h, w = image.shape[0:2]
y, x = np.meshgrid(np.arange(h).astype(np.float32), np.arange(w).astype(np.float32), indexing='ij')
x_shifted = x + (-1) * dx
y_shifted = y + (-1) * dy
for c in sm.xrange(image.shape[2]):
remapped_flat = ndimage.interpolation.map_coordinates(
image[..., c],
(y_shifted.flatten(), x_shifted.flatten()),
order=order,
cval=cval,
mode=mode
)
remapped = remapped_flat.reshape((height, width))
result[..., c] = remapped
else:
h, w, nb_channels = image.shape
y, x = np.meshgrid(np.arange(h).astype(np.float32), np.arange(w).astype(np.float32), indexing='ij')
x_shifted = x + (-1) * dx
y_shifted = y + (-1) * dy
if image.dtype.kind == "f":
cval = float(cval)
else:
cval = int(cval)
border_mode = cls._MAPPING_MODE_SCIPY_CV2[mode]
interpolation = cls._MAPPING_ORDER_SCIPY_CV2[order]
is_nearest_neighbour = (interpolation == cv2.INTER_NEAREST)
map1, map2 = cv2.convertMaps(x_shifted, y_shifted, cv2.CV_16SC2, nninterpolation=is_nearest_neighbour)
# remap only supports up to 4 channels
if nb_channels <= 4:
result = cv2.remap(image, map1, map2, interpolation=interpolation, borderMode=border_mode, borderValue=cval)
if image.ndim == 3 and result.ndim == 2:
result = result[..., np.newaxis]
else:
current_chan_idx = 0
result = []
while current_chan_idx < nb_channels:
channels = image[..., current_chan_idx:current_chan_idx+4]
result_c = cv2.remap(channels, map1, map2, interpolation=interpolation, borderMode=border_mode,
borderValue=cval)
if result_c.ndim == 2:
result_c = result_c[..., np.newaxis]
result.append(result_c)
current_chan_idx += 4
result = np.concatenate(result, axis=2)
if result.dtype.name != input_dtype.name:
result = meta.restore_dtypes_(result, input_dtype)
return result
class ElasticTransformationCv2(iaa.ElasticTransformation):
@classmethod
def map_coordinates(cls, image, dx, dy, order=1, cval=0, mode="constant"):
# small debug message to be sure that the right function is executed
print("map_coordinates() with cv2")
if order == 0 and image.dtype.name in ["uint64", "int64"]:
raise Exception(("dtypes uint64 and int64 are only supported in ElasticTransformation for order=0, "
+ "got order=%d with dtype=%s.") % (order, image.dtype.name))
input_dtype = image.dtype
if image.dtype.name == "bool":
image = image.astype(np.float32)
elif order == 1 and image.dtype.name in ["int8", "int16", "int32"]:
image = image.astype(np.float64)
elif order >= 2 and image.dtype.name == "int8":
image = image.astype(np.int16)
elif order >= 2 and image.dtype.name == "int32":
image = image.astype(np.float64)
shrt_max = 32767
backend = "cv2"
if order == 0:
bad_dtype_cv2 = (image.dtype.name in ["uint32", "uint64", "int64", "float128", "bool"])
elif order == 1:
bad_dtype_cv2 = (image.dtype.name in ["uint32", "uint64", "int8", "int16", "int32", "int64", "float128",
"bool"])
else:
bad_dtype_cv2 = (image.dtype.name in ["uint32", "uint64", "int8", "int32", "int64", "float128", "bool"])
bad_dx_shape_cv2 = (dx.shape[0] >= shrt_max or dx.shape[1] >= shrt_max)
bad_dy_shape_cv2 = (dy.shape[0] >= shrt_max or dy.shape[1] >= shrt_max)
if bad_dtype_cv2 or bad_dx_shape_cv2 or bad_dy_shape_cv2:
backend = "scipy"
ia.do_assert(image.ndim == 3)
result = np.copy(image)
height, width = image.shape[0:2]
# False was added here, only difference to usual code
if False or backend == "scipy":
h, w = image.shape[0:2]
y, x = np.meshgrid(np.arange(h).astype(np.float32), np.arange(w).astype(np.float32), indexing='ij')
x_shifted = x + (-1) * dx
y_shifted = y + (-1) * dy
for c in sm.xrange(image.shape[2]):
remapped_flat = ndimage.interpolation.map_coordinates(
image[..., c],
(x_shifted.flatten(), y_shifted.flatten()),
order=order,
cval=cval,
mode=mode
)
remapped = remapped_flat.reshape((height, width))
result[..., c] = remapped
else:
h, w, nb_channels = image.shape
y, x = np.meshgrid(np.arange(h).astype(np.float32), np.arange(w).astype(np.float32), indexing='ij')
x_shifted = x + (-1) * dx
y_shifted = y + (-1) * dy
if image.dtype.kind == "f":
cval = float(cval)
else:
cval = int(cval)
border_mode = cls._MAPPING_MODE_SCIPY_CV2[mode]
interpolation = cls._MAPPING_ORDER_SCIPY_CV2[order]
is_nearest_neighbour = (interpolation == cv2.INTER_NEAREST)
map1, map2 = cv2.convertMaps(x_shifted, y_shifted, cv2.CV_16SC2, nninterpolation=is_nearest_neighbour)
# remap only supports up to 4 channels
if nb_channels <= 4:
result = cv2.remap(image, map1, map2, interpolation=interpolation, borderMode=border_mode, borderValue=cval)
if image.ndim == 3 and result.ndim == 2:
result = result[..., np.newaxis]
else:
current_chan_idx = 0
result = []
while current_chan_idx < nb_channels:
channels = image[..., current_chan_idx:current_chan_idx+4]
result_c = cv2.remap(channels, map1, map2, interpolation=interpolation, borderMode=border_mode,
borderValue=cval)
if result_c.ndim == 2:
result_c = result_c[..., np.newaxis]
result.append(result_c)
current_chan_idx += 4
result = np.concatenate(result, axis=2)
if result.dtype.name != input_dtype.name:
result = meta.restore_dtypes_(result, input_dtype)
return result
if __name__ == "__main__":
main()
================================================
FILE: checks/check_fast_snowy_landscape.py
================================================
from __future__ import print_function, division
import imageio
import imgaug as ia
from imgaug import augmenters as iaa
def main():
image = imageio.imread("https://upload.wikimedia.org/wikipedia/commons/8/89/Kukle%2CCzech_Republic..jpg",
format="jpg")
augs = [
("iaa.FastSnowyLandscape(64, 1.5)", iaa.FastSnowyLandscape(64, 1.5)),
("iaa.FastSnowyLandscape(128, 1.5)", iaa.FastSnowyLandscape(128, 1.5)),
("iaa.FastSnowyLandscape(200, 1.5)", iaa.FastSnowyLandscape(200, 1.5)),
("iaa.FastSnowyLandscape(64, 2.5)", iaa.FastSnowyLandscape(64, 2.5)),
("iaa.FastSnowyLandscape(128, 2.5)", iaa.FastSnowyLandscape(128, 2.5)),
("iaa.FastSnowyLandscape(200, 2.5)", iaa.FastSnowyLandscape(200, 2.5)),
("iaa.FastSnowyLandscape(64, 3.5)", iaa.FastSnowyLandscape(64, 3.5)),
("iaa.FastSnowyLandscape(128, 3.5)", iaa.FastSnowyLandscape(128, 3.5)),
("iaa.FastSnowyLandscape(200, 3.5)", iaa.FastSnowyLandscape(200, 3.5)),
("iaa.FastSnowyLandscape()", iaa.FastSnowyLandscape())
]
for descr, aug in augs:
print(descr)
images_aug = aug.augment_images([image] * 64)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_fixed_size.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
from imgaug import augmenters as iaa
def main():
image = ia.data.quokka(size=0.5)
kps = [ia.KeypointsOnImage(
[ia.Keypoint(x=245, y=203), ia.Keypoint(x=365, y=195), ia.Keypoint(x=313, y=269)],
shape=(image.shape[0]*2, image.shape[1]*2)
)]
kps[0] = kps[0].on(image.shape)
print("image shape:", image.shape)
augs_many = [
iaa.PadToFixedSize(200, 200, name="pad-width200-height200"),
iaa.PadToFixedSize(200, 322, name="pad-width200-height322"),
iaa.PadToFixedSize(200, 400, name="pad-width200-height400"),
iaa.PadToFixedSize(480, 200, name="pad-width480-height200"),
iaa.PadToFixedSize(480, 322, name="pad-width480-height322"), # input size == output size
iaa.PadToFixedSize(480, 400, name="pad-width480-height400"),
iaa.PadToFixedSize(600, 200, name="pad-width600-height200"),
iaa.PadToFixedSize(600, 322, name="pad-width600-height322"),
iaa.PadToFixedSize(600, 400, name="pad-width600-height400"),
iaa.CropToFixedSize(200, 200, name="crop-width200-height200"),
iaa.CropToFixedSize(200, 322, name="crop-width200-height322"),
iaa.CropToFixedSize(200, 400, name="crop-width200-height400"),
iaa.CropToFixedSize(480, 200, name="crop-width480-height200"),
iaa.CropToFixedSize(480, 322, name="crop-width480-height322"), # input size == output size
iaa.CropToFixedSize(480, 400, name="crop-width480-height400"),
iaa.CropToFixedSize(600, 200, name="crop-width600-height200"),
iaa.CropToFixedSize(600, 322, name="crop-width600-height322"),
iaa.CropToFixedSize(600, 400, name="crop-width600-height400"),
iaa.Sequential([
iaa.PadToFixedSize(200, 200),
iaa.CropToFixedSize(200, 200)
], name="pad-crop-width200-height200"),
iaa.Sequential([
iaa.PadToFixedSize(400, 400),
iaa.CropToFixedSize(400, 400)
], name="pad-crop-width400-height400"),
iaa.Sequential([
iaa.PadToFixedSize(600, 600),
iaa.CropToFixedSize(600, 600)
], name="pad-crop-width600-height600"),
iaa.Sequential([
iaa.CropToFixedSize(200, 200),
iaa.PadToFixedSize(200, 200)
], name="crop-pad-width200-height200"),
iaa.Sequential([
iaa.CropToFixedSize(400, 400),
iaa.PadToFixedSize(400, 400)
], name="crop-pad-width400-height400"),
iaa.Sequential([
iaa.CropToFixedSize(600, 600),
iaa.PadToFixedSize(600, 600)
], name="crop-pad-width600-height600"),
]
print("original", image.shape)
ia.imshow(kps[0].draw_on_image(image))
print("-----------------")
print("Random aug per image")
print("-----------------")
for aug in augs_many:
images_aug = []
for _ in range(36):
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(image)
kps_aug = aug_det.augment_keypoints(kps)[0]
img_aug_kps = kps_aug.draw_on_image(img_aug)
img_aug_kps = np.pad(img_aug_kps, ((1, 1), (1, 1), (0, 0)), mode="constant", constant_values=255)
images_aug.append(img_aug_kps)
print(aug.name)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_flip_performance.py
================================================
from __future__ import print_function, division
import timeit
import argparse
import numpy as np
COMMANDS_HORIZONTAL_FLIPS = [
("slice",
"arr2 = arr[:, ::-1, :]; "),
("slice, contig",
"arr2 = arr[:, ::-1, :]; "
"arr2 = np.ascontiguousarray(arr2);"),
("fliplr",
"arr2 = np.fliplr(arr); "),
("fliplr contig",
"arr2 = np.fliplr(arr); "
"arr2 = np.ascontiguousarray(arr2);"),
("cv2",
"arr2 = cv2.flip(arr, 1); "
"arr2 = arr2 if arr2.ndim == 3 else arr2[..., np.newaxis]; "),
("cv2 contig",
"arr2 = cv2.flip(arr, 1); "
"arr2 = arr2 if arr2.ndim == 3 else arr2[..., np.newaxis]; "
"arr2 = np.ascontiguousarray(arr2); "),
("fort cv2",
"arr = np.asfortranarray(arr); "
"arr2 = cv2.flip(arr, 1); "
"arr2 = arr2 if arr2.ndim == 3 else arr2[..., np.newaxis]; "),
("fort cv2 contig",
"arr = np.asfortranarray(arr); "
"arr2 = cv2.flip(arr, 1); "
"arr2 = arr2 if arr2.ndim == 3 else arr2[..., np.newaxis]; "
"arr2 = np.ascontiguousarray(arr2); "),
("cv2_",
"arr = cv2.flip(arr, 1, dst=arr); "
"arr = arr if arr.ndim == 3 else arr[..., np.newaxis]; "),
("cv2_ contig",
"arr = cv2.flip(arr, 1, dst=arr); "
"arr = np.ascontiguousarray(arr); "
"arr = arr if arr.ndim == 3 else arr[..., np.newaxis]; "),
("fort cv2_",
"arr = np.asfortranarray(arr); "
"arr = cv2.flip(arr, 1, dst=arr); "
"arr = arr if arr.ndim == 3 else arr[..., np.newaxis]; "),
("fort cv2_ contig",
"arr = np.asfortranarray(arr); "
"arr = cv2.flip(arr, 1, dst=arr); "
"arr = np.ascontiguousarray(arr); "
"arr = arr if arr.ndim == 3 else arr[..., np.newaxis]; "),
("cv2_ get",
"arr = cv2.flip(arr, 1, dst=arr); "
"arr = arr.get(); "
"arr = arr if arr.ndim == 3 else arr[..., np.newaxis]; "),
("cv2_ get contig",
"arr = cv2.flip(arr, 1, dst=arr); "
"arr = arr.get(); "
"arr = arr if arr.ndim == 3 else arr[..., np.newaxis]; "
"arr = np.ascontiguousarray(arr); "),
("fort cv2_ get",
"arr = np.asfortranarray(arr); "
"arr = cv2.flip(arr, 1, dst=arr); "
"arr = arr.get(); "
"arr = arr if arr.ndim == 3 else arr[..., np.newaxis]; "),
("fort cv2_ get contig",
"arr = np.asfortranarray(arr); "
"arr = cv2.flip(arr, 1, dst=arr); "
"arr = arr.get(); "
"arr = arr if arr.ndim == 3 else arr[..., np.newaxis]; "
"arr = np.ascontiguousarray(arr); ")
]
COMMANDS_VERTICAL_FLIPS = []
for name, command in COMMANDS_HORIZONTAL_FLIPS:
name = name.replace("fliplr", "flipud")
command = command.replace("[:, ::-1, :]", "[::-1, :, :]")
command = command.replace("fliplr", "flipud")
command = command.replace("cv2.flip(arr, 1", "cv2.flip(arr, 0")
COMMANDS_VERTICAL_FLIPS.append((name, command))
def main():
parser = argparse.ArgumentParser(
description="Test performance of horizontal or vertical flip methods")
parser.add_argument("--type", help="horizontal|vertical", required=True)
args = parser.parse_args()
assert args.type in ["horizontal", "vertical"]
commands = (
COMMANDS_HORIZONTAL_FLIPS
if args.type == "horizontal"
else COMMANDS_VERTICAL_FLIPS)
number = 10000
for dt in ["bool",
"uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64",
"float16", "float32", "float64", "float128"]:
print("")
print("----------")
print(dt)
print("----------")
last_fliplr_time = None
for command_i_title, commands_i in commands:
try:
# verify that dtype does not change
arr_name = "arr2"
if "arr2" not in commands_i:
arr_name = "arr"
_ = timeit.repeat(
"%s assert %s.dtype.name == '%s', ('Got dtype ' + %s.dtype.name)" % (
commands_i, arr_name, dt, arr_name),
setup="import cv2; "
"import numpy as np; "
"arr = np.ones((224, 224, 3), dtype=np.%s)" % (dt,),
repeat=1, number=1)
times = timeit.repeat(
commands_i,
setup="import cv2; "
"import numpy as np; "
"arr = np.ones((224, 224, 3), dtype=np.%s)" % (dt,),
repeat=number, number=1)
time = np.average(times) * 1000
if command_i_title == "slice, contig":
last_fliplr_time = time
if "cv2" not in command_i_title:
print("{:>20s} {:.5f}ms".format(command_i_title, time))
else:
rel_time = last_fliplr_time / time
print("{:>20s} {:.5f}ms ({:.2f}x)".format(
command_i_title, time, rel_time))
except (AssertionError, AttributeError, TypeError) as exc:
print("{:>20s} Error: {}".format(command_i_title, str(exc)))
# import traceback
# traceback.print_exc()
augs = [
("Add", "iaa.Add(10)"),
("Affine", "iaa.Affine(translate_px={'x': 10})"),
("AverageBlur", "iaa.AverageBlur(3)")
]
for aug_name, aug_command in augs:
print("")
print("==============================")
print("flip method followed by %s" % (aug_name,))
print("==============================")
number = 5000
for command_i_title, commands_i in commands:
dt = "uint8"
try:
arr_name = "arr"
if "arr2" in commands_i:
arr_name = "arr2"
_ = timeit.repeat(
"%s assert %s.dtype.name == '%s', ('Got dtype ' + %s.dtype.name)" % (
commands_i, arr_name, dt, arr_name),
setup="import cv2; "
"import numpy as np; "
"arr = np.ones((224, 224, 3), dtype=np.%s)" % (dt,),
repeat=1, number=1)
times = timeit.repeat(
"%s _ = aug(image=%s);" % (commands_i, arr_name),
setup="import cv2; "
"import numpy as np; "
"import imgaug.augmenters as iaa; "
"arr = np.ones((224, 224, 3), dtype=np.%s); "
"aug = %s" % (dt, aug_command),
repeat=number, number=1)
time = np.average(times) * 1000
print("{:>20s} {:.5f}ms".format(command_i_title, time))
except (AssertionError, AttributeError, TypeError) as exc:
print("{:>20s} Error: {}".format(command_i_title, str(exc)))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_fog.py
================================================
from __future__ import print_function, division
import imageio
import imgaug as ia
from imgaug import augmenters as iaa
def main():
for size in [0.1, 0.2, 1.0]:
image = imageio.imread("https://upload.wikimedia.org/wikipedia/commons/8/89/Kukle%2CCzech_Republic..jpg",
format="jpg")
image = ia.imresize_single_image(image, size, "cubic")
print(image.shape)
augs = [
("iaa.Fog()", iaa.Fog())
]
for descr, aug in augs:
print(descr)
images_aug = aug.augment_images([image] * 64)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_heatmaps.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
from imgaug import augmenters as iaa
def main():
quokka = ia.data.quokka(size=0.5)
h, w = quokka.shape[0:2]
heatmap = np.zeros((h, w), dtype=np.float32)
heatmap[70:120, 90:150] = 0.1
heatmap[30:70, 50:65] = 0.5
heatmap[20:50, 55:85] = 1.0
heatmap[120:140, 0:20] = 0.75
heatmaps = ia.HeatmapsOnImage(heatmap[..., np.newaxis], quokka.shape)
print("Affine...")
aug = iaa.Affine(translate_px={"x": 20}, mode="constant", cval=128)
quokka_aug = aug.augment_image(quokka)
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("Affine with mode=edge...")
aug = iaa.Affine(translate_px={"x": 20}, mode="edge")
quokka_aug = aug.augment_image(quokka)
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("PiecewiseAffine...")
aug = iaa.PiecewiseAffine(scale=0.04)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("PerspectiveTransform...")
aug = iaa.PerspectiveTransform(scale=0.04)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("ElasticTransformation alpha=3, sig=0.5...")
aug = iaa.ElasticTransformation(alpha=3.0, sigma=0.5)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("ElasticTransformation alpha=10, sig=3...")
aug = iaa.ElasticTransformation(alpha=10.0, sigma=3.0)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("CopAndPad mode=constant...")
aug = iaa.CropAndPad(px=(-10, 10, 15, -15), pad_mode="constant", pad_cval=128)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("CopAndPad mode=constant + percent...")
aug = iaa.CropAndPad(percent=(-0.05, 0.05, 0.1, -0.1), pad_mode="constant", pad_cval=128)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("CropAndPad mode=edge...")
aug = iaa.CropAndPad(px=(-10, 10, 15, -15), pad_mode="edge")
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
print("Resize...")
aug = iaa.Resize(0.5, interpolation="nearest")
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(ia.draw_grid([heatmaps_drawn[0], heatmaps_aug_drawn[0]], cols=2))
print("Alpha...")
aug = iaa.Alpha(0.7, iaa.Affine(rotate=20))
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
heatmaps_aug = aug_det.augment_heatmaps([heatmaps])[0]
heatmaps_drawn = heatmaps.draw_on_image(quokka)
heatmaps_aug_drawn = heatmaps_aug.draw_on_image(quokka_aug)
ia.imshow(
np.hstack([
heatmaps_drawn[0],
heatmaps_aug_drawn[0]
])
)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_impulse_noise.py
================================================
from __future__ import print_function, division
import imgaug as ia
from imgaug import augmenters as iaa
def main():
img = ia.data.quokka(0.5)
mul = 0.01
augs = [
("iaa.ImpulseNoise(p=0*mul)", iaa.ImpulseNoise(p=0*mul)),
("iaa.ImpulseNoise(p=1*mul)", iaa.ImpulseNoise(p=1*mul)),
("iaa.ImpulseNoise(p=2*mul)", iaa.ImpulseNoise(p=2*mul)),
("iaa.ImpulseNoise(p=3*mul)", iaa.ImpulseNoise(p=3*mul)),
("iaa.ImpulseNoise(p=(0*mul, 1*mul))", iaa.ImpulseNoise(p=(0*mul, 1*mul))),
("iaa.ImpulseNoise(p=[0*mul, 1*mul, 2*mul])", iaa.ImpulseNoise(p=[0*mul, 1*mul, 2*mul]))
]
for descr, aug in augs:
print(descr)
imgs_aug = aug.augment_images([img] * 16)
ia.imshow(ia.draw_grid(imgs_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_imshow.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
def main():
image = ia.data.quokka()
image_gray = np.average(image, axis=2).astype(np.uint8)
image_gray_3d = image_gray[:, :, np.newaxis]
ia.imshow(image)
ia.imshow(image / 255.0)
ia.imshow(image_gray)
ia.imshow(image_gray_3d)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_jigsaw.py
================================================
from __future__ import print_function, division, absolute_import
import imgaug as ia
import imgaug.augmenters as iaa
import timeit
def main():
image = ia.quokka_square((200, 200))
kpsoi = ia.quokka_keypoints((200, 200), extract="square")
aug = iaa.Jigsaw(10, 10)
images_aug, kpsois_aug = aug(images=[image] * 16,
keypoints=[kpsoi] * 16)
images_show = [kpsoi_aug.draw_on_image(image_aug)
for image_aug, kpsoi_aug in zip(images_aug, kpsois_aug)]
ia.imshow(ia.draw_grid(images_show))
gen_time = timeit.timeit(
"iaa.generate_jigsaw_destinations(10, 10, 2, rng)",
number=128,
setup=(
"import imgaug.augmenters as iaa; "
"import imgaug.random as iarandom; "
"rng = iarandom.RNG(0)"
)
)
print("Time to generate 128x dest:", gen_time)
destinations = iaa.generate_jigsaw_destinations(10, 10, 1, seed=1)
image_jig = iaa.apply_jigsaw(image, destinations)
ia.imshow(image_jig)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_jpeg_compression.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
from imgaug import augmenters as iaa
def main():
augs = [
("0", iaa.JpegCompression(compression=0)),
("1", iaa.JpegCompression(compression=1)),
("25", iaa.JpegCompression(compression=25)),
("50", iaa.JpegCompression(compression=50)),
("75", iaa.JpegCompression(compression=75)),
("99", iaa.JpegCompression(compression=99)),
("100", iaa.JpegCompression(compression=100)),
("(0, 50)", iaa.JpegCompression(compression=(0, 50))),
("(50, 100)", iaa.JpegCompression(compression=(50, 100))),
("(0, 100)", iaa.JpegCompression(compression=(0, 100))),
]
image = ia.data.quokka(size=(256, 256), extract="square")
images = np.uint8([image] * (5*5))
for i, (name, aug) in enumerate(augs):
print(i, name)
images_aug = aug.augment_images(images)
ia.imshow(ia.draw_grid(images_aug, cols=5, rows=5))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_kmeans_color_quantization.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
def main():
image = ia.quokka_square((256, 256))
image_q2 = iaa.quantize_kmeans(image, 2)
image_q16 = iaa.quantize_kmeans(image, 16)
ia.imshow(np.hstack([image_q2, image_q16]))
from_cs = "RGB"
to_cs = ["RGB", "Lab"]
kwargs = {"from_colorspace": from_cs, "to_colorspace": to_cs}
augs = [
iaa.KMeansColorQuantization(2, **kwargs),
iaa.KMeansColorQuantization(4, **kwargs),
iaa.KMeansColorQuantization(8, **kwargs),
iaa.KMeansColorQuantization((2, 16), **kwargs),
]
images_aug = []
for aug in augs:
images_aug.extend(aug(images=[image]*8))
ia.imshow(ia.draw_grid(images_aug, cols=8))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_laplace_noise.py
================================================
from __future__ import print_function, division
import imgaug as ia
from imgaug import augmenters as iaa
def main():
img = ia.data.quokka(0.5)
mul = 0.025
augs = [
("iaa.AdditiveLaplaceNoise(255*(1*mul))", iaa.AdditiveLaplaceNoise(scale=255*(1*mul))),
("iaa.AdditiveLaplaceNoise(255*(2*mul))", iaa.AdditiveLaplaceNoise(scale=255*(2*mul))),
("iaa.AdditiveLaplaceNoise(255*(3*mul))", iaa.AdditiveLaplaceNoise(scale=255*(3*mul))),
("iaa.AdditiveLaplaceNoise(255*(4*mul))", iaa.AdditiveLaplaceNoise(scale=255*(4*mul))),
("iaa.AdditiveLaplaceNoise((255*(0*mul), 255*(4*mul)))",
iaa.AdditiveLaplaceNoise(scale=(255*(0*mul), 255*(4*mul)))),
("iaa.AdditiveLaplaceNoise([255*(1*mul), 255*(2*mul), 255*(3*mul)])",
iaa.AdditiveLaplaceNoise(scale=[255*(1*mul), 255*(2*mul), 255*(3*mul)])),
("iaa.AdditiveLaplaceNoise(255*(2*mul), per_channel=True)",
iaa.AdditiveLaplaceNoise(scale=255*(2*mul), per_channel=True)),
]
for descr, aug in augs:
print(descr)
imgs_aug = aug.augment_images([img] * 16)
ia.imshow(ia.draw_grid(imgs_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_mean_shift_blur.py
================================================
from __future__ import print_function, division, absolute_import
import imgaug as ia
import imgaug.augmenters as iaa
def main():
image = ia.quokka_square((128, 128))
aug = iaa.MeanShiftBlur()
images_aug = aug(images=[image] * 16)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_median_blur.py
================================================
from __future__ import print_function, division
import cv2
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
TIME_PER_STEP = 5000
NB_AUGS_PER_IMAGE = 10
def main():
image = data.astronaut()
image = ia.imresize_single_image(image, (64, 64))
print("image shape:", image.shape)
print("Press any key or wait %d ms to proceed to the next image." % (TIME_PER_STEP,))
k = [
1,
3,
5,
7,
(3, 3),
(1, 11)
]
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.resizeWindow("aug", 64*NB_AUGS_PER_IMAGE, 64)
for ki in k:
aug = iaa.MedianBlur(k=ki)
img_aug = [aug.augment_image(image) for _ in range(NB_AUGS_PER_IMAGE)]
img_aug = np.hstack(img_aug)
print("dtype", img_aug.dtype, "averages", np.average(img_aug, axis=tuple(range(0, img_aug.ndim-1))))
title = "k=%s" % (str(ki),)
img_aug = ia.draw_text(img_aug, x=5, y=5, text=title)
cv2.imshow("aug", img_aug[..., ::-1]) # here with rgb2bgr
cv2.waitKey(TIME_PER_STEP)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_motion_blur.py
================================================
from __future__ import print_function, division
from itertools import cycle
import numpy as np
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
POINT_SIZE = 5
DEG_PER_STEP = 2
TIME_PER_STEP = 1
def main():
image = ia.data.quokka(0.5)
height, width = image.shape[0], image.shape[1]
center_x = width // 2
center_y = height // 2
r = int(min(image.shape[0], image.shape[1]) / 3)
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.imshow("aug", image[:, :, ::-1])
cv2.waitKey(TIME_PER_STEP)
for angle in cycle(np.arange(0, 360, DEG_PER_STEP)):
rad = np.deg2rad(angle-90)
point_x = int(center_x + r * np.cos(rad))
point_y = int(center_y + r * np.sin(rad))
aug = iaa.MotionBlur(k=35, angle=angle, direction=-1.0)
img_aug = aug.augment_image(image)
img_aug[
point_y-POINT_SIZE:point_y+POINT_SIZE+1,
point_x-POINT_SIZE:point_x+POINT_SIZE+1,
:] = np.array([0, 255, 0])
aug_inv = iaa.MotionBlur(k=35, angle=angle, direction=1.0)
img_aug_inv = aug_inv.augment_image(image)
img_aug_inv[
point_y - POINT_SIZE:point_y + POINT_SIZE + 1,
point_x - POINT_SIZE:point_x + POINT_SIZE + 1,
:] = np.array([0, 255, 0])
cv2.imshow("aug", np.hstack([img_aug[:, :, ::-1], img_aug_inv[:, :, ::-1]]))
cv2.waitKey(TIME_PER_STEP)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_multicore_pool.py
================================================
from __future__ import print_function, division
import time
import multiprocessing
import numpy as np
from skimage import data
import imgaug as ia
import imgaug.multicore as multicore
from imgaug import augmenters as iaa
class PoolWithMarkedWorker(multicore.Pool):
def __init__(self, *args, **kwargs):
super(PoolWithMarkedWorker, self).__init__(*args, **kwargs)
@classmethod
def _worker(cls, batch_idx, batch):
process_name = multiprocessing.current_process().name
# print("[_worker] called %s. images in batch: %d" % (process_name, len(batch.images_unaug),))
if "-1" in process_name:
for image in batch.images_unaug:
image[::4, ::4, :] = [255, 255, 255]
return multicore.Pool._worker(batch_idx, batch)
def main():
augseq = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.CoarseDropout(p=0.1, size_percent=0.1)
])
def func_images(images, random_state, parents, hooks):
time.sleep(0.2)
return images
def func_heatmaps(heatmaps, random_state, parents, hooks):
return heatmaps
def func_keypoints(keypoints_on_images, random_state, parents, hooks):
return keypoints_on_images
augseq_slow = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.Lambda(
func_images=func_images,
func_heatmaps=func_heatmaps,
func_keypoints=func_keypoints
)
])
print("------------------")
print(".pool()")
print("------------------")
with augseq.pool() as pool:
time_start = time.time()
batches = list(load_images())
batches_aug = pool.map_batches(batches)
images_aug = []
keypoints_aug = []
for batch_aug in batches_aug:
images_aug.append(batch_aug.images_aug)
keypoints_aug.append(batch_aug.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
# ia.imshow(draw_grid(images_aug, keypoints_aug))
print("------------------")
print("Pool.map_batches(batches)")
print("------------------")
with multicore.Pool(augseq) as pool:
time_start = time.time()
batches = list(load_images())
batches_aug = pool.map_batches(batches)
images_aug = []
keypoints_aug = []
for batch_aug in batches_aug:
images_aug.append(batch_aug.images_aug)
keypoints_aug.append(batch_aug.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
# ia.imshow(draw_grid(images_aug, keypoints_aug))
print("------------------")
print("Pool.imap_batches(batches)")
print("------------------")
with multicore.Pool(augseq) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images())
images_aug = []
keypoints_aug = []
for batch in batches_aug:
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
# ia.imshow(draw_grid(images_aug, keypoints_aug))
print("------------------")
print("Pool.imap_batches(batches, chunksize=32)")
print("------------------")
with multicore.Pool(augseq) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=1000), chunksize=32)
count = 0
for batch in batches_aug:
count += 1
assert count == 1000
print("Done in %.4fs" % (time.time() - time_start,))
print("------------------")
print("Pool.imap_batches(batches, chunksize=2)")
print("------------------")
with multicore.Pool(augseq) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=1000), chunksize=2)
count = 0
for batch in batches_aug:
count += 1
assert count == 1000
print("Done in %.4fs" % (time.time() - time_start,))
print("------------------")
print("Pool.imap_batches(batches, chunksize=1)")
print("------------------")
with multicore.Pool(augseq) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=1000), chunksize=1)
count = 0
for batch in batches_aug:
count += 1
assert count == 1000
print("Done in %.4fs" % (time.time() - time_start,))
print("------------------")
print("Pool.map_batches(batches, chunksize=32)")
print("------------------")
with multicore.Pool(augseq) as pool:
time_start = time.time()
batches_aug = pool.map_batches(list(load_images(n_batches=1000)), chunksize=32)
assert len(batches_aug) == 1000
print("Done in %.4fs" % (time.time() - time_start,))
print("------------------")
print("Pool.map_batches chunksize with fast aug")
print("------------------")
def test_fast(processes, chunksize):
augseq = iaa.Dropout(0.1)
with multicore.Pool(augseq, processes=processes) as pool:
batches = list(load_images(n_batches=10000, draw_text=False))
time_start = time.time()
batches_aug = pool.map_batches(batches, chunksize=chunksize)
assert len(batches_aug) == 10000
print("chunksize=%d, worker=%s, time=%.4fs" % (chunksize, processes, time.time() - time_start))
test_fast(-4, 1)
test_fast(1, 1)
test_fast(None, 1)
test_fast(1, 4)
test_fast(None, 4)
test_fast(1, 32)
test_fast(None, 32)
print("------------------")
print("Pool.imap_batches chunksize with fast aug")
print("------------------")
def test_fast_imap(processes, chunksize):
augseq = iaa.Dropout(0.1)
with multicore.Pool(augseq, processes=processes) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=10000, draw_text=False), chunksize=chunksize)
batches_aug = list(batches_aug)
assert len(batches_aug) == 10000
print("chunksize=%d, worker=%s, time=%.4fs" % (chunksize, processes, time.time() - time_start))
test_fast_imap(-4, 1)
test_fast_imap(1, 1)
test_fast_imap(None, 1)
test_fast_imap(1, 4)
test_fast_imap(None, 4)
test_fast_imap(1, 32)
test_fast_imap(None, 32)
print("------------------")
print("Pool.map_batches with computationally expensive aug")
print("------------------")
def test_heavy(processes, chunksize):
augseq_heavy = iaa.PiecewiseAffine(scale=0.2, nb_cols=8, nb_rows=8)
with multicore.Pool(augseq_heavy, processes=processes) as pool:
batches = list(load_images(n_batches=500, draw_text=False))
time_start = time.time()
batches_aug = pool.map_batches(batches, chunksize=chunksize)
assert len(batches_aug) == 500
print("chunksize=%d, worker=%s, time=%.4fs" % (chunksize, processes, time.time() - time_start))
test_heavy(-4, 1)
test_heavy(1, 1)
test_heavy(None, 1)
test_heavy(1, 4)
test_heavy(None, 4)
test_heavy(1, 32)
test_heavy(None, 32)
print("------------------")
print("Pool.imap_batches(batches), slow loading")
print("------------------")
with multicore.Pool(augseq) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=100, sleep=0.2))
images_aug = []
keypoints_aug = []
for batch in batches_aug:
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
print("------------------")
print("Pool.imap_batches(batches), maxtasksperchild=4")
print("------------------")
with multicore.Pool(augseq, maxtasksperchild=4) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=100))
images_aug = []
keypoints_aug = []
for batch in batches_aug:
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
ia.imshow(draw_grid(images_aug, keypoints_aug))
print("------------------")
print("Pool.imap_batches(batches), seed=1")
print("------------------")
# we color here the images of the first worker to see in the grids which images belong to one worker
with PoolWithMarkedWorker(augseq, seed=1) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=4))
images_aug = []
keypoints_aug = []
for batch in batches_aug:
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
grid_a = draw_grid(images_aug, keypoints_aug)
with multicore.Pool(augseq, seed=1) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=4))
images_aug = []
keypoints_aug = []
for batch in batches_aug:
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
grid_b = draw_grid(images_aug, keypoints_aug)
grid_b[:, 0:2, 0] = 0
grid_b[:, 0:2, 1] = 255
grid_b[:, 0:2, 2] = 0
ia.imshow(np.hstack([grid_a, grid_b]))
print("------------------")
print("Pool.imap_batches(batches), seed=None")
print("------------------")
with multicore.Pool(augseq, seed=None) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=4))
images_aug = []
keypoints_aug = []
for batch in batches_aug:
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
grid_a = draw_grid(images_aug, keypoints_aug)
with multicore.Pool(augseq, seed=None) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=4))
images_aug = []
keypoints_aug = []
for batch in batches_aug:
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
grid_b = draw_grid(images_aug, keypoints_aug)
ia.imshow(np.hstack([grid_a, grid_b]))
print("------------------")
print("Pool.imap_batches(batches), maxtasksperchild=4, seed=1")
print("------------------")
with multicore.Pool(augseq, maxtasksperchild=4, seed=1) as pool:
time_start = time.time()
batches_aug = pool.imap_batches(load_images(n_batches=100))
images_aug = []
keypoints_aug = []
for batch in batches_aug:
images_aug.append(batch.images_aug)
keypoints_aug.append(batch.keypoints_aug)
print("Done in %.4fs" % (time.time() - time_start,))
ia.imshow(draw_grid(images_aug, keypoints_aug))
for augseq_i in [augseq, augseq_slow]:
print("------------------")
print("Many very small runs (batches=1)")
print("------------------")
with multicore.Pool(augseq_i) as pool:
time_start = time.time()
for i in range(100):
_ = pool.map_batches(list(load_images(n_batches=1)))
print("Done in %.4fs" % (time.time() - time_start,))
print("------------------")
print("Many very small runs (batches=2)")
print("------------------")
with multicore.Pool(augseq_i) as pool:
time_start = time.time()
for i in range(100):
_ = pool.map_batches(list(load_images(n_batches=2)))
print("Done in %.4fs" % (time.time() - time_start,))
def load_images(n_batches=10, sleep=0.0, draw_text=True):
batch_size = 4
astronaut = data.astronaut()
astronaut = ia.imresize_single_image(astronaut, (64, 64))
kps = ia.KeypointsOnImage([ia.Keypoint(x=15, y=25)], shape=astronaut.shape)
counter = 0
for i in range(n_batches):
if draw_text:
batch_images = []
batch_kps = []
for b in range(batch_size):
astronaut_text = ia.draw_text(astronaut, x=0, y=0, text="%d" % (counter,), color=[0, 255, 0], size=16)
batch_images.append(astronaut_text)
batch_kps.append(kps)
counter += 1
batch = ia.Batch(
images=np.array(batch_images, dtype=np.uint8),
keypoints=batch_kps
)
else:
if i == 0:
batch_images = np.array([np.copy(astronaut) for _ in range(batch_size)], dtype=np.uint8)
batch = ia.Batch(
images=np.copy(batch_images),
keypoints=[kps.deepcopy() for _ in range(batch_size)]
)
yield batch
if sleep > 0:
time.sleep(sleep)
def draw_grid(images_aug, keypoints_aug):
if keypoints_aug is None:
keypoints_aug = []
for bidx in range(len(images_aug)):
keypoints_aug.append([None for image in images_aug[bidx]])
images_kps_batches = []
for bidx in range(len(images_aug)):
images_kps_batch = []
for image, kps in zip(images_aug[bidx], keypoints_aug[bidx]):
if kps is None:
image_kps = image
else:
image_kps = kps.draw_on_image(image, size=5, color=[255, 0, 0])
images_kps_batch.append(image_kps)
images_kps_batches.extend(images_kps_batch)
grid = ia.draw_grid(images_kps_batches, cols=len(images_aug[0]))
return grid
if __name__ == "__main__":
main()
================================================
FILE: checks/check_multiply_hue_and_saturation.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
from imgaug import augmenters as iaa
def main():
image = ia.quokka_square((128, 128))
images_aug = []
for mul in np.linspace(0.0, 2.0, 10):
aug = iaa.MultiplyHueAndSaturation(mul)
image_aug = aug.augment_image(image)
images_aug.append(image_aug)
for mul_hue in np.linspace(0.0, 5.0, 10):
aug = iaa.MultiplyHueAndSaturation(mul_hue=mul_hue)
image_aug = aug.augment_image(image)
images_aug.append(image_aug)
for mul_saturation in np.linspace(0.0, 5.0, 10):
aug = iaa.MultiplyHueAndSaturation(mul_saturation=mul_saturation)
image_aug = aug.augment_image(image)
images_aug.append(image_aug)
ia.imshow(ia.draw_grid(images_aug, rows=3))
images_aug = []
images_aug.extend(iaa.MultiplyHue().augment_images([image] * 10))
images_aug.extend(iaa.MultiplySaturation().augment_images([image] * 10))
ia.imshow(ia.draw_grid(images_aug, rows=2))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_noise.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
def main():
nb_rows = 8
nb_cols = 8
h, w = (128, 128)
sample_size = 128
noise_gens = [
iap.SimplexNoise(),
iap.FrequencyNoise(exponent=-4, size_px_max=sample_size, upscale_method="cubic"),
iap.FrequencyNoise(exponent=-2, size_px_max=sample_size, upscale_method="cubic"),
iap.FrequencyNoise(exponent=0, size_px_max=sample_size, upscale_method="cubic"),
iap.FrequencyNoise(exponent=2, size_px_max=sample_size, upscale_method="cubic"),
iap.FrequencyNoise(exponent=4, size_px_max=sample_size, upscale_method="cubic"),
iap.FrequencyNoise(exponent=(-4, 4), size_px_max=(4, sample_size),
upscale_method=["nearest", "linear", "cubic"]),
iap.IterativeNoiseAggregator(
other_param=iap.FrequencyNoise(exponent=(-4, 4), size_px_max=(4, sample_size),
upscale_method=["nearest", "linear", "cubic"]),
iterations=(1, 3),
aggregation_method=["max", "avg"]
),
iap.IterativeNoiseAggregator(
other_param=iap.Sigmoid(
iap.FrequencyNoise(exponent=(-4, 4), size_px_max=(4, sample_size),
upscale_method=["nearest", "linear", "cubic"]),
threshold=(-10, 10),
activated=0.33,
mul=20,
add=-10
),
iterations=(1, 3),
aggregation_method=["max", "avg"]
)
]
samples = [[] for _ in range(len(noise_gens))]
for _ in range(nb_rows * nb_cols):
for i, noise_gen in enumerate(noise_gens):
samples[i].append(noise_gen.draw_samples((h, w)))
rows = [np.hstack(row) for row in samples]
grid = np.vstack(rows)
ia.imshow((grid*255).astype(np.uint8))
images = [ia.quokka_square(size=(128, 128)) for _ in range(16)]
seqs = [
iaa.SimplexNoiseAlpha(first=iaa.EdgeDetect(1.0)),
iaa.SimplexNoiseAlpha(first=iaa.EdgeDetect(1.0), per_channel=True),
iaa.FrequencyNoiseAlpha(first=iaa.EdgeDetect(1.0)),
iaa.FrequencyNoiseAlpha(first=iaa.EdgeDetect(1.0), per_channel=True)
]
images_aug = []
for seq in seqs:
images_aug.append(np.hstack(seq.augment_images(images)))
images_aug = np.vstack(images_aug)
ia.imshow(images_aug)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_parameters.py
================================================
from __future__ import print_function, division
import imgaug as ia
# TODO ForceSign
from imgaug.parameters import (
Binomial, Choice, DiscreteUniform, Poisson, Normal, Laplace, ChiSquare,
Weibull, Uniform, Beta, Deterministic, Clip, Discretize, Multiply, Add,
Divide, Power, Absolute, RandomSign, Positive, Negative,
SimplexNoise, FrequencyNoise, Sigmoid
)
import numpy as np
def main():
params = [
("Binomial(0.1)", Binomial(0.1)),
("Choice", Choice([0, 1, 2])),
("Choice with p", Choice([0, 1, 2], p=[0.1, 0.2, 0.7])),
("DiscreteUniform(0, 10)", DiscreteUniform(0, 10)),
("Poisson(0)", Poisson(0)),
("Poisson(5)", Poisson(5)),
("Discretize(Poisson(5))", Discretize(Poisson(5))),
("Normal(0, 1)", Normal(0, 1)),
("Normal(1, 1)", Normal(1, 1)),
("Normal(1, 2)", Normal(0, 2)),
("Normal(Choice([-1, 1]), 2)", Normal(Choice([-1, 1]), 2)),
("Discretize(Normal(0, 1.0))", Discretize(Normal(0, 1.0))),
("Positive(Normal(0, 1.0))", Positive(Normal(0, 1.0))),
("Positive(Normal(0, 1.0), mode='reroll')", Positive(Normal(0, 1.0), mode="reroll")),
("Negative(Normal(0, 1.0))", Negative(Normal(0, 1.0))),
("Negative(Normal(0, 1.0), mode='reroll')", Negative(Normal(0, 1.0), mode="reroll")),
("Laplace(0, 1.0)", Laplace(0, 1.0)),
("Laplace(1.0, 3.0)", Laplace(1.0, 3.0)),
("Laplace([-1.0, 1.0], 1.0)", Laplace([-1.0, 1.0], 1.0)),
("ChiSquare(1)", ChiSquare(1)),
("ChiSquare([1, 6])", ChiSquare([1, 6])),
("Weibull(0.5)", Weibull(0.5)),
("Weibull((1.0, 3.0))", Weibull((1.0, 3.0))),
("Uniform(0, 10)", Uniform(0, 10)),
("Beta(0.5, 0.5)", Beta(0.5, 0.5)),
("Deterministic(1)", Deterministic(1)),
("Clip(Normal(0, 1), 0, None)", Clip(Normal(0, 1), minval=0, maxval=None)),
("Multiply(Uniform(0, 10), 2)", Multiply(Uniform(0, 10), 2)),
("Add(Uniform(0, 10), 5)", Add(Uniform(0, 10), 5)),
("Absolute(Normal(0, 1))", Absolute(Normal(0, 1))),
("RandomSign(Poisson(1))", RandomSign(Poisson(1))),
("RandomSign(Poisson(1), 0.9)", RandomSign(Poisson(1), 0.9))
]
params_arithmetic = [
("Normal(0, 1.0)", Normal(0.0, 1.0)),
("Normal(0, 1.0) + 5", Normal(0.0, 1.0) + 5),
("5 + Normal(0, 1.0)", 5 + Normal(0.0, 1.0)),
("5 + Normal(0, 1.0)", Add(5, Normal(0.0, 1.0), elementwise=True)),
("Normal(0, 1.0) * 10", Normal(0.0, 1.0) * 10),
("10 * Normal(0, 1.0)", 10 * Normal(0.0, 1.0)),
("10 * Normal(0, 1.0)", Multiply(10, Normal(0.0, 1.0), elementwise=True)),
("Normal(0, 1.0) / 10", Normal(0.0, 1.0) / 10),
("10 / Normal(0, 1.0)", 10 / Normal(0.0, 1.0)),
("10 / Normal(0, 1.0)", Divide(10, Normal(0.0, 1.0), elementwise=True)),
("Normal(0, 1.0) ** 2", Normal(0.0, 1.0) ** 2),
("2 ** Normal(0, 1.0)", 2 ** Normal(0.0, 1.0)),
("2 ** Normal(0, 1.0)", Power(2, Normal(0.0, 1.0), elementwise=True))
]
params_noise = [
("SimplexNoise", SimplexNoise()),
("Sigmoid(SimplexNoise)", Sigmoid(SimplexNoise())),
("SimplexNoise(linear)", SimplexNoise(upscale_method="linear")),
("SimplexNoise(nearest)", SimplexNoise(upscale_method="nearest")),
("FrequencyNoise((-4, 4))", FrequencyNoise(exponent=(-4, 4))),
("FrequencyNoise(-2)", FrequencyNoise(exponent=-2)),
("FrequencyNoise(2)", FrequencyNoise(exponent=2))
]
images_params = [param.draw_distribution_graph() for (title, param) in params]
images_arithmetic = [param.draw_distribution_graph() for (title, param) in params_arithmetic]
images_noise = [param.draw_distribution_graph(size=(1000, 10, 10)) for (title, param) in params_noise]
ia.imshow(np.vstack(images_params))
ia.imshow(np.vstack(images_arithmetic))
ia.imshow(np.vstack(images_noise))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_performance.py
================================================
"""
Tests to measure the performance of each augmenter.
Run these checks from the project directory (i.e. parent directory) via
python check_performance.py
"""
from __future__ import print_function, division
import imgaug as ia
from imgaug import augmenters as iaa
import numpy as np
import time
import random
import six.moves as sm
"""
---------------------------
Keypoints
---------------------------
[Augmenter: Identity]
(4, 4, 3) | SUM 0.01990s | PER ITER avg 0.00020s, min 0.00017s, max 0.00043s
(32, 32, 3) | SUM 0.01863s | PER ITER avg 0.00019s, min 0.00017s, max 0.00033s
(256, 256, 3) | SUM 0.01879s | PER ITER avg 0.00019s, min 0.00017s, max 0.00029s
[Augmenter: Crop-px]
(4, 4, 3) | SUM 0.20215s | PER ITER avg 0.00202s, min 0.00168s, max 0.01908s
(32, 32, 3) | SUM 0.19844s | PER ITER avg 0.00198s, min 0.00164s, max 0.01933s
(256, 256, 3) | SUM 0.17918s | PER ITER avg 0.00179s, min 0.00166s, max 0.00214s
[Augmenter: Crop-percent]
(4, 4, 3) | SUM 0.14201s | PER ITER avg 0.00142s, min 0.00114s, max 0.02041s
(32, 32, 3) | SUM 0.16912s | PER ITER avg 0.00169s, min 0.00137s, max 0.02023s
(256, 256, 3) | SUM 0.15548s | PER ITER avg 0.00155s, min 0.00142s, max 0.00193s
[Augmenter: Fliplr]
(4, 4, 3) | SUM 0.02303s | PER ITER avg 0.00023s, min 0.00021s, max 0.00034s
(32, 32, 3) | SUM 0.02477s | PER ITER avg 0.00025s, min 0.00021s, max 0.00038s
(256, 256, 3) | SUM 0.02383s | PER ITER avg 0.00024s, min 0.00022s, max 0.00036s
[Augmenter: Flipud]
(4, 4, 3) | SUM 0.02362s | PER ITER avg 0.00024s, min 0.00021s, max 0.00035s
(32, 32, 3) | SUM 0.02356s | PER ITER avg 0.00024s, min 0.00021s, max 0.00032s
(256, 256, 3) | SUM 0.02415s | PER ITER avg 0.00024s, min 0.00021s, max 0.00037s
[Augmenter: Grayscale]
(4, 4, 3) | SUM 0.01908s | PER ITER avg 0.00019s, min 0.00017s, max 0.00030s
(32, 32, 3) | SUM 0.01903s | PER ITER avg 0.00019s, min 0.00017s, max 0.00030s
(256, 256, 3) | SUM 0.01876s | PER ITER avg 0.00019s, min 0.00017s, max 0.00027s
[Augmenter: GaussianBlur]
(4, 4, 3) | SUM 0.01904s | PER ITER avg 0.00019s, min 0.00017s, max 0.00029s
(32, 32, 3) | SUM 0.01851s | PER ITER avg 0.00019s, min 0.00017s, max 0.00033s
(256, 256, 3) | SUM 0.01894s | PER ITER avg 0.00019s, min 0.00017s, max 0.00025s
[Augmenter: AdditiveGaussianNoise]
(4, 4, 3) | SUM 0.01902s | PER ITER avg 0.00019s, min 0.00017s, max 0.00029s
(32, 32, 3) | SUM 0.01905s | PER ITER avg 0.00019s, min 0.00017s, max 0.00028s
(256, 256, 3) | SUM 0.01971s | PER ITER avg 0.00020s, min 0.00017s, max 0.00046s
[Augmenter: Dropout]
(4, 4, 3) | SUM 0.01887s | PER ITER avg 0.00019s, min 0.00017s, max 0.00027s
(32, 32, 3) | SUM 0.01913s | PER ITER avg 0.00019s, min 0.00017s, max 0.00030s
(256, 256, 3) | SUM 0.01922s | PER ITER avg 0.00019s, min 0.00017s, max 0.00029s
[Augmenter: Multiply]
(4, 4, 3) | SUM 0.01942s | PER ITER avg 0.00019s, min 0.00017s, max 0.00028s
(32, 32, 3) | SUM 0.01922s | PER ITER avg 0.00019s, min 0.00017s, max 0.00032s
(256, 256, 3) | SUM 0.01875s | PER ITER avg 0.00019s, min 0.00017s, max 0.00030s
[Augmenter: ContrastNormalization]
(4, 4, 3) | SUM 0.01852s | PER ITER avg 0.00019s, min 0.00017s, max 0.00029s
(32, 32, 3) | SUM 0.01869s | PER ITER avg 0.00019s, min 0.00017s, max 0.00026s
(256, 256, 3) | SUM 0.01875s | PER ITER avg 0.00019s, min 0.00017s, max 0.00028s
[Augmenter: Grayscale]
(4, 4, 3) | SUM 0.01919s | PER ITER avg 0.00019s, min 0.00017s, max 0.00030s
(32, 32, 3) | SUM 0.01923s | PER ITER avg 0.00019s, min 0.00017s, max 0.00033s
(256, 256, 3) | SUM 0.01888s | PER ITER avg 0.00019s, min 0.00017s, max 0.00028s
[Augmenter: ElasticTransformation]
(4, 4, 3) | SUM 0.01882s | PER ITER avg 0.00019s, min 0.00017s, max 0.00024s
(32, 32, 3) | SUM 0.01883s | PER ITER avg 0.00019s, min 0.00017s, max 0.00029s
(256, 256, 3) | SUM 0.01869s | PER ITER avg 0.00019s, min 0.00017s, max 0.00029s
[Augmenter: AffineOrder0ModeConstant]
(4, 4, 3) | SUM 0.28146s | PER ITER avg 0.00281s, min 0.00243s, max 0.02199s
(32, 32, 3) | SUM 0.28047s | PER ITER avg 0.00280s, min 0.00243s, max 0.02083s
(256, 256, 3) | SUM 0.28715s | PER ITER avg 0.00287s, min 0.00243s, max 0.02088s
[Augmenter: AffineOrder0]
(4, 4, 3) | SUM 0.27242s | PER ITER avg 0.00272s, min 0.00246s, max 0.00362s
(32, 32, 3) | SUM 0.29675s | PER ITER avg 0.00297s, min 0.00247s, max 0.02220s
(256, 256, 3) | SUM 0.28988s | PER ITER avg 0.00290s, min 0.00247s, max 0.02128s
[Augmenter: AffineOrder1]
(4, 4, 3) | SUM 0.26750s | PER ITER avg 0.00267s, min 0.00246s, max 0.00321s
(32, 32, 3) | SUM 0.28361s | PER ITER avg 0.00284s, min 0.00245s, max 0.02144s
(256, 256, 3) | SUM 0.28973s | PER ITER avg 0.00290s, min 0.00246s, max 0.02070s
[Augmenter: AffineAll]
(4, 4, 3) | SUM 0.27070s | PER ITER avg 0.00271s, min 0.00246s, max 0.00367s
(32, 32, 3) | SUM 0.28405s | PER ITER avg 0.00284s, min 0.00247s, max 0.02120s
(256, 256, 3) | SUM 0.28895s | PER ITER avg 0.00289s, min 0.00247s, max 0.02144s
---------------------------
Images
---------------------------
[Augmenter: Identity]
(16, 4, 4, 3) | SUM 0.00135s | PER ITER avg 0.00001s, min 0.00001s, max 0.00008s
(16, 32, 32, 3) | SUM 0.00203s | PER ITER avg 0.00002s, min 0.00002s, max 0.00005s
(16, 256, 256, 3) | SUM 0.05284s | PER ITER avg 0.00053s, min 0.00044s, max 0.00194s
[Augmenter: Crop-px]
(16, 4, 4, 3) | SUM 0.09324s | PER ITER avg 0.00093s, min 0.00084s, max 0.00315s
(16, 32, 32, 3) | SUM 0.10302s | PER ITER avg 0.00103s, min 0.00094s, max 0.00162s
(16, 256, 256, 3) | SUM 0.81943s | PER ITER avg 0.00819s, min 0.00767s, max 0.00934s
[Augmenter: Crop-percent]
(16, 4, 4, 3) | SUM 0.06562s | PER ITER avg 0.00066s, min 0.00057s, max 0.00099s
(16, 32, 32, 3) | SUM 0.09784s | PER ITER avg 0.00098s, min 0.00089s, max 0.00131s
(16, 256, 256, 3) | SUM 0.80779s | PER ITER avg 0.00808s, min 0.00732s, max 0.01008s
[Augmenter: Fliplr]
(16, 4, 4, 3) | SUM 0.00525s | PER ITER avg 0.00005s, min 0.00004s, max 0.00017s
(16, 32, 32, 3) | SUM 0.01025s | PER ITER avg 0.00010s, min 0.00007s, max 0.00015s
(16, 256, 256, 3) | SUM 0.36918s | PER ITER avg 0.00369s, min 0.00181s, max 0.00553s
[Augmenter: Flipud]
(16, 4, 4, 3) | SUM 0.00512s | PER ITER avg 0.00005s, min 0.00004s, max 0.00009s
(16, 32, 32, 3) | SUM 0.00665s | PER ITER avg 0.00007s, min 0.00006s, max 0.00011s
(16, 256, 256, 3) | SUM 0.12664s | PER ITER avg 0.00127s, min 0.00092s, max 0.00167s
[Augmenter: Grayscale]
(16, 4, 4, 3) | SUM 0.05943s | PER ITER avg 0.00059s, min 0.00050s, max 0.00125s
(16, 32, 32, 3) | SUM 0.12247s | PER ITER avg 0.00122s, min 0.00106s, max 0.00205s
(16, 256, 256, 3) | SUM 3.62785s | PER ITER avg 0.03628s, min 0.03508s, max 0.03963s
[Augmenter: GaussianBlur]
(16, 4, 4, 3) | SUM 0.15514s | PER ITER avg 0.00155s, min 0.00136s, max 0.00188s
(16, 32, 32, 3) | SUM 0.25121s | PER ITER avg 0.00251s, min 0.00221s, max 0.00298s
(16, 256, 256, 3) | SUM 5.51685s | PER ITER avg 0.05517s, min 0.04923s, max 0.06026s
[Augmenter: AdditiveGaussianNoise]
(16, 4, 4, 3) | SUM 0.09606s | PER ITER avg 0.00096s, min 0.00085s, max 0.00150s
(16, 32, 32, 3) | SUM 0.21302s | PER ITER avg 0.00213s, min 0.00196s, max 0.00254s
(16, 256, 256, 3) | SUM 7.22374s | PER ITER avg 0.07224s, min 0.07017s, max 0.07558s
[Augmenter: Dropout]
(16, 4, 4, 3) | SUM 0.09362s | PER ITER avg 0.00094s, min 0.00084s, max 0.00118s
(16, 32, 32, 3) | SUM 0.17472s | PER ITER avg 0.00175s, min 0.00161s, max 0.00230s
(16, 256, 256, 3) | SUM 5.04969s | PER ITER avg 0.05050s, min 0.04839s, max 0.05631s
[Augmenter: Multiply]
(16, 4, 4, 3) | SUM 0.05442s | PER ITER avg 0.00054s, min 0.00046s, max 0.00089s
(16, 32, 32, 3) | SUM 0.06895s | PER ITER avg 0.00069s, min 0.00060s, max 0.00109s
(16, 256, 256, 3) | SUM 0.87311s | PER ITER avg 0.00873s, min 0.00799s, max 0.00993s
[Augmenter: ContrastNormalization]
(16, 4, 4, 3) | SUM 0.05746s | PER ITER avg 0.00057s, min 0.00050s, max 0.00094s
(16, 32, 32, 3) | SUM 0.08083s | PER ITER avg 0.00081s, min 0.00071s, max 0.00133s
(16, 256, 256, 3) | SUM 1.57577s | PER ITER avg 0.01576s, min 0.01443s, max 0.01831s
[Augmenter: Grayscale]
(16, 4, 4, 3) | SUM 0.05464s | PER ITER avg 0.00055s, min 0.00049s, max 0.00069s
(16, 32, 32, 3) | SUM 0.12058s | PER ITER avg 0.00121s, min 0.00104s, max 0.00223s
(16, 256, 256, 3) | SUM 3.57037s | PER ITER avg 0.03570s, min 0.03461s, max 0.03780s
[Augmenter: ElasticTransformation]
(16, 4, 4, 3) | SUM 0.29551s | PER ITER avg 0.00296s, min 0.00272s, max 0.00336s
(16, 32, 32, 3) | SUM 0.68591s | PER ITER avg 0.00686s, min 0.00642s, max 0.00764s
(16, 256, 256, 3) | SUM 26.30515s | PER ITER avg 0.26305s, min 0.25754s, max 0.26912s
[Augmenter: AffineOrder0ModeConstant]
(16, 4, 4, 3) | SUM 0.35887s | PER ITER avg 0.00359s, min 0.00333s, max 0.00424s
(16, 32, 32, 3) | SUM 0.47889s | PER ITER avg 0.00479s, min 0.00451s, max 0.00535s
(16, 256, 256, 3) | SUM 9.83738s | PER ITER avg 0.09837s, min 0.09417s, max 0.10458s
[Augmenter: AffineOrder0]
(16, 4, 4, 3) | SUM 0.37980s | PER ITER avg 0.00380s, min 0.00340s, max 0.00517s
(16, 32, 32, 3) | SUM 0.53106s | PER ITER avg 0.00531s, min 0.00472s, max 0.00630s
(16, 256, 256, 3) | SUM 10.69961s | PER ITER avg 0.10700s, min 0.10223s, max 0.11325s
[Augmenter: AffineOrder1]
(16, 4, 4, 3) | SUM 0.39431s | PER ITER avg 0.00394s, min 0.00363s, max 0.00511s
(16, 32, 32, 3) | SUM 0.62730s | PER ITER avg 0.00627s, min 0.00576s, max 0.00711s
(16, 256, 256, 3) | SUM 14.50003s | PER ITER avg 0.14500s, min 0.13785s, max 0.15291s
[Augmenter: AffineAll]
(16, 4, 4, 3) | SUM 0.58742s | PER ITER avg 0.00587s, min 0.00429s, max 0.00724s
(16, 32, 32, 3) | SUM 3.69956s | PER ITER avg 0.03700s, min 0.01358s, max 0.06233s
(16, 256, 256, 3) | SUM 212.91776s | PER ITER avg 2.12918s, min 0.57114s, max 3.95389s
"""
def main():
augmenters = [
iaa.Identity(name="Identity"),
iaa.Crop(px=(0, 8), name="Crop-px"),
iaa.Crop(percent=(0, 0.1), name="Crop-percent"),
iaa.Fliplr(0.5, name="Fliplr"),
iaa.Flipud(0.5, name="Flipud"),
iaa.Grayscale((0.0, 1.0), name="Grayscale"),
iaa.GaussianBlur((0, 3.0), name="GaussianBlur"),
iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.1), name="AdditiveGaussianNoise"),
iaa.Dropout((0.0, 0.1), name="Dropout"),
iaa.Multiply((0.5, 1.5), name="Multiply"),
iaa.ContrastNormalization(alpha=(0.5, 2.0), name="ContrastNormalization"),
iaa.Grayscale(alpha=(0.0, 1.0), name="Grayscale"),
iaa.ElasticTransformation(alpha=(0.5, 8.0), sigma=1.0, name="ElasticTransformation"),
iaa.Affine(
scale={"x": (0.8, 1.2), "y": (0.8, 1.2)},
translate_px={"x": (-16, 16), "y": (-16, 16)},
rotate=(-45, 45),
shear=(-16, 16),
order=0,
cval=(0, 255),
mode="constant",
name="AffineOrder0ModeConstant"
)
]
for order in [0, 1]:
augmenters.append(
iaa.Affine(
scale={"x": (0.8, 1.2), "y": (0.8, 1.2)},
translate_px={"x": (-16, 16), "y": (-16, 16)},
rotate=(-45, 45),
shear=(-16, 16),
order=order,
cval=(0, 255),
mode=ia.ALL,
name="AffineOrder%d" % (order,)
)
)
augmenters.append(
iaa.Affine(
scale={"x": (0.8, 1.2), "y": (0.8, 1.2)},
translate_px={"x": (-16, 16), "y": (-16, 16)},
rotate=(-45, 45),
shear=(-16, 16),
order=ia.ALL,
cval=(0, 255),
mode=ia.ALL,
name="AffineAll"
)
)
kps = []
for _ in sm.xrange(20):
x = random.randint(0, 31)
y = random.randint(0, 31)
kps.append(ia.Keypoint(x=x, y=y))
kps = ia.KeypointsOnImage(kps, shape=(32, 32, 3))
small_keypoints_one = kps.on((4, 4, 3))
medium_keypoints_one = kps.on((32, 32, 3))
large_keypoints_one = kps.on((256, 256, 3))
small_keypoints = [small_keypoints_one.deepcopy() for _ in sm.xrange(16)]
medium_keypoints = [medium_keypoints_one.deepcopy() for _ in sm.xrange(16)]
large_keypoints = [large_keypoints_one.deepcopy() for _ in sm.xrange(16)]
small_images = np.random.randint(0, 255, (16, 4, 4, 3)).astype(np.uint8)
medium_images = np.random.randint(0, 255, (16, 32, 32, 3)).astype(np.uint8)
large_images = np.random.randint(0, 255, (16, 256, 256, 3)).astype(np.uint8)
print("---------------------------")
print("Keypoints")
print("---------------------------")
for augmenter in augmenters:
print("[Augmenter: %s]" % (augmenter.name,))
for keypoints in [small_keypoints, medium_keypoints, large_keypoints]:
times = []
for i in sm.xrange(100):
time_start = time.time()
_img_aug = augmenter.augment_keypoints(keypoints)
time_end = time.time()
times.append(time_end - time_start)
times = np.array(times)
img_str = "{:20s}".format(keypoints[0].shape)
print("%s | SUM %.5fs | PER ITER avg %.5fs, min %.5fs, max %.5fs" % (
img_str, float(np.sum(times)), np.average(times), np.min(times), np.max(times)))
print("---------------------------")
print("Images")
print("---------------------------")
for augmenter in augmenters:
print("[Augmenter: %s]" % (augmenter.name,))
for images in [small_images, medium_images, large_images]:
times = []
for i in sm.xrange(100):
time_start = time.time()
_img_aug = augmenter.augment_images(images)
time_end = time.time()
times.append(time_end - time_start)
times = np.array(times)
img_str = "{:20s}".format(images.shape)
print("%s | SUM %.5fs | PER ITER avg %.5fs, min %.5fs, max %.5fs" % (
img_str, float(np.sum(times)), np.average(times), np.min(times), np.max(times)))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_perspective_transform.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
from imgaug import augmenters as iaa
def main():
image = ia.data.quokka(size=0.5)
kps = [ia.KeypointsOnImage(
[ia.Keypoint(x=245, y=203), ia.Keypoint(x=365, y=195), ia.Keypoint(x=313, y=269)],
shape=(image.shape[0]*2, image.shape[1]*2)
)]
kps[0] = kps[0].on(image.shape)
print("image shape:", image.shape)
augs = [
iaa.PerspectiveTransform(scale=0.01, name="pt001", keep_size=True),
iaa.PerspectiveTransform(scale=0.1, name="pt01", keep_size=True),
iaa.PerspectiveTransform(scale=0.2, name="pt02", keep_size=True),
iaa.PerspectiveTransform(scale=0.3, name="pt03", keep_size=True),
iaa.PerspectiveTransform(scale=(0, 0.3), name="pt00to03", keep_size=True)
]
print("original", image.shape)
ia.imshow(kps[0].draw_on_image(image))
print("-----------------")
print("Random aug per image")
print("-----------------")
for aug in augs:
images_aug = []
for _ in range(16):
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(image)
kps_aug = aug_det.augment_keypoints(kps)[0]
img_aug_kps = kps_aug.draw_on_image(img_aug)
img_aug_kps = np.pad(img_aug_kps, ((1, 1), (1, 1), (0, 0)), mode="constant", constant_values=255)
images_aug.append(img_aug_kps)
print(aug.name)
ia.imshow(ia.draw_grid(images_aug))
print("----------------")
print("6 channels")
print("----------------")
image6 = np.dstack([image, image])
image6_aug = augs[1].augment_image(image6)
ia.imshow(
np.hstack([image6_aug[..., 0:3], image6_aug[..., 3:6]])
)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_piecewise_affine.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
import imgaug.random as iarandom
from imgaug import augmenters as iaa
iarandom.seed(3)
def main():
image = ia.data.quokka(size=0.5)
print(image.shape)
kps = [
ia.KeypointsOnImage(
[
ia.Keypoint(x=123, y=102),
ia.Keypoint(x=182, y=98),
ia.Keypoint(x=155, y=134),
ia.Keypoint(x=-20, y=20)
],
shape=(image.shape[0], image.shape[1])
)
]
print("image shape:", image.shape)
augs = [
iaa.PiecewiseAffine(scale=0.05),
iaa.PiecewiseAffine(scale=0.1),
iaa.PiecewiseAffine(scale=0.2)
]
ia.imshow(kps[0].draw_on_image(image))
print("-----------------")
print("Random aug per image")
print("-----------------")
for aug in augs:
images_aug = []
for _ in range(16):
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(image)
kps_aug = aug_det.augment_keypoints(kps)[0]
img_aug_kps = keypoints_draw_on_image(kps_aug, img_aug)
img_aug_kps = np.pad(img_aug_kps, ((1, 1), (1, 1), (0, 0)), mode="constant", constant_values=255)
images_aug.append(img_aug_kps)
print(aug.name)
ia.imshow(ia.draw_grid(images_aug))
# TODO why was this used here?
def keypoints_draw_on_image(kps, image, color=[0, 255, 0], size=3, copy=True, raise_if_out_of_image=False, border=50):
if copy:
image = np.copy(image)
image = np.pad(
image,
((border, border), (border, border), (0, 0)),
mode="constant",
constant_values=0
)
height, width = image.shape[0:2]
for keypoint in kps.keypoints:
y, x = keypoint.y + border, keypoint.x + border
if 0 <= y < height and 0 <= x < width:
x1 = max(x - size//2, 0)
x2 = min(x + 1 + size//2, width - 1)
y1 = max(y - size//2, 0)
y2 = min(y + 1 + size//2, height - 1)
image[y1:y2, x1:x2] = color
else:
if raise_if_out_of_image:
raise Exception("Cannot draw keypoint x=%d, y=%d on image with shape %s." % (y, x, image.shape))
return image
if __name__ == "__main__":
main()
================================================
FILE: checks/check_poisson_noise.py
================================================
from __future__ import print_function, division
import imgaug as ia
from imgaug import augmenters as iaa
def main():
img = ia.data.quokka(0.5)
augs = [
("iaa.AdditivePoissonNoise(0)", iaa.AdditivePoissonNoise(0)),
("iaa.AdditivePoissonNoise(10.0)", iaa.AdditivePoissonNoise(10.0)),
("iaa.AdditivePoissonNoise(20.0)", iaa.AdditivePoissonNoise(20.0)),
("iaa.AdditivePoissonNoise(50.0)", iaa.AdditivePoissonNoise(50.0)),
("iaa.AdditivePoissonNoise((10.0, 20))", iaa.AdditivePoissonNoise((10.0, 20))),
("iaa.AdditivePoissonNoise([10.0, 20.0, 50])", iaa.AdditivePoissonNoise([10.0, 20.0, 50])),
("iaa.AdditivePoissonNoise(20, per_channel=True)", iaa.AdditivePoissonNoise(50, per_channel=True)),
]
for descr, aug in augs:
print(descr)
imgs_aug = aug.augment_images([img] * 16)
ia.imshow(ia.draw_grid(imgs_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_polygons_stay_valid_during_augmentation.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
def main():
nb_checked = 0
augs = iaa.SomeOf((1, None), [
iaa.Resize({"height": (1, 100), "width": (1, 100)}),
iaa.Affine(
scale=(0.01, 2.0),
rotate=(-360, 360),
shear=(-360, 360),
translate_px={"x": (-50, 50), "y": (-50, 50)}
),
iaa.PerspectiveTransform((0.01, 0.2))
])
height, width = 100, 200
while True:
poly = create_random_polygon(height, width, nb_checked)
psoi = PolygonsOnImage([poly], shape=(height, width, 3))
psoi_aug = augs.augment_polygons(psoi)
if not poly.is_valid or not psoi_aug.polygons[0].is_valid:
print("poly: ", poly, poly.is_valid)
print("poly_aug: ", psoi_aug.polygons[0], psoi_aug.polygons[0].is_valid)
assert poly.is_valid
assert psoi_aug.polygons[0].is_valid
nb_checked += 1
if nb_checked % 100 == 0:
print("Checked %d..." % (nb_checked,))
if nb_checked > 100000:
break
def create_random_polygon(height, width, seed):
rs = np.random.RandomState(seed)
nb_points = rs.randint(3, 50)
coords = rs.rand(nb_points, 2)
coords = (coords * 2 - 0.5) # allow coords outside of the image plane
coords[:, 0] *= width
coords[:, 1] *= height
poly = Polygon(coords)
if poly.is_valid:
return poly
new_seed = rs.randint(ia.SEED_MIN_VALUE, ia.SEED_MAX_VALUE)
return create_random_polygon(height, width, new_seed)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_pooling.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
def main():
run(iaa.AveragePooling)
run(iaa.MaxPooling)
run(iaa.MinPooling)
run(iaa.MedianPooling)
def run(clazz):
image = ia.quokka_square((128, 128))
aug = clazz(2)
ia.imshow(
ia.draw_grid(aug.augment_images([image] * (5*5)))
)
aug = clazz(2, keep_size=False)
ia.imshow(
ia.draw_grid(aug.augment_images([image] * (5*5)))
)
aug_pool = clazz(((0, 10), (0, 10)))
aug_blur = clazz(((0, 10), (0, 10)))
ia.imshow(
np.hstack([
ia.draw_grid(aug_pool.augment_images([image] * (4*5)), cols=4),
ia.draw_grid(aug_blur.augment_images([image] * (4*5)), cols=4)
])
)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_quantize_uniform_to_n_bits.py
================================================
from __future__ import print_function, division, absolute_import
import imgaug.augmenters as iaa
import imgaug as ia
import timeit
def main():
for size in [64, 128, 256, 512, 1024]:
for nb_bits in [1, 2, 3, 4, 5, 6, 7, 8]:
time_iaa = timeit.timeit(
"iaa.quantize_uniform_to_n_bits(image, %d)" % (nb_bits,),
number=1000,
setup=(
"import imgaug as ia; "
"import imgaug.augmenters as iaa; "
"image = ia.quokka_square((%d, %d))" % (size, size))
)
time_pil = timeit.timeit(
"np.asarray("
"PIL.ImageOps.posterize(PIL.Image.fromarray(image), %d)"
")" % (nb_bits,),
number=1000,
setup=(
"import numpy as np; "
"import PIL.Image; "
"import PIL.ImageOps; "
"import imgaug as ia; "
"image = ia.quokka_square((%d, %d))" % (size, size))
)
print("[size=%04d, bits=%d] iaa=%.4f pil=%.4f" % (
size, nb_bits, time_iaa, time_pil))
image = ia.quokka_square((128, 128))
images_q = [iaa.quantize_uniform_to_n_bits(image, nb_bits)
for nb_bits
in [1, 2, 3, 4, 5, 6, 7, 8]]
ia.imshow(ia.draw_grid(images_q, cols=8, rows=1))
def posterize(arr, n_bits):
import numpy as np
import PIL.Image
import PIL.ImageOps
img = PIL.Image.fromarray(arr)
img_q = PIL.ImageOps.posterize(img, n_bits)
return np.asarray(img_q)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_rain.py
================================================
from __future__ import print_function, division, absolute_import
import imageio
import imgaug as ia
from imgaug import augmenters as iaa
def main():
augs = [
iaa.Rain(speed=(0.1, 0.3)),
iaa.Rain(),
iaa.Rain(drop_size=(0.1, 0.2))
]
image = imageio.imread(
("https://upload.wikimedia.org/wikipedia/commons/8/89/"
"Kukle%2CCzech_Republic..jpg"),
format="jpg")
for aug, size in zip(augs, [0.1, 0.2, 1.0]):
image_rs = ia.imresize_single_image(image, size, "cubic")
print(image_rs.shape)
images_aug = aug.augment_images([image_rs] * 64)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_randaugment.py
================================================
from __future__ import print_function, division, absolute_import
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
def main():
image = ia.data.quokka(0.25)
for N in [1, 2]:
print("N=%d" % (N,))
images_aug = []
for M in [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]:
images_aug.extend(
iaa.RandAugment(n=N, m=M, random_state=1)(images=[image] * 10)
)
ia.imshow(ia.draw_grid(images_aug, cols=10))
for M in [0, 1, 2, 4, 8, 10]:
print("M=%d" % (M,))
aug = iaa.RandAugment(m=M, random_state=1)
images_aug = []
for _ in np.arange(6):
images_aug.extend(aug(images=[image] * 16))
ia.imshow(ia.draw_grid(images_aug, cols=16, rows=6))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_readme_examples.py
================================================
"""
Script to verify all examples in the readme.
Simply execute
python test_readme_examples.py
The tests in this file are currently not unittests!
They do plot images.
TODO move this to checks/ ?
"""
from __future__ import print_function, division
import functools
def main():
example_simple_training_setting()
example_very_complex_augmentation_pipeline()
example_augment_images_and_keypoints()
example_augment_images_and_bounding_boxes()
example_augment_images_and_polygons()
example_augment_images_and_linestrings()
example_augment_images_and_heatmaps()
example_augment_images_and_segmentation_maps()
example_visualize_augmented_images()
example_visualize_augmented_non_image_data()
example_using_augmenters_only_once()
example_multicore_augmentation()
example_probability_distributions_as_parameters()
example_withchannels()
example_hooks()
def seeded(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
import imgaug.random as iarandom
iarandom.seed(0)
func(*args, **kwargs)
return wrapper
@seeded
def example_simple_training_setting():
print("Example: Simple Training Setting")
import numpy as np
import imgaug.augmenters as iaa
def load_batch(batch_idx):
# dummy function, implement this
# Return a numpy array of shape (N, height, width, #channels)
# or a list of (height, width, #channels) arrays (may have different image
# sizes).
# Images should be in RGB for colorspace augmentations.
# (cv2.imread() returns BGR!)
# Images should usually be in uint8 with values from 0-255.
return np.zeros((128, 32, 32, 3), dtype=np.uint8) + (batch_idx % 255)
def train_on_images(images):
# dummy function, implement this
pass
# Pipeline:
# (1) Crop images from each side by 1-16px, do not resize the results
# images back to the input size. Keep them at the cropped size.
# (2) Horizontally flip 50% of the images.
# (3) Blur images using a gaussian kernel with sigma between 0.0 and 3.0.
seq = iaa.Sequential([
iaa.Crop(px=(1, 16), keep_size=False),
iaa.Fliplr(0.5),
iaa.GaussianBlur(sigma=(0, 3.0))
])
for batch_idx in range(100):
images = load_batch(batch_idx)
images_aug = seq(images=images) # done by the library
train_on_images(images_aug)
# -----
# Make sure that the example really does something
if batch_idx == 0:
assert not np.array_equal(images, images_aug)
@seeded
def example_very_complex_augmentation_pipeline():
print("Example: Very Complex Augmentation Pipeline")
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
# random example images
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
# Sometimes(0.5, ...) applies the given augmenter in 50% of all cases,
# e.g. Sometimes(0.5, GaussianBlur(0.3)) would blur roughly every second image.
sometimes = lambda aug: iaa.Sometimes(0.5, aug)
# Define our sequence of augmentation steps that will be applied to every image
# All augmenters with per_channel=0.5 will sample one value _per image_
# in 50% of all cases. In all other cases they will sample new values
# _per channel_.
seq = iaa.Sequential(
[
# apply the following augmenters to most images
iaa.Fliplr(0.5), # horizontally flip 50% of all images
iaa.Flipud(0.2), # vertically flip 20% of all images
# crop images by -5% to 10% of their height/width
sometimes(iaa.CropAndPad(
percent=(-0.05, 0.1),
pad_mode=ia.ALL,
pad_cval=(0, 255)
)),
sometimes(iaa.Affine(
scale={"x": (0.8, 1.2), "y": (0.8, 1.2)}, # scale images to 80-120% of their size, individually per axis
translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)}, # translate by -20 to +20 percent (per axis)
rotate=(-45, 45), # rotate by -45 to +45 degrees
shear=(-16, 16), # shear by -16 to +16 degrees
order=[0, 1], # use nearest neighbour or bilinear interpolation (fast)
cval=(0, 255), # if mode is constant, use a cval between 0 and 255
mode=ia.ALL # use any of scikit-image's warping modes (see 2nd image from the top for examples)
)),
# execute 0 to 5 of the following (less important) augmenters per image
# don't execute all of them, as that would often be way too strong
iaa.SomeOf((0, 5),
[
sometimes(iaa.Superpixels(p_replace=(0, 1.0), n_segments=(20, 200))), # convert images into their superpixel representation
iaa.OneOf([
iaa.GaussianBlur((0, 3.0)), # blur images with a sigma between 0 and 3.0
iaa.AverageBlur(k=(2, 7)), # blur image using local means with kernel sizes between 2 and 7
iaa.MedianBlur(k=(3, 11)), # blur image using local medians with kernel sizes between 2 and 7
]),
iaa.Sharpen(alpha=(0, 1.0), lightness=(0.75, 1.5)), # sharpen images
iaa.Emboss(alpha=(0, 1.0), strength=(0, 2.0)), # emboss images
# search either for all edges or for directed edges,
# blend the result with the original image using a blobby mask
iaa.SimplexNoiseAlpha(iaa.OneOf([
iaa.EdgeDetect(alpha=(0.5, 1.0)),
iaa.DirectedEdgeDetect(alpha=(0.5, 1.0), direction=(0.0, 1.0)),
])),
iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.05*255), per_channel=0.5), # add gaussian noise to images
iaa.OneOf([
iaa.Dropout((0.01, 0.1), per_channel=0.5), # randomly remove up to 10% of the pixels
iaa.CoarseDropout((0.03, 0.15), size_percent=(0.02, 0.05), per_channel=0.2),
]),
iaa.Invert(0.05, per_channel=True), # invert color channels
iaa.Add((-10, 10), per_channel=0.5), # change brightness of images (by -10 to 10 of original value)
iaa.AddToHueAndSaturation((-20, 20)), # change hue and saturation
# either change the brightness of the whole image (sometimes
# per channel) or change the brightness of subareas
iaa.OneOf([
iaa.Multiply((0.5, 1.5), per_channel=0.5),
iaa.FrequencyNoiseAlpha(
exponent=(-4, 0),
first=iaa.Multiply((0.5, 1.5), per_channel=True),
second=iaa.LinearContrast((0.5, 2.0))
)
]),
iaa.LinearContrast((0.5, 2.0), per_channel=0.5), # improve or worsen the contrast
iaa.Grayscale(alpha=(0.0, 1.0)),
sometimes(iaa.ElasticTransformation(alpha=(0.5, 3.5), sigma=0.25)), # move pixels locally around (with random strengths)
sometimes(iaa.PiecewiseAffine(scale=(0.01, 0.05))), # sometimes move parts of the image around
sometimes(iaa.PerspectiveTransform(scale=(0.01, 0.1)))
],
random_order=True
)
],
random_order=True
)
images_aug = seq(images=images)
# -----
# Make sure that the example really does something
assert not np.array_equal(images, images_aug)
@seeded
def example_augment_images_and_keypoints():
print("Example: Augment Images and Keypoints")
import numpy as np
import imgaug.augmenters as iaa
images = np.zeros((2, 128, 128, 3), dtype=np.uint8) # two example images
images[:, 64, 64, :] = 255
points = [
[(10.5, 20.5)], # points on first image
[(50.5, 50.5), (60.5, 60.5), (70.5, 70.5)] # points on second image
]
seq = iaa.Sequential([
iaa.AdditiveGaussianNoise(scale=0.05*255),
iaa.Affine(translate_px={"x": (1, 5)})
])
# augment keypoints and images
images_aug, points_aug = seq(images=images, keypoints=points)
print("Image 1 center", np.argmax(images_aug[0, 64, 64:64+6, 0]))
print("Image 2 center", np.argmax(images_aug[1, 64, 64:64+6, 0]))
print("Points 1", points_aug[0])
print("Points 2", points_aug[1])
@seeded
def example_augment_images_and_bounding_boxes():
print("Example: Augment Images and Bounding Boxes")
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
images = np.zeros((2, 128, 128, 3), dtype=np.uint8) # two example images
images[:, 64, 64, :] = 255
bbs = [
[ia.BoundingBox(x1=10.5, y1=15.5, x2=30.5, y2=50.5)],
[ia.BoundingBox(x1=10.5, y1=20.5, x2=50.5, y2=50.5),
ia.BoundingBox(x1=40.5, y1=75.5, x2=70.5, y2=100.5)]
]
seq = iaa.Sequential([
iaa.AdditiveGaussianNoise(scale=0.05*255),
iaa.Affine(translate_px={"x": (1, 5)})
])
images_aug, bbs_aug = seq(images=images, bounding_boxes=bbs)
@seeded
def example_augment_images_and_polygons():
print("Example: Augment Images and Polygons")
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
images = np.zeros((2, 128, 128, 3), dtype=np.uint8) # two example images
images[:, 64, 64, :] = 255
polygons = [
[ia.Polygon([(10.5, 10.5), (50.5, 10.5), (50.5, 50.5)])],
[ia.Polygon([(0.0, 64.5), (64.5, 0.0), (128.0, 128.0), (64.5, 128.0)])]
]
seq = iaa.Sequential([
iaa.AdditiveGaussianNoise(scale=0.05*255),
iaa.Affine(translate_px={"x": (1, 5)})
])
images_aug, polygons_aug = seq(images=images, polygons=polygons)
@seeded
def example_augment_images_and_linestrings():
print("Example: Augment Images and LineStrings")
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
images = np.zeros((2, 128, 128, 3), dtype=np.uint8) # two example images
images[:, 64, 64, :] = 255
ls = [
[ia.LineString([(10.5, 10.5), (50.5, 10.5), (50.5, 50.5)])],
[ia.LineString([(0.0, 64.5), (64.5, 0.0), (128.0, 128.0), (64.5, 128.0),
(128.0, 0.0)])]
]
seq = iaa.Sequential([
iaa.AdditiveGaussianNoise(scale=0.05*255),
iaa.Affine(translate_px={"x": (1, 5)})
])
images_aug, ls_aug = seq(images=images, line_strings=ls)
@seeded
def example_augment_images_and_heatmaps():
print("Example: Augment Images and Heatmaps")
import numpy as np
import imgaug.augmenters as iaa
# Standard scenario: You have N RGB-images and additionally 21 heatmaps per
# image. You want to augment each image and its heatmaps identically.
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
heatmaps = np.random.random(size=(16, 64, 64, 1)).astype(np.float32)
seq = iaa.Sequential([
iaa.GaussianBlur((0, 3.0)),
iaa.Affine(translate_px={"x": (-40, 40)}),
iaa.Crop(px=(0, 10))
])
images_aug, heatmaps_aug = seq(images=images, heatmaps=heatmaps)
@seeded
def example_augment_images_and_segmentation_maps():
print("Example: Augment Images and Segmentation Maps")
import numpy as np
import imgaug.augmenters as iaa
# Standard scenario: You have N=16 RGB-images and additionally one segmentation
# map per image. You want to augment each image and its heatmaps identically.
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
segmaps = np.random.randint(0, 10, size=(16, 64, 64, 1), dtype=np.int32)
seq = iaa.Sequential([
iaa.GaussianBlur((0, 3.0)),
iaa.Affine(translate_px={"x": (-40, 40)}),
iaa.Crop(px=(0, 10))
])
images_aug, segmaps_aug = seq(images=images, segmentation_maps=segmaps)
@seeded
def example_visualize_augmented_images():
print("Example: Visualize Augmented Images")
import numpy as np
import imgaug.augmenters as iaa
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
seq = iaa.Sequential([iaa.Fliplr(0.5), iaa.GaussianBlur((0, 3.0))])
# Show an image with 8*8 augmented versions of image 0 and 8*8 augmented
# versions of image 1. Identical augmentations will be applied to
# image 0 and 1.
seq.show_grid([images[0], images[1]], cols=8, rows=8)
@seeded
def example_visualize_augmented_non_image_data():
print("Example: Visualize Augmented Non-Image Data")
import numpy as np
import imgaug as ia
image = np.zeros((64, 64, 3), dtype=np.uint8)
# points
kps = [ia.Keypoint(x=10.5, y=20.5), ia.Keypoint(x=60.5, y=60.5)]
kpsoi = ia.KeypointsOnImage(kps, shape=image.shape)
image_with_kps = kpsoi.draw_on_image(image, size=7, color=(0, 0, 255))
ia.imshow(image_with_kps)
# bbs
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=10.5, y1=20.5, x2=50.5, y2=30.5)
], shape=image.shape)
image_with_bbs = bbsoi.draw_on_image(image)
image_with_bbs = ia.BoundingBox(
x1=50.5, y1=10.5, x2=100.5, y2=16.5
).draw_on_image(image_with_bbs, color=(255, 0, 0), size=3)
ia.imshow(image_with_bbs)
# polygons
psoi = ia.PolygonsOnImage([
ia.Polygon([(10.5, 20.5), (50.5, 30.5), (10.5, 50.5)])
], shape=image.shape)
image_with_polys = psoi.draw_on_image(
image, alpha_points=0, alpha_face=0.5, color_lines=(255, 0, 0))
ia.imshow(image_with_polys)
# heatmaps
# pick first result via [0] here, because one image per heatmap channel
# is generated
hms = ia.HeatmapsOnImage(np.random.random(size=(32, 32, 1)).astype(np.float32),
shape=image.shape)
image_with_hms = hms.draw_on_image(image)[0]
ia.imshow(image_with_hms)
@seeded
def example_using_augmenters_only_once():
print("Example: Using Augmenters Only Once")
from imgaug import augmenters as iaa
import numpy as np
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
# always horizontally flip each input image
images_aug = iaa.Fliplr(1.0)(images=images)
# vertically flip each input image with 90% probability
images_aug = iaa.Flipud(0.9)(images=images)
# blur 50% of all images using a gaussian kernel with a sigma of 3.0
images_aug = iaa.Sometimes(0.5, iaa.GaussianBlur(3.0))(images=images)
@seeded
def example_multicore_augmentation():
print("Example: Multicore Augmentation")
import skimage.data
import imgaug as ia
import imgaug.augmenters as iaa
from imgaug.augmentables.batches import UnnormalizedBatch
# Number of batches and batch size for this example
nb_batches = 10
batch_size = 32
# Example augmentation sequence to run in the background
augseq = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.CoarseDropout(p=0.1, size_percent=0.1)
])
# For simplicity, we use the same image here many times
astronaut = skimage.data.astronaut()
astronaut = ia.imresize_single_image(astronaut, (64, 64))
# Make batches out of the example image (here: 10 batches, each 32 times
# the example image)
batches = []
for _ in range(nb_batches):
batches.append(UnnormalizedBatch(images=[astronaut] * batch_size))
# Show the augmented images.
# Note that augment_batches() returns a generator.
for images_aug in augseq.augment_batches(batches, background=True):
ia.imshow(ia.draw_grid(images_aug.images_aug, cols=8))
@seeded
def example_probability_distributions_as_parameters():
print("Example: Probability Distributions as Parameters")
import numpy as np
from imgaug import augmenters as iaa
from imgaug import parameters as iap
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
# Blur by a value sigma which is sampled from a uniform distribution
# of range 10.1 <= x < 13.0.
# The convenience shortcut for this is: GaussianBlur((10.1, 13.0))
blurer = iaa.GaussianBlur(10 + iap.Uniform(0.1, 3.0))
images_aug = blurer(images=images)
# Blur by a value sigma which is sampled from a gaussian distribution
# N(1.0, 0.1), i.e. sample a value that is usually around 1.0.
# Clip the resulting value so that it never gets below 0.1 or above 3.0.
blurer = iaa.GaussianBlur(iap.Clip(iap.Normal(1.0, 0.1), 0.1, 3.0))
images_aug = blurer(images=images)
@seeded
def example_withchannels():
print("Example: WithChannels")
import numpy as np
import imgaug.augmenters as iaa
# fake RGB images
images = np.random.randint(0, 255, (16, 128, 128, 3), dtype=np.uint8)
# add a random value from the range (-30, 30) to the first two channels of
# input images (e.g. to the R and G channels)
aug = iaa.WithChannels(
channels=[0, 1],
children=iaa.Add((-30, 30))
)
images_aug = aug(images=images)
@seeded
def example_hooks():
print("Example: Hooks")
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
# Images and heatmaps, just arrays filled with value 30.
# We define the heatmaps here as uint8 arrays as we are going to feed them
# through the pipeline similar to normal images. In that way, every
# augmenter is applied to them.
images = np.full((16, 128, 128, 3), 30, dtype=np.uint8)
heatmaps = np.full((16, 128, 128, 21), 30, dtype=np.uint8)
# add vertical lines to see the effect of flip
images[:, 16:128-16, 120:124, :] = 120
heatmaps[:, 16:128-16, 120:124, :] = 120
seq = iaa.Sequential([
iaa.Fliplr(0.5, name="Flipper"),
iaa.GaussianBlur((0, 3.0), name="GaussianBlur"),
iaa.Dropout(0.02, name="Dropout"),
iaa.AdditiveGaussianNoise(scale=0.01*255, name="MyLittleNoise"),
iaa.AdditiveGaussianNoise(loc=32, scale=0.0001*255, name="SomeOtherNoise"),
iaa.Affine(translate_px={"x": (-40, 40)}, name="Affine")
])
# change the activated augmenters for heatmaps,
# we only want to execute horizontal flip, affine transformation and one of
# the gaussian noises
def activator_heatmaps(images, augmenter, parents, default):
if augmenter.name in ["GaussianBlur", "Dropout", "MyLittleNoise"]:
return False
else:
# default value for all other augmenters
return default
hooks_heatmaps = ia.HooksImages(activator=activator_heatmaps)
# call to_deterministic() once per batch, NOT only once at the start
seq_det = seq.to_deterministic()
images_aug = seq_det(images=images)
heatmaps_aug = seq_det(images=heatmaps, hooks=hooks_heatmaps)
# -----------
ia.show_grid(images_aug)
ia.show_grid(heatmaps_aug[..., 0:3])
if __name__ == "__main__":
main()
================================================
FILE: checks/check_remove_saturation.py
================================================
import imgaug as ia
import imgaug.augmenters as iaa
import imageio
def main():
urls = [
("https://upload.wikimedia.org/wikipedia/commons/thumb/4/43/"
"Sarcophilus_harrisii_taranna.jpg/"
"320px-Sarcophilus_harrisii_taranna.jpg"),
("https://upload.wikimedia.org/wikipedia/commons/thumb/b/ba/"
"Vincent_van_Gogh_-_Wheatfield_with_crows_-_Google_Art_Project.jpg/"
"320px-Vincent_van_Gogh_-_Wheatfield_with_crows_-_Google_Art_Project"
".jpg"),
("https://upload.wikimedia.org/wikipedia/commons/thumb/0/0c/"
"Galerella_sanguinea_Zoo_Praha_2011-2.jpg/207px-Galerella_sanguinea_"
"Zoo_Praha_2011-2.jpg"),
("https://upload.wikimedia.org/wikipedia/commons/thumb/9/96/"
"Ambrosius_Bosschaert_the_Elder_%28Dutch_-_Flower_Still_Life_-_"
"Google_Art_Project.jpg/307px-Ambrosius_Bosschaert_the_Elder_%28"
"Dutch_-_Flower_Still_Life_-_Google_Art_Project.jpg")
]
image = imageio.imread(urls[3])
aug = iaa.RemoveSaturation()
images_aug = aug(images=[image] * (5*5))
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_resize.py
================================================
from __future__ import print_function, division
import cv2
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
def main():
# test 2d image
ia.imshow(iaa.Resize(64).augment_image(data.camera()))
# test many images
images = [ia.data.quokka(size=0.5), ia.data.quokka(size=0.5)]
images_aug = iaa.Resize(64).augment_images(images)
ia.imshow(np.hstack(images_aug))
image = ia.data.quokka(size=0.5)
kps = [ia.KeypointsOnImage(
[ia.Keypoint(x=245, y=203), ia.Keypoint(x=365, y=195), ia.Keypoint(x=313, y=269)],
shape=(image.shape[0]*2, image.shape[1]*2)
)]
kps[0] = kps[0].on(image.shape)
print("image shape:", image.shape)
augs = [
iaa.Resize("keep", name="keep"),
iaa.Resize(32, name="i32"),
iaa.Resize(0.5, name="f05"),
iaa.Resize({"height": 32}, name="height32"),
iaa.Resize({"width": 32}, name="width32"),
iaa.Resize({"height": "keep", "width": 32}, name="keep-width32"),
iaa.Resize({"height": 32, "width": "keep"}, name="height32-keep"),
iaa.Resize({"height": "keep", "width": "keep"}, name="keep-keep"),
iaa.Resize({"height": 32, "width": 64}, name="height32width64"),
iaa.Resize({"height": 64, "width": "keep-aspect-ratio"}, name="height64width-kar"),
iaa.Resize({"height": "keep-aspect-ratio", "width": 64}, name="height-kar_width64")
]
augs_many = [
iaa.Resize((32, 128), name="tuple-32-128"),
iaa.Resize([32, 64, 128], name="list-32-64-128"),
iaa.Resize({"height": (32, 128), "width": "keep"}, name="height-32-64_width-keep"),
iaa.Resize({"height": (32, 128), "width": "keep-aspect-ratio"}, name="height-32-128_width-kar"),
iaa.Resize({"height": (32, 128), "width": (32, 128)}, name="height-32-128_width-32-128")
]
print("original", image.shape)
ia.imshow(kps[0].draw_on_image(image))
print("-----------------")
print("Same size per image")
print("-----------------")
for aug in augs:
img_aug = aug.augment_image(image)
kps_aug = aug.augment_keypoints(kps)[0]
img_aug_kps = kps_aug.draw_on_image(img_aug)
print(aug.name, img_aug_kps.shape, img_aug_kps.shape[1]/img_aug_kps.shape[0])
ia.imshow(img_aug_kps)
print("-----------------")
print("Random per image")
print("-----------------")
for aug in augs_many:
images_aug = []
for _ in range(64):
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(image)
kps_aug = aug_det.augment_keypoints(kps)[0]
img_aug_kps = kps_aug.draw_on_image(img_aug)
images_aug.append(img_aug_kps)
print(aug.name)
ia.imshow(ia.draw_grid(images_aug))
print("nearest/cv2.INTER_NEAREST/cubic")
ia.imshow(np.hstack([
iaa.Resize(64, interpolation="nearest").augment_image(image),
iaa.Resize(64, interpolation=cv2.INTER_NEAREST).augment_image(image),
iaa.Resize(64, interpolation="cubic").augment_image(image)
]))
print("random nearest/cubic")
iaa.Resize(64, interpolation=["nearest", "cubic"]).show_grid([image], 8, 8)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_rot90.py
================================================
from __future__ import print_function, division
import imgaug as ia
from imgaug import augmenters as iaa
def main():
augs = [
("iaa.Rot90(-1, keep_size=False)", iaa.Rot90(-1, keep_size=False)),
("iaa.Rot90(0, keep_size=False)", iaa.Rot90(0, keep_size=False)),
("iaa.Rot90(1, keep_size=False)", iaa.Rot90(1, keep_size=False)),
("iaa.Rot90(2, keep_size=False)", iaa.Rot90(2, keep_size=False)),
("iaa.Rot90(3, keep_size=False)", iaa.Rot90(3, keep_size=False)),
("iaa.Rot90(4, keep_size=False)", iaa.Rot90(4, keep_size=False)),
("iaa.Rot90(-1, keep_size=True)", iaa.Rot90(-1, keep_size=True)),
("iaa.Rot90(0, keep_size=True)", iaa.Rot90(0, keep_size=True)),
("iaa.Rot90(1, keep_size=True)", iaa.Rot90(1, keep_size=True)),
("iaa.Rot90(2, keep_size=True)", iaa.Rot90(2, keep_size=True)),
("iaa.Rot90(3, keep_size=True)", iaa.Rot90(3, keep_size=True)),
("iaa.Rot90(4, keep_size=True)", iaa.Rot90(4, keep_size=True)),
("iaa.Rot90([0, 1, 2, 3, 4], keep_size=False)", iaa.Rot90([0, 1, 2, 3, 4], keep_size=False)),
("iaa.Rot90([0, 1, 2, 3, 4], keep_size=True)", iaa.Rot90([0, 1, 2, 3, 4], keep_size=True)),
("iaa.Rot90((0, 4), keep_size=False)", iaa.Rot90((0, 4), keep_size=False)),
("iaa.Rot90((0, 4), keep_size=True)", iaa.Rot90((0, 4), keep_size=True)),
("iaa.Rot90((1, 3), keep_size=False)", iaa.Rot90((1, 3), keep_size=False)),
("iaa.Rot90((1, 3), keep_size=True)", iaa.Rot90((1, 3), keep_size=True))
]
image = ia.data.quokka(0.25)
print("--------")
print("Image + Keypoints")
print("--------")
kps = ia.quokka_keypoints(0.25)
for name, aug in augs:
print(name, "...")
aug_det = aug.to_deterministic()
images_aug = aug_det.augment_images([image] * 16)
kps_aug = aug_det.augment_keypoints([kps] * 16)
images_aug = [kps_aug_i.draw_on_image(image_aug_i, size=5)
for image_aug_i, kps_aug_i in zip(images_aug, kps_aug)]
ia.imshow(ia.draw_grid(images_aug))
print("--------")
print("Image + Heatmaps (low res)")
print("--------")
hms = ia.quokka_heatmap(0.10)
for name, aug in augs:
print(name, "...")
aug_det = aug.to_deterministic()
images_aug = aug_det.augment_images([image] * 16)
hms_aug = aug_det.augment_heatmaps([hms] * 16)
images_aug = [hms_aug_i.draw_on_image(image_aug_i)[0]
for image_aug_i, hms_aug_i in zip(images_aug, hms_aug)]
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_seed.py
================================================
from __future__ import print_function, division
import numpy as np
from skimage import data
import imgaug as ia
import imgaug.random as iarandom
from imgaug import augmenters as iaa
def main():
img = data.astronaut()
img = ia.imresize_single_image(img, (64, 64))
aug = iaa.Fliplr(0.5)
unseeded1 = aug.draw_grid(img, cols=8, rows=1)
unseeded2 = aug.draw_grid(img, cols=8, rows=1)
iarandom.seed(1000)
seeded1 = aug.draw_grid(img, cols=8, rows=1)
seeded2 = aug.draw_grid(img, cols=8, rows=1)
iarandom.seed(1000)
reseeded1 = aug.draw_grid(img, cols=8, rows=1)
reseeded2 = aug.draw_grid(img, cols=8, rows=1)
iarandom.seed(1001)
reseeded3 = aug.draw_grid(img, cols=8, rows=1)
reseeded4 = aug.draw_grid(img, cols=8, rows=1)
all_rows = np.vstack([unseeded1, unseeded2, seeded1, seeded2, reseeded1, reseeded2, reseeded3, reseeded4])
ia.imshow(all_rows)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_segmentation_maps.py
================================================
from __future__ import print_function
import numpy as np
import imgaug as ia
from imgaug import augmenters as iaa
def main():
quokka = ia.data.quokka(size=0.5)
h, w = quokka.shape[0:2]
c = 1
segmap = np.zeros((h, w, c), dtype=np.int32)
segmap[70:120, 90:150, 0] = 1
segmap[30:70, 50:65, 0] = 2
segmap[20:50, 55:85, 0] = 3
segmap[120:140, 0:20, 0] = 4
segmap = ia.SegmentationMapsOnImage(segmap, quokka.shape)
print("Affine...")
aug = iaa.Affine(translate_px={"x": 20}, mode="constant", cval=128)
quokka_aug = aug.augment_image(quokka)
segmaps_aug = aug.augment_segmentation_maps([segmap])[0]
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("Affine with mode=edge...")
aug = iaa.Affine(translate_px={"x": 20}, mode="edge")
quokka_aug = aug.augment_image(quokka)
segmaps_aug = aug.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("PiecewiseAffine...")
aug = iaa.PiecewiseAffine(scale=0.04)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("PerspectiveTransform...")
aug = iaa.PerspectiveTransform(scale=0.04)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("ElasticTransformation alpha=3, sig=0.5...")
aug = iaa.ElasticTransformation(alpha=3.0, sigma=0.5)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("ElasticTransformation alpha=10, sig=3...")
aug = iaa.ElasticTransformation(alpha=10.0, sigma=3.0)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("ElasticTransformation alpha=200, sig=20...")
aug = iaa.ElasticTransformation(alpha=200.0, sigma=20.0)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("CopAndPad mode=constant...")
aug = iaa.CropAndPad(px=(-10, 10, 15, -15), pad_mode="constant", pad_cval=128)
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("CropAndPad mode=edge...")
aug = iaa.CropAndPad(px=(-10, 10, 15, -15), pad_mode="edge")
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
print("Resize...")
aug = iaa.Resize(0.5, interpolation="nearest")
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(ia.draw_grid([segmaps_drawn, segmaps_aug_drawn], cols=2))
print("Alpha...")
aug = iaa.Alpha(0.7, iaa.Affine(rotate=20))
aug_det = aug.to_deterministic()
quokka_aug = aug_det.augment_image(quokka)
segmaps_aug = aug_det.augment_segmentation_maps(segmap)
segmaps_drawn = segmap.draw_on_image(quokka)[0]
segmaps_aug_drawn = segmaps_aug.draw_on_image(quokka_aug)[0]
ia.imshow(
np.hstack([
segmaps_drawn,
segmaps_aug_drawn
])
)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_single_image_warning.py
================================================
from __future__ import print_function, division
import numpy as np
from imgaug import augmenters as iaa
def main():
def z(shape):
return np.zeros(shape, dtype=np.uint8)
seq = iaa.Identity()
print("This should generate NO warning:")
_image_aug = seq.augment_images(z((1, 16, 16, 3)))
print("This should generate NO warning:")
_image_aug = seq.augment_images(z((16, 16, 8)))
print("This should generate NO warning:")
_image_aug = seq.augment_images([z((16, 16, 3))])
print("This should generate NO warning:")
_image_aug = seq.augment_images([z((16, 16))])
print("This should generate a warning:")
_image_aug = seq.augment_images(z((16, 16, 3)))
print("This should generate a warning:")
for _ in range(2):
_image_aug = seq.augment_images(z((16, 16, 1)))
print("This should fail:")
_image_aug = seq.augment_images(z((16, 16)))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_snowflakes.py
================================================
from __future__ import print_function, division
import imageio
import imgaug as ia
from imgaug import augmenters as iaa
def main():
for size in [0.1, 0.2, 1.0]:
image = imageio.imread("https://upload.wikimedia.org/wikipedia/commons/8/89/Kukle%2CCzech_Republic..jpg",
format="jpg")
image = ia.imresize_single_image(image, size, "cubic")
print(image.shape)
augs = [
("iaa.Snowflakes()", iaa.Snowflakes())
]
for descr, aug in augs:
print(descr)
images_aug = aug.augment_images([image] * 64)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_snowflakes_layer.py
================================================
from __future__ import print_function, division
import imageio
import imgaug as ia
from imgaug import augmenters as iaa
def main():
image = imageio.imread("https://upload.wikimedia.org/wikipedia/commons/8/89/Kukle%2CCzech_Republic..jpg",
format="jpg")
augs = [
("iaa.SnowflakesLayer()", iaa.SnowflakesLayer(
density=0.05, density_uniformity=0.5, flake_size=0.9, flake_size_uniformity=0.5,
angle=(-45, 45), speed=(0.001, 0.04), blur_sigma_fraction=(0.75*0.0001, 0.75*0.001))
)
]
for descr, aug in augs:
print(descr)
images_aug = aug.augment_images([image] * 64)
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_solarize.py
================================================
from __future__ import print_function, division, absolute_import
import imgaug as ia
import imgaug.augmenters as iaa
import timeit
def main():
for size in [64, 128, 256, 512, 1024]:
for threshold in [64, 128, 192]:
time_iaa = timeit.timeit(
"iaa.solarize(image, %d)" % (threshold,),
number=1000,
setup=(
"import imgaug as ia; "
"import imgaug.augmenters as iaa; "
"image = ia.quokka_square((%d, %d))" % (size, size))
)
time_pil = timeit.timeit(
"np.asarray("
"PIL.ImageOps.solarize(PIL.Image.fromarray(image), %d)"
")" % (threshold,),
number=1000,
setup=(
"import numpy as np; "
"import PIL.Image; "
"import PIL.ImageOps; "
"import imgaug as ia; "
"image = ia.quokka_square((%d, %d))" % (size, size))
)
print("[size=%04d, thresh=%03d] iaa=%.4f pil=%.4f" % (
size, threshold, time_iaa, time_pil))
image = ia.quokka_square((128, 128))
images_aug = iaa.Solarize(1.0)(images=[image] * (5*5))
ia.imshow(ia.draw_grid(images_aug))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_some_of.py
================================================
from __future__ import print_function, division
import cv2
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
TIME_PER_STEP = 20000
NB_AUGS_PER_IMAGE = 10
def main():
image = data.astronaut()
image = ia.imresize_single_image(image, (64, 64))
keypoints_on_image = ia.KeypointsOnImage([ia.Keypoint(x=10, y=10)], shape=image.shape)
images_arr = np.array([image for _ in range(NB_AUGS_PER_IMAGE)])
images_list = [image for _ in range(NB_AUGS_PER_IMAGE)]
keypoints_on_images = [keypoints_on_image.deepcopy() for _ in range(NB_AUGS_PER_IMAGE)]
print("image shape:", image.shape)
print("Press ENTER or wait %d ms to proceed to the next image." % (TIME_PER_STEP,))
children = [
iaa.CoarseDropout(p=0.5, size_percent=0.05),
iaa.AdditiveGaussianNoise(scale=0.1*255)
]
n = [
None,
0,
1,
len(children),
len(children)+1,
(1, 1),
(1, len(children)),
(1, len(children)+1),
(1, None)
]
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.resizeWindow("aug", 64*NB_AUGS_PER_IMAGE, (2+4+4)*64)
rows = []
for ni in n:
aug = iaa.SomeOf(ni, children, random_order=False)
rows.append(aug.augment_images(images_arr))
grid = to_grid(rows, None)
cv2.imshow("aug", grid[..., ::-1]) # here with rgb2bgr
cv2.waitKey(TIME_PER_STEP)
for ni in n:
print("------------------------")
print("-- %s" % (str(ni),))
print("------------------------")
aug = iaa.SomeOf(ni, children, random_order=False)
aug_ro = iaa.SomeOf(ni, children, random_order=True)
aug_det = aug.to_deterministic()
aug_ro_det = aug_ro.to_deterministic()
aug_kps = []
aug_kps.extend([aug_det.augment_keypoints(keypoints_on_images)] * 4)
aug_kps.extend([aug_ro_det.augment_keypoints(keypoints_on_images)] * 4)
aug_rows = []
aug_rows.append(images_arr)
aug_rows.append(images_list)
aug_rows.append(aug_det.augment_images(images_arr))
aug_rows.append(aug_det.augment_images(images_arr))
aug_rows.append(aug_det.augment_images(images_list))
aug_rows.append(aug_det.augment_images(images_list))
aug_rows.append(aug_ro_det.augment_images(images_arr))
aug_rows.append(aug_ro_det.augment_images(images_arr))
aug_rows.append(aug_ro_det.augment_images(images_list))
aug_rows.append(aug_ro_det.augment_images(images_list))
grid = to_grid(aug_rows, aug_kps)
title = "n=%s" % (str(ni),)
grid = ia.draw_text(grid, x=5, y=5, text=title)
cv2.imshow("aug", grid[..., ::-1]) # here with rgb2bgr
cv2.waitKey(TIME_PER_STEP)
# TODO could be replaced by imgaug.draw_grid()?
def to_grid(rows, rows_kps):
if rows_kps is None:
rows = [np.hstack(list(row)) for row in rows]
return np.vstack(rows)
else:
rows_rendered = []
for row, row_kps in zip(rows, rows_kps):
row_with_kps = []
for i in range(len(row)):
img = row[i]
img_kps = row_kps[i].draw_on_image(img)
row_with_kps.append(img_kps)
rows_rendered.append(np.hstack(row_with_kps))
return np.vstack(rows_rendered)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_superpixels.py
================================================
from __future__ import print_function, division
import time
from itertools import cycle
import cv2
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
POINT_SIZE = 5
SEGMENTS_PER_STEP = 1
TIME_PER_STEP = 10
def main():
image = data.astronaut()[..., ::-1] # rgb2bgr
print(image.shape)
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.imshow("aug", image)
cv2.waitKey(TIME_PER_STEP)
for n_segments in cycle(reversed(np.arange(1, 200, SEGMENTS_PER_STEP))):
aug = iaa.Superpixels(p_replace=0.75, n_segments=n_segments)
time_start = time.time()
img_aug = aug.augment_image(image)
print("augmented %d in %.4fs" % (n_segments, time.time() - time_start))
img_aug = ia.draw_text(img_aug, x=5, y=5, text="%d" % (n_segments,))
cv2.imshow("aug", img_aug)
cv2.waitKey(TIME_PER_STEP)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_uniform_color_quantization.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
def main():
image = ia.quokka_square((256, 256))
ia.imshow(
ia.draw_grid([
iaa.quantize_uniform(image, 2),
iaa.quantize_uniform(image, 4),
iaa.quantize_uniform(image, 8),
iaa.quantize_uniform(image, 16),
iaa.quantize_uniform(image, 32),
iaa.quantize_uniform(image, 64)
], cols=6)
)
aug = iaa.UniformColorQuantization((2, 16))
ia.imshow(ia.draw_grid(aug(images=[image] * 16)))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_visually.py
================================================
"""
Tests to visually inspect the results of the library's functionality.
Run checks via
python check_visually.py
"""
from __future__ import print_function, division
import argparse
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
def main():
parser = argparse.ArgumentParser(description="Check augmenters visually.")
parser.add_argument(
"--only", default=None,
help="If this is set, then only the results of an augmenter with this name will be shown. "
"Optionally, comma-separated list.",
required=False)
args = parser.parse_args()
images = [
ia.quokka_square(size=(128, 128)),
ia.imresize_single_image(data.astronaut(), (128, 128))
]
keypoints = [
ia.KeypointsOnImage([
ia.Keypoint(x=50, y=40),
ia.Keypoint(x=70, y=38),
ia.Keypoint(x=62, y=52)
],
shape=images[0].shape
),
ia.KeypointsOnImage([
ia.Keypoint(x=55, y=32),
ia.Keypoint(x=42, y=95),
ia.Keypoint(x=75, y=89)
],
shape=images[1].shape
)
]
bounding_boxes = [
ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=10, y1=10, x2=20, y2=20),
ia.BoundingBox(x1=40, y1=50, x2=70, y2=60)
],
shape=images[0].shape
),
ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=10, y1=10, x2=20, y2=20),
ia.BoundingBox(x1=40, y1=50, x2=70, y2=60)
],
shape=images[1].shape
)
]
augmenters = [
iaa.Sequential([
iaa.CoarseDropout(p=0.5, size_percent=0.05),
iaa.AdditiveGaussianNoise(scale=0.1*255),
iaa.Crop(percent=0.1)
], name="Sequential"),
iaa.SomeOf(2, children=[
iaa.CoarseDropout(p=0.5, size_percent=0.05),
iaa.AdditiveGaussianNoise(scale=0.1*255),
iaa.Crop(percent=0.1)
], name="SomeOf"),
iaa.OneOf(children=[
iaa.CoarseDropout(p=0.5, size_percent=0.05),
iaa.AdditiveGaussianNoise(scale=0.1*255),
iaa.Crop(percent=0.1)
], name="OneOf"),
iaa.Sometimes(0.5, iaa.AdditiveGaussianNoise(scale=0.1*255), name="Sometimes"),
iaa.WithColorspace("HSV", children=[iaa.Add(20)], name="WithColorspace"),
iaa.WithChannels([0], children=[iaa.Add(20)], name="WithChannels"),
iaa.AddToHueAndSaturation((-20, 20), per_channel=True, name="AddToHueAndSaturation"),
iaa.Identity(name="Identity"),
iaa.Resize({"width": 64, "height": 64}, name="Resize"),
iaa.CropAndPad(px=(-8, 8), name="CropAndPad-px"),
iaa.Pad(px=(0, 8), name="Pad-px"),
iaa.Crop(px=(0, 8), name="Crop-px"),
iaa.Crop(percent=(0, 0.1), name="Crop-percent"),
iaa.Fliplr(0.5, name="Fliplr"),
iaa.Flipud(0.5, name="Flipud"),
iaa.Superpixels(p_replace=0.75, n_segments=50, name="Superpixels"),
iaa.Grayscale(0.5, name="Grayscale0.5"),
iaa.Grayscale(1.0, name="Grayscale1.0"),
iaa.GaussianBlur((0, 3.0), name="GaussianBlur"),
iaa.AverageBlur(k=(3, 11), name="AverageBlur"),
iaa.MedianBlur(k=(3, 11), name="MedianBlur"),
iaa.BilateralBlur(d=10, name="BilateralBlur"),
iaa.Sharpen(alpha=(0.1, 1.0), lightness=(0, 2.0), name="Sharpen"),
iaa.Emboss(alpha=(0.1, 1.0), strength=(0, 2.0), name="Emboss"),
iaa.EdgeDetect(alpha=(0.1, 1.0), name="EdgeDetect"),
iaa.DirectedEdgeDetect(alpha=(0.1, 1.0), direction=(0, 1.0), name="DirectedEdgeDetect"),
iaa.Add((-50, 50), name="Add"),
iaa.Add((-50, 50), per_channel=True, name="AddPerChannel"),
iaa.AddElementwise((-50, 50), name="AddElementwise"),
iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.1*255), name="AdditiveGaussianNoise"),
iaa.Multiply((0.5, 1.5), name="Multiply"),
iaa.Multiply((0.5, 1.5), per_channel=True, name="MultiplyPerChannel"),
iaa.MultiplyElementwise((0.5, 1.5), name="MultiplyElementwise"),
iaa.Dropout((0.0, 0.1), name="Dropout"),
iaa.CoarseDropout(p=0.05, size_percent=(0.05, 0.5), name="CoarseDropout"),
iaa.Invert(p=0.5, name="Invert"),
iaa.Invert(p=0.5, per_channel=True, name="InvertPerChannel"),
iaa.ContrastNormalization(alpha=(0.5, 2.0), name="ContrastNormalization"),
iaa.SaltAndPepper(p=0.05, name="SaltAndPepper"),
iaa.Salt(p=0.05, name="Salt"),
iaa.Pepper(p=0.05, name="Pepper"),
iaa.CoarseSaltAndPepper(p=0.05, size_percent=(0.01, 0.1), name="CoarseSaltAndPepper"),
iaa.CoarseSalt(p=0.05, size_percent=(0.01, 0.1), name="CoarseSalt"),
iaa.CoarsePepper(p=0.05, size_percent=(0.01, 0.1), name="CoarsePepper"),
iaa.Affine(
scale={"x": (0.8, 1.2), "y": (0.8, 1.2)},
translate_px={"x": (-16, 16), "y": (-16, 16)},
rotate=(-45, 45),
shear=(-16, 16),
order=ia.ALL,
cval=(0, 255),
mode=ia.ALL,
name="Affine"
),
iaa.PiecewiseAffine(scale=0.03, nb_rows=(2, 6), nb_cols=(2, 6), name="PiecewiseAffine"),
iaa.PerspectiveTransform(scale=0.1, name="PerspectiveTransform"),
iaa.ElasticTransformation(alpha=(0.5, 8.0), sigma=1.0, name="ElasticTransformation"),
iaa.Alpha(
factor=(0.0, 1.0),
first=iaa.Add(100),
second=iaa.Dropout(0.5),
per_channel=False,
name="Alpha"
),
iaa.Alpha(
factor=(0.0, 1.0),
first=iaa.Add(100),
second=iaa.Dropout(0.5),
per_channel=True,
name="AlphaPerChannel"
),
iaa.Alpha(
factor=(0.0, 1.0),
first=iaa.Affine(rotate=(-45, 45)),
per_channel=True,
name="AlphaAffine"
),
iaa.AlphaElementwise(
factor=(0.0, 1.0),
first=iaa.Add(50),
second=iaa.ContrastNormalization(2.0),
per_channel=False,
name="AlphaElementwise"
),
iaa.AlphaElementwise(
factor=(0.0, 1.0),
first=iaa.Add(50),
second=iaa.ContrastNormalization(2.0),
per_channel=True,
name="AlphaElementwisePerChannel"
),
iaa.AlphaElementwise(
factor=(0.0, 1.0),
first=iaa.Affine(rotate=(-45, 45)),
per_channel=True,
name="AlphaElementwiseAffine"
),
iaa.SimplexNoiseAlpha(
first=iaa.EdgeDetect(1.0),
per_channel=False,
name="SimplexNoiseAlpha"
),
iaa.FrequencyNoiseAlpha(
first=iaa.EdgeDetect(1.0),
per_channel=False,
name="FrequencyNoiseAlpha"
)
]
augmenters.append(iaa.Sequential([iaa.Sometimes(0.2, aug.copy()) for aug in augmenters], name="Sequential"))
augmenters.append(iaa.Sometimes(0.5, [aug.copy() for aug in augmenters], name="Sometimes"))
for augmenter in augmenters:
if args.only is None or augmenter.name in [v.strip() for v in args.only.split(",")]:
print("Augmenter: %s" % (augmenter.name,))
grid = []
for image, kps, bbs in zip(images, keypoints, bounding_boxes):
aug_det = augmenter.to_deterministic()
imgs_aug = aug_det.augment_images(np.tile(image[np.newaxis, ...], (16, 1, 1, 1)))
kps_aug = aug_det.augment_keypoints([kps] * 16)
bbs_aug = aug_det.augment_bounding_boxes([bbs] * 16)
imgs_aug_drawn = [kps_aug_one.draw_on_image(img_aug) for img_aug, kps_aug_one in zip(imgs_aug, kps_aug)]
imgs_aug_drawn = [bbs_aug_one.draw_on_image(img_aug) for img_aug, bbs_aug_one in zip(imgs_aug_drawn, bbs_aug)]
grid.append(np.hstack(imgs_aug_drawn))
ia.imshow(np.vstack(grid))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_voronoi.py
================================================
from __future__ import print_function, division
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
def main():
image = ia.quokka_square((256, 256))
reggrid_sampler = iaa.DropoutPointsSampler(
iaa.RegularGridPointsSampler(n_rows=50, n_cols=50),
0.5
)
uniform_sampler = iaa.UniformPointsSampler(50*50)
for p_replace in [1.0, 0.5, 0.1, 0.0]:
augs = [
iaa.Voronoi(points_sampler=reggrid_sampler, p_replace=p_replace,
max_size=128),
iaa.Voronoi(points_sampler=uniform_sampler, p_replace=p_replace,
max_size=128),
iaa.UniformVoronoi(50*50, p_replace=p_replace, max_size=128),
iaa.RegularGridVoronoi(50, 50, p_drop_points=0.4,
p_replace=p_replace, max_size=128),
iaa.RelativeRegularGridVoronoi(p_replace=p_replace, max_size=128)
]
images = [aug(image=image) for aug in augs]
ia.imshow(np.hstack(images))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_with_hue_and_saturation.py
================================================
from __future__ import print_function, division
import imgaug as ia
import imgaug.augmenters as iaa
def main():
image = ia.quokka_square(size=(128, 128))
images = []
for i in range(15):
aug = iaa.WithHueAndSaturation(iaa.WithChannels(0, iaa.Add(i*20)))
images.append(aug.augment_image(image))
for i in range(15):
aug = iaa.WithHueAndSaturation(iaa.WithChannels(1, iaa.Add(i*20)))
images.append(aug.augment_image(image))
ia.imshow(ia.draw_grid(images, rows=2))
if __name__ == "__main__":
main()
================================================
FILE: checks/check_withchannels.py
================================================
from __future__ import print_function, division
import cv2
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
TIME_PER_STEP = 10000
def main():
image = data.astronaut()
print("image shape:", image.shape)
print("Press ENTER or wait %d ms to proceed to the next image." % (TIME_PER_STEP,))
children_all = [
("hflip", iaa.Fliplr(1)),
("add", iaa.Add(50)),
("dropout", iaa.Dropout(0.2)),
("affine", iaa.Affine(rotate=35))
]
channels_all = [
None,
0,
[],
[0],
[0, 1],
[1, 2],
[0, 1, 2]
]
cv2.namedWindow("aug", cv2.WINDOW_NORMAL)
cv2.imshow("aug", image[..., ::-1])
cv2.waitKey(TIME_PER_STEP)
for children_title, children in children_all:
for channels in channels_all:
aug = iaa.WithChannels(channels=channels, children=children)
img_aug = aug.augment_image(image)
print("dtype", img_aug.dtype, "averages", np.average(img_aug, axis=tuple(range(0, img_aug.ndim-1))))
title = "children=%s | channels=%s" % (children_title, channels)
img_aug = ia.draw_text(img_aug, x=5, y=5, text=title)
cv2.imshow("aug", img_aug[..., ::-1]) # here with rgb2bgr
cv2.waitKey(TIME_PER_STEP)
if __name__ == "__main__":
main()
================================================
FILE: checks/check_withcolorspace.py
================================================
from __future__ import print_function, division
import numpy as np
from skimage import data
import imgaug as ia
from imgaug import augmenters as iaa
def main():
image = data.astronaut()
print("image shape:", image.shape)
aug = iaa.WithColorspace(
from_colorspace="RGB",
to_colorspace="HSV",
children=iaa.WithChannels(0, iaa.Add(50))
)
aug_no_colorspace = iaa.WithChannels(0, iaa.Add(50))
img_show = np.hstack([
image,
aug.augment_image(image),
aug_no_colorspace.augment_image(image)
])
ia.imshow(img_show)
if __name__ == "__main__":
main()
================================================
FILE: codecov.yml
================================================
#see https://github.com/codecov/support/wiki/Codecov-Yaml
codecov:
notify:
require_ci_to_pass: yes
coverage:
precision: 2 # 2 = xx.xx%, 0 = xx%
round: nearest # how coverage is rounded: down/up/nearest
range: 10...90 # custom range of coverage colors from red -> yellow -> green
status:
# https://codecov.readme.io/v1.0/docs/commit-status
project:
default:
against: auto
target: 40% # specify the target coverage for each commit status
threshold: 20% # allow this little decrease on project
# https://github.com/codecov/support/wiki/Filtering-Branches
# branches: master
if_ci_failed: error
# https://github.com/codecov/support/wiki/Patch-Status
patch:
default:
against: parent
target: 30% # specify the target "X%" coverage to hit
# threshold: 50% # allow this much decrease on patch
changes: false
parsers:
gcov:
branch_detection:
conditional: true
loop: true
macro: false
method: false
javascript:
enable_partials: false
comment:
layout: header, diff
require_changes: false
behavior: default # update if exists else create new
branches: *
ignore:
imgaug/external/*.py
================================================
FILE: imgaug/__init__.py
================================================
"""Imports for package imgaug."""
from __future__ import absolute_import
# this contains some deprecated classes/functions pointing to the new
# classes/functions, hence always place the other imports below this so that
# the deprecated stuff gets overwritten as much as possible
from imgaug.imgaug import * # pylint: disable=redefined-builtin
import imgaug.augmentables as augmentables
from imgaug.augmentables import *
import imgaug.augmenters as augmenters
import imgaug.parameters as parameters
import imgaug.dtypes as dtypes
import imgaug.data as data
__version__ = '0.4.0'
================================================
FILE: imgaug/augmentables/__init__.py
================================================
"""Combination of all augmentable classes and related functions."""
from __future__ import absolute_import
from imgaug.augmentables.kps import *
from imgaug.augmentables.bbs import *
from imgaug.augmentables.polys import *
from imgaug.augmentables.lines import *
from imgaug.augmentables.heatmaps import *
from imgaug.augmentables.segmaps import *
from imgaug.augmentables.batches import *
================================================
FILE: imgaug/augmentables/base.py
================================================
"""Interfaces used by augmentable objects.
Added in 0.4.0.
"""
from __future__ import print_function, division, absolute_import
class IAugmentable(object):
"""Interface of augmentable objects.
This interface is right now only used to "mark" augmentable objects.
It does not enforce any methods yet (but will probably in the future).
Currently, only ``*OnImage`` clases are marked as augmentable.
Non-OnImage objects are normalized to OnImage-objects.
Batches are not yet marked as augmentable, but might be in the future.
Added in 0.4.0.
"""
================================================
FILE: imgaug/augmentables/batches.py
================================================
"""Classes representing batches of normalized or unnormalized data."""
from __future__ import print_function, division, absolute_import
import collections
import numpy as np
from .. import imgaug as ia
from . import normalization as nlib
from . import utils
DEFAULT = "DEFAULT"
_AUGMENTABLE_NAMES = [
"images", "heatmaps", "segmentation_maps", "keypoints",
"bounding_boxes", "polygons", "line_strings"]
_AugmentableColumn = collections.namedtuple(
"_AugmentableColumn",
["name", "value", "attr_name"])
def _get_column_names(batch, postfix):
return [column.name
for column
in _get_columns(batch, postfix)]
def _get_columns(batch, postfix):
result = []
for name in _AUGMENTABLE_NAMES:
attr_name = name + postfix
value = getattr(batch, name + postfix)
# Every data item is either an array or a list. If there are no
# items in the array/list, there are also no shapes to change
# as shape-changes are imagewise. Hence, we can afford to check
# len() here.
if value is not None and len(value) > 0:
result.append(_AugmentableColumn(name, value, attr_name))
return result
# TODO also support (H,W,C) for heatmaps of len(images) == 1
# TODO also support (H,W) for segmaps of len(images) == 1
class UnnormalizedBatch(object):
"""
Class for batches of unnormalized data before and after augmentation.
Parameters
----------
images : None or (N,H,W,C) ndarray or (N,H,W) ndarray or iterable of (H,W,C) ndarray or iterable of (H,W) ndarray
The images to augment.
heatmaps : None or (N,H,W,C) ndarray or imgaug.augmentables.heatmaps.HeatmapsOnImage or iterable of (H,W,C) ndarray or iterable of imgaug.augmentables.heatmaps.HeatmapsOnImage
The heatmaps to augment.
If anything else than ``HeatmapsOnImage``, then the number of heatmaps
must match the number of images provided via parameter `images`.
The number is contained either in ``N`` or the first iterable's size.
segmentation_maps : None or (N,H,W) ndarray or imgaug.augmentables.segmaps.SegmentationMapsOnImage or iterable of (H,W) ndarray or iterable of imgaug.augmentables.segmaps.SegmentationMapsOnImage
The segmentation maps to augment.
If anything else than ``SegmentationMapsOnImage``, then the number of
segmaps must match the number of images provided via parameter
`images`. The number is contained either in ``N`` or the first
iterable's size.
keypoints : None or list of (N,K,2) ndarray or tuple of number or imgaug.augmentables.kps.Keypoint or iterable of (K,2) ndarray or iterable of tuple of number or iterable of imgaug.augmentables.kps.Keypoint or iterable of imgaug.augmentables.kps.KeypointOnImage or iterable of iterable of tuple of number or iterable of iterable of imgaug.augmentables.kps.Keypoint
The keypoints to augment.
If a tuple (or iterable(s) of tuple), then iterpreted as (x,y)
coordinates and must hence contain two numbers.
A single tuple represents a single coordinate on one image, an
iterable of tuples the coordinates on one image and an iterable of
iterable of tuples the coordinates on several images. Analogous if
``Keypoint`` objects are used instead of tuples.
If an ndarray, then ``N`` denotes the number of images and ``K`` the
number of keypoints on each image.
If anything else than ``KeypointsOnImage`` is provided, then the
number of keypoint groups must match the number of images provided
via parameter `images`. The number is contained e.g. in ``N`` or
in case of "iterable of iterable of tuples" in the first iterable's
size.
bounding_boxes : None or (N,B,4) ndarray or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage or iterable of (B,4) ndarray or iterable of tuple of number or iterable of imgaug.augmentables.bbs.BoundingBox or iterable of imgaug.augmentables.bbs.BoundingBoxesOnImage or iterable of iterable of tuple of number or iterable of iterable imgaug.augmentables.bbs.BoundingBox
The bounding boxes to augment.
This is analogous to the `keypoints` parameter. However, each
tuple -- and also the last index in case of arrays -- has size 4,
denoting the bounding box coordinates ``x1``, ``y1``, ``x2`` and ``y2``.
polygons : None or (N,#polys,#points,2) ndarray or imgaug.augmentables.polys.Polygon or imgaug.augmentables.polys.PolygonsOnImage or iterable of (#polys,#points,2) ndarray or iterable of tuple of number or iterable of imgaug.augmentables.kps.Keypoint or iterable of imgaug.augmentables.polys.Polygon or iterable of imgaug.augmentables.polys.PolygonsOnImage or iterable of iterable of (#points,2) ndarray or iterable of iterable of tuple of number or iterable of iterable of imgaug.augmentables.kps.Keypoint or iterable of iterable of imgaug.augmentables.polys.Polygon or iterable of iterable of iterable of tuple of number or iterable of iterable of iterable of tuple of imgaug.augmentables.kps.Keypoint
The polygons to augment.
This is similar to the `keypoints` parameter. However, each polygon
may be made up of several ``(x,y)`` coordinates (three or more are
required for valid polygons).
The following datatypes will be interpreted as a single polygon on a
single image:
* ``imgaug.augmentables.polys.Polygon``
* ``iterable of tuple of number``
* ``iterable of imgaug.augmentables.kps.Keypoint``
The following datatypes will be interpreted as multiple polygons on a
single image:
* ``imgaug.augmentables.polys.PolygonsOnImage``
* ``iterable of imgaug.augmentables.polys.Polygon``
* ``iterable of iterable of tuple of number``
* ``iterable of iterable of imgaug.augmentables.kps.Keypoint``
* ``iterable of iterable of imgaug.augmentables.polys.Polygon``
The following datatypes will be interpreted as multiple polygons on
multiple images:
* ``(N,#polys,#points,2) ndarray``
* ``iterable of (#polys,#points,2) ndarray``
* ``iterable of iterable of (#points,2) ndarray``
* ``iterable of iterable of iterable of tuple of number``
* ``iterable of iterable of iterable of tuple of imgaug.augmentables.kps.Keypoint``
line_strings : None or (N,#lines,#points,2) ndarray or imgaug.augmentables.lines.LineString or imgaug.augmentables.lines.LineStringOnImage or iterable of (#lines,#points,2) ndarray or iterable of tuple of number or iterable of imgaug.augmentables.kps.Keypoint or iterable of imgaug.augmentables.lines.LineString or iterable of imgaug.augmentables.lines.LineStringOnImage or iterable of iterable of (#points,2) ndarray or iterable of iterable of tuple of number or iterable of iterable of imgaug.augmentables.kps.Keypoint or iterable of iterable of imgaug.augmentables.polys.LineString or iterable of iterable of iterable of tuple of number or iterable of iterable of iterable of tuple of imgaug.augmentables.kps.Keypoint
The line strings to augment.
See `polygons` for more details as polygons follow a similar
structure to line strings.
data
Additional data that is saved in the batch and may be read out
after augmentation. This could e.g. contain filepaths to each image
in `images`. As this object is usually used for background
augmentation with multiple processes, the augmented Batch objects might
not be returned in the original order, making this information useful.
"""
def __init__(self, images=None, heatmaps=None, segmentation_maps=None,
keypoints=None, bounding_boxes=None, polygons=None,
line_strings=None, data=None):
"""Construct a new :class:`UnnormalizedBatch` instance."""
self.images_unaug = images
self.images_aug = None
self.heatmaps_unaug = heatmaps
self.heatmaps_aug = None
self.segmentation_maps_unaug = segmentation_maps
self.segmentation_maps_aug = None
self.keypoints_unaug = keypoints
self.keypoints_aug = None
self.bounding_boxes_unaug = bounding_boxes
self.bounding_boxes_aug = None
self.polygons_unaug = polygons
self.polygons_aug = None
self.line_strings_unaug = line_strings
self.line_strings_aug = None
self.data = data
def get_column_names(self):
"""Get the names of types of augmentables that contain data.
This method is intended for situations where one wants to know which
data is contained in the batch that has to be augmented, visualized
or something similar.
Added in 0.4.0.
Returns
-------
list of str
Names of types of augmentables. E.g. ``["images", "polygons"]``.
"""
return _get_column_names(self, "_unaug")
def to_normalized_batch(self):
"""Convert this unnormalized batch to an instance of Batch.
As this method is intended to be called before augmentation, it
assumes that none of the ``*_aug`` attributes is yet set.
It will produce an AssertionError otherwise.
The newly created Batch's ``*_unaug`` attributes will match the ones
in this batch, just in normalized form.
Returns
-------
imgaug.augmentables.batches.Batch
The batch, with ``*_unaug`` attributes being normalized.
"""
contains_no_augmented_data_yet = all([
attr is None
for attr_name, attr
in self.__dict__.items()
if attr_name.endswith("_aug")])
assert contains_no_augmented_data_yet, (
"Expected UnnormalizedBatch to not contain any augmented data "
"before normalization, but at least one '*_aug' attribute was "
"already set.")
images_unaug = nlib.normalize_images(self.images_unaug)
shapes = None
if images_unaug is not None:
shapes = [image.shape for image in images_unaug]
return Batch(
images=images_unaug,
heatmaps=nlib.normalize_heatmaps(
self.heatmaps_unaug, shapes),
segmentation_maps=nlib.normalize_segmentation_maps(
self.segmentation_maps_unaug, shapes),
keypoints=nlib.normalize_keypoints(
self.keypoints_unaug, shapes),
bounding_boxes=nlib.normalize_bounding_boxes(
self.bounding_boxes_unaug, shapes),
polygons=nlib.normalize_polygons(
self.polygons_unaug, shapes),
line_strings=nlib.normalize_line_strings(
self.line_strings_unaug, shapes),
data=self.data
)
def fill_from_augmented_normalized_batch_(self, batch_aug_norm):
"""
Fill this batch with (normalized) augmentation results in-place.
This method receives a (normalized) Batch instance, takes all
``*_aug`` attributes out if it and assigns them to this
batch *in unnormalized form*. Hence, the datatypes of all ``*_aug``
attributes will match the datatypes of the ``*_unaug`` attributes.
Added in 0.4.0.
Parameters
----------
batch_aug_norm: imgaug.augmentables.batches.Batch
Batch after normalization and augmentation.
Returns
-------
imgaug.augmentables.batches.UnnormalizedBatch
This instance itself.
All ``*_unaug`` attributes are unchanged.
All ``*_aug`` attributes are taken from `batch_normalized`,
converted to unnormalized form.
"""
self.images_aug = nlib.invert_normalize_images(
batch_aug_norm.images_aug, self.images_unaug)
self.heatmaps_aug = nlib.invert_normalize_heatmaps(
batch_aug_norm.heatmaps_aug, self.heatmaps_unaug)
self.segmentation_maps_aug = nlib.invert_normalize_segmentation_maps(
batch_aug_norm.segmentation_maps_aug, self.segmentation_maps_unaug)
self.keypoints_aug = nlib.invert_normalize_keypoints(
batch_aug_norm.keypoints_aug, self.keypoints_unaug)
self.bounding_boxes_aug = nlib.invert_normalize_bounding_boxes(
batch_aug_norm.bounding_boxes_aug, self.bounding_boxes_unaug)
self.polygons_aug = nlib.invert_normalize_polygons(
batch_aug_norm.polygons_aug, self.polygons_unaug)
self.line_strings_aug = nlib.invert_normalize_line_strings(
batch_aug_norm.line_strings_aug, self.line_strings_unaug)
return self
def fill_from_augmented_normalized_batch(self, batch_aug_norm):
"""
Fill this batch with (normalized) augmentation results.
This method receives a (normalized) Batch instance, takes all
``*_aug`` attributes out if it and assigns them to this
batch *in unnormalized form*. Hence, the datatypes of all ``*_aug``
attributes will match the datatypes of the ``*_unaug`` attributes.
Parameters
----------
batch_aug_norm: imgaug.augmentables.batches.Batch
Batch after normalization and augmentation.
Returns
-------
imgaug.augmentables.batches.UnnormalizedBatch
New UnnormalizedBatch instance. All ``*_unaug`` attributes are
taken from the old UnnormalizedBatch (without deepcopying them)
and all ``*_aug`` attributes are taken from `batch_normalized`,
converted to unnormalized form.
"""
# we take here the .data from the normalized batch instead of from
# self for the rare case where one has decided to somehow change it
# during augmentation
batch = UnnormalizedBatch(
images=self.images_unaug,
heatmaps=self.heatmaps_unaug,
segmentation_maps=self.segmentation_maps_unaug,
keypoints=self.keypoints_unaug,
bounding_boxes=self.bounding_boxes_unaug,
polygons=self.polygons_unaug,
line_strings=self.line_strings_unaug,
data=batch_aug_norm.data
)
batch.images_aug = nlib.invert_normalize_images(
batch_aug_norm.images_aug, self.images_unaug)
batch.heatmaps_aug = nlib.invert_normalize_heatmaps(
batch_aug_norm.heatmaps_aug, self.heatmaps_unaug)
batch.segmentation_maps_aug = nlib.invert_normalize_segmentation_maps(
batch_aug_norm.segmentation_maps_aug, self.segmentation_maps_unaug)
batch.keypoints_aug = nlib.invert_normalize_keypoints(
batch_aug_norm.keypoints_aug, self.keypoints_unaug)
batch.bounding_boxes_aug = nlib.invert_normalize_bounding_boxes(
batch_aug_norm.bounding_boxes_aug, self.bounding_boxes_unaug)
batch.polygons_aug = nlib.invert_normalize_polygons(
batch_aug_norm.polygons_aug, self.polygons_unaug)
batch.line_strings_aug = nlib.invert_normalize_line_strings(
batch_aug_norm.line_strings_aug, self.line_strings_unaug)
return batch
class Batch(object):
"""
Class encapsulating a batch before and after augmentation.
Parameters
----------
images : None or (N,H,W,C) ndarray or list of (H,W,C) ndarray
The images to augment.
heatmaps : None or list of imgaug.augmentables.heatmaps.HeatmapsOnImage
The heatmaps to augment.
segmentation_maps : None or list of imgaug.augmentables.segmaps.SegmentationMapsOnImage
The segmentation maps to augment.
keypoints : None or list of imgaug.augmentables.kps.KeypointOnImage
The keypoints to augment.
bounding_boxes : None or list of imgaug.augmentables.bbs.BoundingBoxesOnImage
The bounding boxes to augment.
polygons : None or list of imgaug.augmentables.polys.PolygonsOnImage
The polygons to augment.
line_strings : None or list of imgaug.augmentables.lines.LineStringsOnImage
The line strings to augment.
data
Additional data that is saved in the batch and may be read out
after augmentation. This could e.g. contain filepaths to each image
in `images`. As this object is usually used for background
augmentation with multiple processes, the augmented Batch objects might
not be returned in the original order, making this information useful.
"""
def __init__(self, images=None, heatmaps=None, segmentation_maps=None,
keypoints=None, bounding_boxes=None, polygons=None,
line_strings=None, data=None):
"""Construct a new :class:`Batch` instance."""
self.images_unaug = images
self.images_aug = None
self.heatmaps_unaug = heatmaps
self.heatmaps_aug = None
self.segmentation_maps_unaug = segmentation_maps
self.segmentation_maps_aug = None
self.keypoints_unaug = keypoints
self.keypoints_aug = None
self.bounding_boxes_unaug = bounding_boxes
self.bounding_boxes_aug = None
self.polygons_unaug = polygons
self.polygons_aug = None
self.line_strings_unaug = line_strings
self.line_strings_aug = None
self.data = data
@property
@ia.deprecated("Batch.images_unaug")
def images(self):
"""Get unaugmented images."""
return self.images_unaug
@property
@ia.deprecated("Batch.heatmaps_unaug")
def heatmaps(self):
"""Get unaugmented heatmaps."""
return self.heatmaps_unaug
@property
@ia.deprecated("Batch.segmentation_maps_unaug")
def segmentation_maps(self):
"""Get unaugmented segmentation maps."""
return self.segmentation_maps_unaug
@property
@ia.deprecated("Batch.keypoints_unaug")
def keypoints(self):
"""Get unaugmented keypoints."""
return self.keypoints_unaug
@property
@ia.deprecated("Batch.bounding_boxes_unaug")
def bounding_boxes(self):
"""Get unaugmented bounding boxes."""
return self.bounding_boxes_unaug
def get_column_names(self):
"""Get the names of types of augmentables that contain data.
This method is intended for situations where one wants to know which
data is contained in the batch that has to be augmented, visualized
or something similar.
Added in 0.4.0.
Returns
-------
list of str
Names of types of augmentables. E.g. ``["images", "polygons"]``.
"""
return _get_column_names(self, "_unaug")
def to_normalized_batch(self):
"""Return this batch.
This method does nothing and only exists to simplify interfaces
that accept both :class:`UnnormalizedBatch` and :class:`Batch`.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.batches.Batch
This batch (not copied).
"""
return self
def to_batch_in_augmentation(self):
"""Convert this batch to a :class:`_BatchInAugmentation` instance.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.batches._BatchInAugmentation
The converted batch.
"""
def _copy(var):
# TODO first check here if _aug is set and if it is then use that?
if var is not None:
return utils.copy_augmentables(var)
return var
return _BatchInAugmentation(
images=_copy(self.images_unaug),
heatmaps=_copy(self.heatmaps_unaug),
segmentation_maps=_copy(self.segmentation_maps_unaug),
keypoints=_copy(self.keypoints_unaug),
bounding_boxes=_copy(self.bounding_boxes_unaug),
polygons=_copy(self.polygons_unaug),
line_strings=_copy(self.line_strings_unaug)
)
def fill_from_batch_in_augmentation_(self, batch_in_augmentation):
"""Set the columns in this batch to the column values of another batch.
This method works in-place.
Added in 0.4.0.
Parameters
----------
batch_in_augmentation : _BatchInAugmentation
Batch of which to use the column values.
The values are *not* copied. Only their references are used.
Returns
-------
Batch
The updated batch. (Modified in-place.)
"""
self.images_aug = batch_in_augmentation.images
self.heatmaps_aug = batch_in_augmentation.heatmaps
self.segmentation_maps_aug = batch_in_augmentation.segmentation_maps
self.keypoints_aug = batch_in_augmentation.keypoints
self.bounding_boxes_aug = batch_in_augmentation.bounding_boxes
self.polygons_aug = batch_in_augmentation.polygons
self.line_strings_aug = batch_in_augmentation.line_strings
return self
def deepcopy(self,
images_unaug=DEFAULT,
images_aug=DEFAULT,
heatmaps_unaug=DEFAULT,
heatmaps_aug=DEFAULT,
segmentation_maps_unaug=DEFAULT,
segmentation_maps_aug=DEFAULT,
keypoints_unaug=DEFAULT,
keypoints_aug=DEFAULT,
bounding_boxes_unaug=DEFAULT,
bounding_boxes_aug=DEFAULT,
polygons_unaug=DEFAULT,
polygons_aug=DEFAULT,
line_strings_unaug=DEFAULT,
line_strings_aug=DEFAULT):
"""Copy this batch and all of its column values.
Parameters
----------
images_unaug : imgaug.augmentables.batches.DEFAULT or None or (N,H,W,C) ndarray or list of (H,W,C) ndarray
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
images_aug : imgaug.augmentables.batches.DEFAULT or None or (N,H,W,C) ndarray or list of (H,W,C) ndarray
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
heatmaps_unaug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.heatmaps.HeatmapsOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
heatmaps_aug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.heatmaps.HeatmapsOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
segmentation_maps_unaug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.segmaps.SegmentationMapsOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
segmentation_maps_aug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.segmaps.SegmentationMapsOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
keypoints_unaug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.kps.KeypointOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
keypoints_aug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.kps.KeypointOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
bounding_boxes_unaug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.bbs.BoundingBoxesOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
bounding_boxes_aug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.bbs.BoundingBoxesOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
polygons_unaug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.polys.PolygonsOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
polygons_aug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.polys.PolygonsOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
line_strings_unaug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.lines.LineStringsOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
line_strings_aug : imgaug.augmentables.batches.DEFAULT or None or list of imgaug.augmentables.lines.LineStringsOnImage
Copies the current attribute value without changes if set to
``imgaug.augmentables.batches.DEFAULT``.
Otherwise same as in :func:`Batch.__init__`.
Returns
-------
Batch
Deep copy of the batch, optionally with new attributes.
"""
def _copy_optional(self_attr, arg):
return utils.deepcopy_fast(arg if arg is not DEFAULT else self_attr)
batch = Batch(
images=_copy_optional(self.images_unaug, images_unaug),
heatmaps=_copy_optional(self.heatmaps_unaug, heatmaps_unaug),
segmentation_maps=_copy_optional(self.segmentation_maps_unaug,
segmentation_maps_unaug),
keypoints=_copy_optional(self.keypoints_unaug, keypoints_unaug),
bounding_boxes=_copy_optional(self.bounding_boxes_unaug,
bounding_boxes_unaug),
polygons=_copy_optional(self.polygons_unaug, polygons_unaug),
line_strings=_copy_optional(self.line_strings_unaug,
line_strings_unaug),
data=utils.deepcopy_fast(self.data)
)
batch.images_aug = _copy_optional(self.images_aug, images_aug)
batch.heatmaps_aug = _copy_optional(self.heatmaps_aug, heatmaps_aug)
batch.segmentation_maps_aug = _copy_optional(self.segmentation_maps_aug,
segmentation_maps_aug)
batch.keypoints_aug = _copy_optional(self.keypoints_aug, keypoints_aug)
batch.bounding_boxes_aug = _copy_optional(self.bounding_boxes_aug,
bounding_boxes_aug)
batch.polygons_aug = _copy_optional(self.polygons_aug, polygons_aug)
batch.line_strings_aug = _copy_optional(self.line_strings_aug,
line_strings_aug)
return batch
# Added in 0.4.0.
class _BatchInAugmentationPropagationContext(object):
def __init__(self, batch, augmenter, hooks, parents):
self.batch = batch
self.augmenter = augmenter
self.hooks = hooks
self.parents = parents
self.noned_info = None
def __enter__(self):
if self.hooks is not None:
self.noned_info = self.batch.apply_propagation_hooks_(
self.augmenter, self.hooks, self.parents)
return self.batch
def __exit__(self, exc_type, exc_val, exc_tb):
if self.noned_info is not None:
self.batch = \
self.batch.invert_apply_propagation_hooks_(self.noned_info)
class _BatchInAugmentation(object):
"""
Class encapsulating a batch during the augmentation process.
Data within the batch is already verified and normalized, similar to
:class:`Batch`. Data within the batch may be changed in-place. No initial
copy is needed.
Added in 0.4.0.
Parameters
----------
images : None or (N,H,W,C) ndarray or list of (H,W,C) ndarray
The images to augment.
heatmaps : None or list of imgaug.augmentables.heatmaps.HeatmapsOnImage
The heatmaps to augment.
segmentation_maps : None or list of imgaug.augmentables.segmaps.SegmentationMapsOnImage
The segmentation maps to augment.
keypoints : None or list of imgaug.augmentables.kps.KeypointOnImage
The keypoints to augment.
bounding_boxes : None or list of imgaug.augmentables.bbs.BoundingBoxesOnImage
The bounding boxes to augment.
polygons : None or list of imgaug.augmentables.polys.PolygonsOnImage
The polygons to augment.
line_strings : None or list of imgaug.augmentables.lines.LineStringsOnImage
The line strings to augment.
"""
# Added in 0.4.0.
def __init__(self, images=None, heatmaps=None, segmentation_maps=None,
keypoints=None, bounding_boxes=None, polygons=None,
line_strings=None, data=None):
"""Create a new :class:`_BatchInAugmentation` instance."""
self.images = images
self.heatmaps = heatmaps
self.segmentation_maps = segmentation_maps
self.keypoints = keypoints
self.bounding_boxes = bounding_boxes
self.polygons = polygons
self.line_strings = line_strings
self.data = data
@property
def empty(self):
"""Estimate whether this batch is empty, i.e. contains no data.
Added in 0.4.0.
Returns
-------
bool
``True`` if the batch contains no data to augment.
``False`` otherwise.
"""
return self.nb_rows == 0
@property
def nb_rows(self):
"""Get the number of rows (i.e. examples) in this batch.
Note that this method assumes that all columns have the same number
of rows.
Added in 0.4.0.
Returns
-------
int
Number of rows or ``0`` if there is no data in the batch.
"""
for augm_name in _AUGMENTABLE_NAMES:
value = getattr(self, augm_name)
if value is not None:
return len(value)
return 0
@property
def columns(self):
"""Get the columns of data to augment.
Each column represents one datatype and its corresponding data,
e.g. images or polygons.
Added in 0.4.0.
Returns
-------
list of _AugmentableColumn
The columns to augment within this batch.
"""
return _get_columns(self, "")
def get_column_names(self):
"""Get the names of types of augmentables that contain data.
This method is intended for situations where one wants to know which
data is contained in the batch that has to be augmented, visualized
or something similar.
Added in 0.4.0.
Returns
-------
list of str
Names of types of augmentables. E.g. ``["images", "polygons"]``.
"""
return _get_column_names(self, "")
def get_rowwise_shapes(self):
"""Get the shape of each row within this batch.
Each row denotes the data of different types (e.g. image array,
polygons) corresponding to a single example in the batch.
This method assumes that all ``.shape`` attributes contain the same
shape and that it is identical to the image's shape.
It also assumes that there are no columns containing only ``None`` s.
Added in 0.4.0.
Returns
-------
list of tuple of int
The shapes of each row.
"""
nb_rows = self.nb_rows
columns = self.columns
shapes = [None] * nb_rows
found = np.zeros((nb_rows,), dtype=bool)
for column in columns:
if column.name == "images" and ia.is_np_array(column.value):
shapes = [column.value.shape[1:]] * nb_rows
else:
for i, item in enumerate(column.value):
if item is not None:
shapes[i] = item.shape
found[i] = True
if np.all(found):
return shapes
return shapes
def subselect_rows_by_indices(self, indices):
"""Reduce this batch to a subset of rows based on their row indices.
Added in 0.4.0.
Parameters
----------
indices : iterable of int
Row indices to select.
Returns
-------
_BatchInAugmentation
Batch containing only a subselection of rows.
"""
kwargs = {"data": self.data}
for augm_name in _AUGMENTABLE_NAMES:
rows = getattr(self, augm_name)
if rows is not None:
if augm_name == "images" and ia.is_np_array(rows):
rows = rows[indices] # pylint: disable=unsubscriptable-object
else:
rows = [rows[index] for index in indices]
if len(rows) == 0:
rows = None
kwargs[augm_name] = rows
return _BatchInAugmentation(**kwargs)
def invert_subselect_rows_by_indices_(self, indices, batch_subselected):
"""Reverse the subselection of rows in-place.
This is the inverse of
:func:`_BatchInAugmentation.subselect_rows_by_indices`.
This method has to be executed on the batch *before* subselection.
Added in 0.4.0.
Parameters
----------
indices : iterable of int
Row indices that were selected. (This is the input to
batch_subselected : _BatchInAugmentation
The batch after
:func:`_BatchInAugmentation.subselect_rows_by_indices` was called.
Returns
-------
_BatchInAugmentation
The updated batch. (Modified in-place.)
Examples
--------
>>> import numpy as np
>>> from imgaug.augmentables.batches import _BatchInAugmentation
>>> images = np.zeros((2, 10, 20, 3), dtype=np.uint8)
>>> batch = _BatchInAugmentation(images=images)
>>> batch_sub = batch.subselect_rows_by_indices([0])
>>> batch_sub.images += 1
>>> batch = batch.invert_subselect_rows_by_indices_([0], batch_sub)
"""
for augm_name in _AUGMENTABLE_NAMES:
column = getattr(self, augm_name)
if column is not None:
column_sub = getattr(batch_subselected, augm_name)
if column_sub is None:
# list of indices was empty, resulting in the columns
# in the subselected batch being empty and replaced
# by Nones. We can just re-use the columns before
# subselection.
pass
elif augm_name == "images" and ia.is_np_array(column):
# An array does not have to stay an array after
# augmentation. The shapes and/or dtypes of rows may
# change, turning the array into a list.
if ia.is_np_array(column_sub):
shapes = {column.shape[1:], column_sub.shape[1:]}
dtypes = {column.dtype.name, column_sub.dtype.name}
else:
shapes = set(
[column.shape[1:]]
+ [image.shape for image in column_sub])
dtypes = set(
[column.dtype.name]
+ [image.dtype.name for image in column_sub])
if len(shapes) == 1 and len(dtypes) == 1:
column[indices] = column_sub # pylint: disable=unsupported-assignment-operation
else:
self.images = list(column)
for ith_index, index in enumerate(indices):
self.images[index] = column_sub[ith_index]
else:
for ith_index, index in enumerate(indices):
column[index] = column_sub[ith_index] # pylint: disable=unsupported-assignment-operation
return self
def propagation_hooks_ctx(self, augmenter, hooks, parents):
"""Start a context in which propagation hooks are applied.
Added in 0.4.0.
Parameters
----------
augmenter : imgaug.augmenters.meta.Augmenter
Augmenter to provide to the propagation hook function.
hooks : imgaug.imgaug.HooksImages or imgaug.imgaug.HooksKeypoints
The hooks that might contain a propagation hook function.
parents : list of imgaug.augmenters.meta.Augmenter
The list of parents to provide to the propagation hook function.
Returns
-------
_BatchInAugmentationPropagationContext
The progagation hook context.
"""
return _BatchInAugmentationPropagationContext(
self, augmenter=augmenter, hooks=hooks, parents=parents)
def apply_propagation_hooks_(self, augmenter, hooks, parents):
"""Set columns in this batch to ``None`` based on a propagation hook.
This method works in-place.
Added in 0.4.0.
Parameters
----------
augmenter : imgaug.augmenters.meta.Augmenter
Augmenter to provide to the propagation hook function.
hooks : imgaug.imgaug.HooksImages or imgaug.imgaug.HooksKeypoints
The hooks that might contain a propagation hook function.
parents : list of imgaug.augmenters.meta.Augmenter
The list of parents to provide to the propagation hook function.
Returns
-------
list of tuple of str
Information about which columns were set to ``None``.
Each tuple contains
``(column attribute name, column value before setting it to None)``.
This information is required when calling
:func:`_BatchInAugmentation.invert_apply_propagation_hooks_`.
"""
if hooks is None:
return None
noned_info = []
for column in self.columns:
is_prop = hooks.is_propagating(
column.value, augmenter=augmenter, parents=parents,
default=True)
if not is_prop:
setattr(self, column.attr_name, None)
noned_info.append((column.attr_name, column.value))
return noned_info
def invert_apply_propagation_hooks_(self, noned_info):
"""Set columns from ``None`` back to their original values.
This is the inverse of
:func:`_BatchInAugmentation.apply_propagation_hooks_`.
This method works in-place.
Added in 0.4.0.
Parameters
----------
noned_info : list of tuple of str
Information about which columns were set to ``None`` and their
original values. This is the output of
:func:`_BatchInAugmentation.apply_propagation_hooks_`.
Returns
-------
_BatchInAugmentation
The updated batch. (Modified in-place.)
"""
for attr_name, value in noned_info:
setattr(self, attr_name, value)
return self
def to_batch_in_augmentation(self):
"""Convert this batch to a :class:`_BatchInAugmentation` instance.
This method simply returns the batch itself. It exists for consistency
with the other batch classes.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.batches._BatchInAugmentation
The batch itself. (Not copied.)
"""
return self
def fill_from_batch_in_augmentation_(self, batch_in_augmentation):
"""Set the columns in this batch to the column values of another batch.
This method works in-place.
Added in 0.4.0.
Parameters
----------
batch_in_augmentation : _BatchInAugmentation
Batch of which to use the column values.
The values are *not* copied. Only their references are used.
Returns
-------
_BatchInAugmentation
The updated batch. (Modified in-place.)
"""
if batch_in_augmentation is self:
return self
self.images = batch_in_augmentation.images
self.heatmaps = batch_in_augmentation.heatmaps
self.segmentation_maps = batch_in_augmentation.segmentation_maps
self.keypoints = batch_in_augmentation.keypoints
self.bounding_boxes = batch_in_augmentation.bounding_boxes
self.polygons = batch_in_augmentation.polygons
self.line_strings = batch_in_augmentation.line_strings
return self
def to_batch(self, batch_before_aug):
"""Convert this batch into a :class:`Batch` instance.
Added in 0.4.0.
Parameters
----------
batch_before_aug : imgaug.augmentables.batches.Batch
The batch before augmentation. It is required to set the input
data of the :class:`Batch` instance, e.g. ``images_unaug``
or ``data``.
Returns
-------
imgaug.augmentables.batches.Batch
Batch, with original unaugmented inputs from `batch_before_aug`
and augmented outputs from this :class:`_BatchInAugmentation`
instance.
"""
batch = Batch(
images=batch_before_aug.images_unaug,
heatmaps=batch_before_aug.heatmaps_unaug,
segmentation_maps=batch_before_aug.segmentation_maps_unaug,
keypoints=batch_before_aug.keypoints_unaug,
bounding_boxes=batch_before_aug.bounding_boxes_unaug,
polygons=batch_before_aug.polygons_unaug,
line_strings=batch_before_aug.line_strings_unaug,
data=batch_before_aug.data
)
batch.images_aug = self.images
batch.heatmaps_aug = self.heatmaps
batch.segmentation_maps_aug = self.segmentation_maps
batch.keypoints_aug = self.keypoints
batch.bounding_boxes_aug = self.bounding_boxes
batch.polygons_aug = self.polygons
batch.line_strings_aug = self.line_strings
return batch
def deepcopy(self):
"""Copy this batch and all of its column values.
Added in 0.4.0.
Returns
-------
_BatchInAugmentation
Deep copy of this batch.
"""
batch = _BatchInAugmentation(data=utils.deepcopy_fast(self.data))
for augm_name in _AUGMENTABLE_NAMES:
value = getattr(self, augm_name)
if value is not None:
setattr(batch, augm_name, utils.copy_augmentables(value))
return batch
================================================
FILE: imgaug/augmentables/bbs.py
================================================
"""Classes representing bounding boxes."""
from __future__ import print_function, division, absolute_import
import copy
import numpy as np
import skimage.draw
import skimage.measure
from .. import imgaug as ia
from .base import IAugmentable
from .utils import (
normalize_imglike_shape,
project_coords,
_remove_out_of_image_fraction_,
_normalize_shift_args,
_handle_on_image_shape
)
# TODO functions: square(), to_aspect_ratio(), contains_point()
class BoundingBox(object):
"""Class representing bounding boxes.
Each bounding box is parameterized by its top left and bottom right
corners. Both are given as x and y-coordinates. The corners are intended
to lie inside the bounding box area. As a result, a bounding box that lies
completely inside the image but has maximum extensions would have
coordinates ``(0.0, 0.0)`` and ``(W - epsilon, H - epsilon)``. Note that
coordinates are saved internally as floats.
Parameters
----------
x1 : number
X-coordinate of the top left of the bounding box.
y1 : number
Y-coordinate of the top left of the bounding box.
x2 : number
X-coordinate of the bottom right of the bounding box.
y2 : number
Y-coordinate of the bottom right of the bounding box.
label : None or str, optional
Label of the bounding box, e.g. a string representing the class.
"""
def __init__(self, x1, y1, x2, y2, label=None):
"""Create a new BoundingBox instance."""
if x1 > x2:
x2, x1 = x1, x2
if y1 > y2:
y2, y1 = y1, y2
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.label = label
@property
def coords(self):
"""Get the top-left and bottom-right coordinates as one array.
Added in 0.4.0.
Returns
-------
ndarray
A ``(N, 2)`` numpy array with ``N=2`` containing the top-left
and bottom-right coordinates.
"""
arr = np.empty((2, 2), dtype=np.float32)
arr[0, :] = (self.x1, self.y1)
arr[1, :] = (self.x2, self.y2)
return arr
@property
def x1_int(self):
"""Get the x-coordinate of the top left corner as an integer.
Returns
-------
int
X-coordinate of the top left corner, rounded to the closest
integer.
"""
# use numpy's round to have consistent behaviour between python
# versions
return int(np.round(self.x1))
@property
def y1_int(self):
"""Get the y-coordinate of the top left corner as an integer.
Returns
-------
int
Y-coordinate of the top left corner, rounded to the closest
integer.
"""
# use numpy's round to have consistent behaviour between python
# versions
return int(np.round(self.y1))
@property
def x2_int(self):
"""Get the x-coordinate of the bottom left corner as an integer.
Returns
-------
int
X-coordinate of the bottom left corner, rounded to the closest
integer.
"""
# use numpy's round to have consistent behaviour between python
# versions
return int(np.round(self.x2))
@property
def y2_int(self):
"""Get the y-coordinate of the bottom left corner as an integer.
Returns
-------
int
Y-coordinate of the bottom left corner, rounded to the closest
integer.
"""
# use numpy's round to have consistent behaviour between python
# versions
return int(np.round(self.y2))
@property
def height(self):
"""Estimate the height of the bounding box.
Returns
-------
number
Height of the bounding box.
"""
return self.y2 - self.y1
@property
def width(self):
"""Estimate the width of the bounding box.
Returns
-------
number
Width of the bounding box.
"""
return self.x2 - self.x1
@property
def center_x(self):
"""Estimate the x-coordinate of the center point of the bounding box.
Returns
-------
number
X-coordinate of the center point of the bounding box.
"""
return self.x1 + self.width/2
@property
def center_y(self):
"""Estimate the y-coordinate of the center point of the bounding box.
Returns
-------
number
Y-coordinate of the center point of the bounding box.
"""
return self.y1 + self.height/2
@property
def area(self):
"""Estimate the area of the bounding box.
Returns
-------
number
Area of the bounding box, i.e. ``height * width``.
"""
return self.height * self.width
# TODO add test for tuple of number
def contains(self, other):
"""Estimate whether the bounding box contains a given point.
Parameters
----------
other : tuple of number or imgaug.augmentables.kps.Keypoint
Point to check for.
Returns
-------
bool
``True`` if the point is contained in the bounding box,
``False`` otherwise.
"""
if isinstance(other, tuple):
x, y = other
else:
x, y = other.x, other.y
return self.x1 <= x <= self.x2 and self.y1 <= y <= self.y2
def project_(self, from_shape, to_shape):
"""Project the bounding box onto a differently shaped image in-place.
E.g. if the bounding box is on its original image at
``x1=(10 of 100 pixels)`` and ``y1=(20 of 100 pixels)`` and is
projected onto a new image with size ``(width=200, height=200)``,
its new position will be ``(x1=20, y1=40)``.
(Analogous for ``x2``/``y2``.)
This is intended for cases where the original image is resized.
It cannot be used for more complex changes (e.g. padding, cropping).
Added in 0.4.0.
Parameters
----------
from_shape : tuple of int or ndarray
Shape of the original image. (Before resize.)
to_shape : tuple of int or ndarray
Shape of the new image. (After resize.)
Returns
-------
imgaug.augmentables.bbs.BoundingBox
``BoundingBox`` instance with new coordinates.
The object may have been modified in-place.
"""
(self.x1, self.y1), (self.x2, self.y2) = project_coords(
[(self.x1, self.y1), (self.x2, self.y2)],
from_shape,
to_shape)
return self
# TODO add tests for ndarray inputs
def project(self, from_shape, to_shape):
"""Project the bounding box onto a differently shaped image.
E.g. if the bounding box is on its original image at
``x1=(10 of 100 pixels)`` and ``y1=(20 of 100 pixels)`` and is
projected onto a new image with size ``(width=200, height=200)``,
its new position will be ``(x1=20, y1=40)``.
(Analogous for ``x2``/``y2``.)
This is intended for cases where the original image is resized.
It cannot be used for more complex changes (e.g. padding, cropping).
Parameters
----------
from_shape : tuple of int or ndarray
Shape of the original image. (Before resize.)
to_shape : tuple of int or ndarray
Shape of the new image. (After resize.)
Returns
-------
imgaug.augmentables.bbs.BoundingBox
``BoundingBox`` instance with new coordinates.
"""
return self.deepcopy().project_(from_shape, to_shape)
def extend_(self, all_sides=0, top=0, right=0, bottom=0, left=0):
"""Extend the size of the bounding box along its sides in-place.
Added in 0.4.0.
Parameters
----------
all_sides : number, optional
Value by which to extend the bounding box size along all
sides.
top : number, optional
Value by which to extend the bounding box size along its top
side.
right : number, optional
Value by which to extend the bounding box size along its right
side.
bottom : number, optional
Value by which to extend the bounding box size along its bottom
side.
left : number, optional
Value by which to extend the bounding box size along its left
side.
Returns
-------
imgaug.BoundingBox
Extended bounding box.
The object may have been modified in-place.
"""
self.x1 = self.x1 - all_sides - left
self.x2 = self.x2 + all_sides + right
self.y1 = self.y1 - all_sides - top
self.y2 = self.y2 + all_sides + bottom
return self
def extend(self, all_sides=0, top=0, right=0, bottom=0, left=0):
"""Extend the size of the bounding box along its sides.
Parameters
----------
all_sides : number, optional
Value by which to extend the bounding box size along all
sides.
top : number, optional
Value by which to extend the bounding box size along its top
side.
right : number, optional
Value by which to extend the bounding box size along its right
side.
bottom : number, optional
Value by which to extend the bounding box size along its bottom
side.
left : number, optional
Value by which to extend the bounding box size along its left
side.
Returns
-------
imgaug.BoundingBox
Extended bounding box.
"""
return self.deepcopy().extend_(all_sides, top, right, bottom, left)
def intersection(self, other, default=None):
"""Compute the intersection BB between this BB and another BB.
Note that in extreme cases, the intersection can be a single point.
In that case the intersection bounding box exists and it will be
returned, but it will have a height and width of zero.
Parameters
----------
other : imgaug.augmentables.bbs.BoundingBox
Other bounding box with which to generate the intersection.
default : any, optional
Default value to return if there is no intersection.
Returns
-------
imgaug.augmentables.bbs.BoundingBox or any
Intersection bounding box of the two bounding boxes if there is
an intersection.
If there is no intersection, the default value will be returned,
which can by anything.
"""
x1_i = max(self.x1, other.x1)
y1_i = max(self.y1, other.y1)
x2_i = min(self.x2, other.x2)
y2_i = min(self.y2, other.y2)
if x1_i > x2_i or y1_i > y2_i:
return default
return BoundingBox(x1=x1_i, y1=y1_i, x2=x2_i, y2=y2_i)
def union(self, other):
"""Compute the union BB between this BB and another BB.
This is equivalent to drawing a bounding box around all corner points
of both bounding boxes.
Parameters
----------
other : imgaug.augmentables.bbs.BoundingBox
Other bounding box with which to generate the union.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Union bounding box of the two bounding boxes.
"""
return BoundingBox(
x1=min(self.x1, other.x1),
y1=min(self.y1, other.y1),
x2=max(self.x2, other.x2),
y2=max(self.y2, other.y2),
)
def iou(self, other):
"""Compute the IoU between this bounding box and another one.
IoU is the intersection over union, defined as::
``area(intersection(A, B)) / area(union(A, B))``
``= area(intersection(A, B))
/ (area(A) + area(B) - area(intersection(A, B)))``
Parameters
----------
other : imgaug.augmentables.bbs.BoundingBox
Other bounding box with which to compare.
Returns
-------
float
IoU between the two bounding boxes.
"""
inters = self.intersection(other)
if inters is None:
return 0.0
area_union = self.area + other.area - inters.area
return inters.area / area_union if area_union > 0 else 0.0
def compute_out_of_image_area(self, image):
"""Compute the area of the BB that is outside of the image plane.
Added in 0.4.0.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
float
Total area of the bounding box that is outside of the image plane.
Can be ``0.0``.
"""
shape = normalize_imglike_shape(image)
height, width = shape[0:2]
bb_image = BoundingBox(x1=0, y1=0, x2=width, y2=height)
inter = self.intersection(bb_image, default=None)
area = self.area
return area if inter is None else area - inter.area
def compute_out_of_image_fraction(self, image):
"""Compute fraction of BB area outside of the image plane.
This estimates ``f = A_ooi / A``, where ``A_ooi`` is the area of the
bounding box that is outside of the image plane, while ``A`` is the
total area of the bounding box.
Added in 0.4.0.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
float
Fraction of the bounding box area that is outside of the image
plane. Returns ``0.0`` if the bounding box is fully inside of
the image plane. If the bounding box has an area of zero, the
result is ``1.0`` if its coordinates are outside of the image
plane, otherwise ``0.0``.
"""
area = self.area
if area == 0:
shape = normalize_imglike_shape(image)
height, width = shape[0:2]
y1_outside = self.y1 < 0 or self.y1 >= height
x1_outside = self.x1 < 0 or self.x1 >= width
is_outside = (y1_outside or x1_outside)
return 1.0 if is_outside else 0.0
return self.compute_out_of_image_area(image) / area
def is_fully_within_image(self, image):
"""Estimate whether the bounding box is fully inside the image area.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
bool
``True`` if the bounding box is fully inside the image area.
``False`` otherwise.
"""
shape = normalize_imglike_shape(image)
height, width = shape[0:2]
return (
self.x1 >= 0
and self.x2 < width
and self.y1 >= 0
and self.y2 < height)
def is_partly_within_image(self, image):
"""Estimate whether the BB is at least partially inside the image area.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
bool
``True`` if the bounding box is at least partially inside the
image area.
``False`` otherwise.
"""
shape = normalize_imglike_shape(image)
height, width = shape[0:2]
eps = np.finfo(np.float32).eps
img_bb = BoundingBox(x1=0, x2=width-eps, y1=0, y2=height-eps)
return self.intersection(img_bb) is not None
def is_out_of_image(self, image, fully=True, partly=False):
"""Estimate whether the BB is partially/fully outside of the image area.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape and
must contain at least two integers.
fully : bool, optional
Whether to return ``True`` if the bounding box is fully outside
of the image area.
partly : bool, optional
Whether to return ``True`` if the bounding box is at least
partially outside fo the image area.
Returns
-------
bool
``True`` if the bounding box is partially/fully outside of the
image area, depending on defined parameters.
``False`` otherwise.
"""
if self.is_fully_within_image(image):
return False
if self.is_partly_within_image(image):
return partly
return fully
@ia.deprecated(alt_func="BoundingBox.clip_out_of_image()",
comment="clip_out_of_image() has the exactly same "
"interface.")
def cut_out_of_image(self, *args, **kwargs):
"""Clip off all parts of the BB box that are outside of the image."""
return self.clip_out_of_image(*args, **kwargs)
def clip_out_of_image_(self, image):
"""Clip off parts of the BB box that are outside of the image in-place.
Added in 0.4.0.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use for the clipping of the bounding box.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape and
must contain at least two integers.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Bounding box, clipped to fall within the image dimensions.
The object may have been modified in-place.
"""
shape = normalize_imglike_shape(image)
height, width = shape[0:2]
assert height > 0, (
"Expected image with height>0, got shape %s." % (image.shape,))
assert width > 0, (
"Expected image with width>0, got shape %s." % (image.shape,))
eps = np.finfo(np.float32).eps
self.x1 = np.clip(self.x1, 0, width - eps)
self.x2 = np.clip(self.x2, 0, width - eps)
self.y1 = np.clip(self.y1, 0, height - eps)
self.y2 = np.clip(self.y2, 0, height - eps)
return self
def clip_out_of_image(self, image):
"""Clip off all parts of the BB box that are outside of the image.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use for the clipping of the bounding box.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape and
must contain at least two integers.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Bounding box, clipped to fall within the image dimensions.
"""
return self.deepcopy().clip_out_of_image_(image)
def shift_(self, x=0, y=0):
"""Move this bounding box along the x/y-axis in-place.
The origin ``(0, 0)`` is at the top left of the image.
Added in 0.4.0.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Shifted bounding box.
The object may have been modified in-place.
"""
self.x1 += x
self.x2 += x
self.y1 += y
self.y2 += y
return self
def shift(self, x=0, y=0, top=None, right=None, bottom=None, left=None):
"""Move this bounding box along the x/y-axis.
The origin ``(0, 0)`` is at the top left of the image.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
top : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
top (towards the bottom).
right : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
right (towards the left).
bottom : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
bottom (towards the top).
left : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
left (towards the right).
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Shifted bounding box.
"""
# pylint: disable=redefined-outer-name
x, y = _normalize_shift_args(
x, y, top=top, right=right, bottom=bottom, left=left)
return self.deepcopy().shift_(x, y)
def draw_label_on_image(self, image, color=(0, 255, 0),
color_text=None, color_bg=None, alpha=1.0, size=1,
size_text=20, height=30,
copy=True, raise_if_out_of_image=False):
"""Draw a box showing the BB's label.
The box is placed right above the BB's rectangle.
Added in 0.4.0.
Parameters
----------
image : (H,W,C) ndarray
The image onto which to draw the label.
Currently expected to be ``uint8``.
color : None or iterable of int, optional
The color to use, corresponding to the channel layout of the
image. Usually RGB. Text and background colors will be derived
from this.
color_text : None or iterable of int, optional
The text color to use.
If ``None``, derived from `color_bg`.
color_bg : None or iterable of int, optional
The background color of the label box.
If ``None``, derived from `color`.
alpha : float, optional
The transparency of the drawn bounding box, where ``1.0`` denotes
no transparency and ``0.0`` is invisible.
size : int, optional
The thickness of the bounding box in pixels. If the value is
larger than ``1``, then additional pixels will be added around
the bounding box (i.e. extension towards the outside).
size_text : int, optional
Font size to use.
height : int, optional
Height of the label box in pixels.
copy : bool, optional
Whether to copy the input image or change it in-place.
raise_if_out_of_image : bool, optional
Whether to raise an error if the bounding box is fully outside of
the image. If set to ``False``, no error will be raised and only
the parts inside the image will be drawn.
Returns
-------
(H,W,C) ndarray(uint8)
Image with bounding box drawn on it.
"""
# pylint: disable=redefined-outer-name
drawer = _LabelOnImageDrawer(
color=color,
color_text=color_text,
color_bg=color_bg,
size=size,
alpha=alpha,
raise_if_out_of_image=raise_if_out_of_image,
height=height,
size_text=size_text)
if copy:
return drawer.draw_on_image(image, self)
return drawer.draw_on_image_(image, self)
def draw_box_on_image(self, image, color=(0, 255, 0), alpha=1.0,
size=1, copy=True, raise_if_out_of_image=False,
thickness=None):
"""Draw the rectangle of the bounding box on an image.
This method does not draw the label.
Added in 0.4.0.
Parameters
----------
image : (H,W,C) ndarray
The image onto which to draw the bounding box rectangle.
Currently expected to be ``uint8``.
color : iterable of int, optional
The color to use, corresponding to the channel layout of the
image. Usually RGB.
alpha : float, optional
The transparency of the drawn bounding box, where ``1.0`` denotes
no transparency and ``0.0`` is invisible.
size : int, optional
The thickness of the bounding box in pixels. If the value is
larger than ``1``, then additional pixels will be added around
the bounding box (i.e. extension towards the outside).
copy : bool, optional
Whether to copy the input image or change it in-place.
raise_if_out_of_image : bool, optional
Whether to raise an error if the bounding box is fully outside of
the image. If set to ``False``, no error will be raised and only
the parts inside the image will be drawn.
thickness : None or int, optional
Deprecated.
Returns
-------
(H,W,C) ndarray(uint8)
Image with bounding box drawn on it.
"""
# pylint: disable=invalid-name, redefined-outer-name
if thickness is not None:
ia.warn_deprecated(
"Usage of argument 'thickness' in BoundingBox.draw_on_image() "
"is deprecated. The argument was renamed to 'size'.")
size = thickness
if raise_if_out_of_image and self.is_out_of_image(image):
raise Exception(
"Cannot draw bounding box x1=%.8f, y1=%.8f, x2=%.8f, y2=%.8f "
"on image with shape %s." % (
self.x1, self.y1, self.x2, self.y2, image.shape))
result = np.copy(image) if copy else image
if isinstance(color, (tuple, list)):
color = np.uint8(color)
for i in range(size):
y1, y2, x1, x2 = self.y1_int, self.y2_int, self.x1_int, self.x2_int
# When y values get into the range (H-0.5, H), the *_int functions
# round them to H. That is technically sensible, but in the case
# of drawing means that the border lies just barely outside of
# the image, making the border disappear, even though the BB is
# fully inside the image. Here we correct for that because of
# beauty reasons. Same is the case for x coordinates.
if self.is_fully_within_image(image):
y1 = np.clip(y1, 0, image.shape[0]-1)
y2 = np.clip(y2, 0, image.shape[0]-1)
x1 = np.clip(x1, 0, image.shape[1]-1)
x2 = np.clip(x2, 0, image.shape[1]-1)
y = [y1-i, y1-i, y2+i, y2+i]
x = [x1-i, x2+i, x2+i, x1-i]
rr, cc = skimage.draw.polygon_perimeter(y, x, shape=result.shape)
if alpha >= 0.99:
result[rr, cc, :] = color
else:
if ia.is_float_array(result):
# TODO use blend_alpha here
result[rr, cc, :] = (
(1 - alpha) * result[rr, cc, :]
+ alpha * color)
result = np.clip(result, 0, 255)
else:
input_dtype = result.dtype
result = result.astype(np.float32)
result[rr, cc, :] = (
(1 - alpha) * result[rr, cc, :]
+ alpha * color)
result = np.clip(result, 0, 255).astype(input_dtype)
return result
# TODO add explicit test for zero-sized BBs (worked when tested by hand)
def draw_on_image(self, image, color=(0, 255, 0), alpha=1.0, size=1,
copy=True, raise_if_out_of_image=False, thickness=None):
"""Draw the bounding box on an image.
This will automatically also draw the label, unless it is ``None``.
To only draw the box rectangle use
:func:`~imgaug.augmentables.bbs.BoundingBox.draw_box_on_image`.
To draw the label even if it is ``None`` or to configure e.g. its
color, use
:func:`~imgaug.augmentables.bbs.BoundingBox.draw_label_on_image`.
Parameters
----------
image : (H,W,C) ndarray
The image onto which to draw the bounding box.
Currently expected to be ``uint8``.
color : iterable of int, optional
The color to use, corresponding to the channel layout of the
image. Usually RGB.
alpha : float, optional
The transparency of the drawn bounding box, where ``1.0`` denotes
no transparency and ``0.0`` is invisible.
size : int, optional
The thickness of the bounding box in pixels. If the value is
larger than ``1``, then additional pixels will be added around
the bounding box (i.e. extension towards the outside).
copy : bool, optional
Whether to copy the input image or change it in-place.
raise_if_out_of_image : bool, optional
Whether to raise an error if the bounding box is fully outside of
the image. If set to ``False``, no error will be raised and only
the parts inside the image will be drawn.
thickness : None or int, optional
Deprecated.
Returns
-------
(H,W,C) ndarray(uint8)
Image with bounding box drawn on it.
"""
# pylint: disable=redefined-outer-name
image_drawn = self.draw_box_on_image(
image, color=color, alpha=alpha, size=size,
copy=copy, raise_if_out_of_image=raise_if_out_of_image,
thickness=thickness
)
if self.label is not None:
image_drawn = self.draw_label_on_image(
image_drawn, color=color, alpha=alpha,
size=size if thickness is None else thickness,
copy=False, raise_if_out_of_image=raise_if_out_of_image
)
return image_drawn
# TODO add tests for pad and pad_max
def extract_from_image(self, image, pad=True, pad_max=None,
prevent_zero_size=True):
"""Extract the image pixels within the bounding box.
This function will zero-pad the image if the bounding box is
partially/fully outside of the image.
Parameters
----------
image : (H,W) ndarray or (H,W,C) ndarray
The image from which to extract the pixels within the bounding box.
pad : bool, optional
Whether to zero-pad the image if the object is partially/fully
outside of it.
pad_max : None or int, optional
The maximum number of pixels that may be zero-paded on any side,
i.e. if this has value ``N`` the total maximum of added pixels
is ``4*N``.
This option exists to prevent extremely large images as a result of
single points being moved very far away during augmentation.
prevent_zero_size : bool, optional
Whether to prevent the height or width of the extracted image from
becoming zero.
If this is set to ``True`` and the height or width of the bounding
box is below ``1``, the height/width will be increased to ``1``.
This can be useful to prevent problems, e.g. with image saving or
plotting.
If it is set to ``False``, images will be returned as ``(H', W')``
or ``(H', W', 3)`` with ``H`` or ``W`` potentially being 0.
Returns
-------
(H',W') ndarray or (H',W',C) ndarray
Pixels within the bounding box. Zero-padded if the bounding box
is partially/fully outside of the image.
If `prevent_zero_size` is activated, it is guarantueed that
``H'>0`` and ``W'>0``, otherwise only ``H'>=0`` and ``W'>=0``.
"""
# pylint: disable=no-else-return, too-many-statements
height, width = image.shape[0], image.shape[1]
x1, x2, y1, y2 = self.x1_int, self.x2_int, self.y1_int, self.y2_int
# When y values get into the range (H-0.5, H), the *_int functions
# round them to H. That is technically sensible, but in the case of
# extraction leads to a black border, which is both ugly and
# unexpected after calling cut_out_of_image(). Here we correct for
# that because of beauty reasons. Same is the case for x coordinates.
fully_within = self.is_fully_within_image(image)
if fully_within:
y1, y2 = np.clip([y1, y2], 0, height-1)
x1, x2 = np.clip([x1, x2], 0, width-1)
# TODO add test
if prevent_zero_size:
if abs(x2 - x1) < 1:
x2 = x1 + 1
if abs(y2 - y1) < 1:
y2 = y1 + 1
if pad:
# if the bb is outside of the image area, the following pads the
# image first with black pixels until the bb is inside the image
# and only then extracts the image area
# TODO probably more efficient to initialize an array of zeros
# and copy only the portions of the bb into that array that
# are natively inside the image area
from ..augmenters import size as iasize
pad_top = 0
pad_right = 0
pad_bottom = 0
pad_left = 0
if x1 < 0:
pad_left = abs(x1)
x2 = x2 + pad_left
width = width + pad_left
x1 = 0
if y1 < 0:
pad_top = abs(y1)
y2 = y2 + pad_top
height = height + pad_top
y1 = 0
if x2 >= width:
pad_right = x2 - width
if y2 >= height:
pad_bottom = y2 - height
paddings = [pad_top, pad_right, pad_bottom, pad_left]
any_padded = any([val > 0 for val in paddings])
if any_padded:
if pad_max is None:
pad_max = max(paddings)
image = iasize.pad(
image,
top=min(pad_top, pad_max),
right=min(pad_right, pad_max),
bottom=min(pad_bottom, pad_max),
left=min(pad_left, pad_max)
)
return image[y1:y2, x1:x2]
else:
within_image = (
(0, 0, 0, 0)
<= (x1, y1, x2, y2)
< (width, height, width, height)
)
out_height, out_width = (y2 - y1), (x2 - x1)
nonzero_height = (out_height > 0)
nonzero_width = (out_width > 0)
if within_image and nonzero_height and nonzero_width:
return image[y1:y2, x1:x2]
if prevent_zero_size:
out_height = 1
out_width = 1
else:
out_height = 0
out_width = 0
if image.ndim == 2:
return np.zeros((out_height, out_width), dtype=image.dtype)
return np.zeros((out_height, out_width, image.shape[-1]),
dtype=image.dtype)
# TODO also add to_heatmap
# TODO add this to BoundingBoxesOnImage
# TODO add label to keypoints?
def to_keypoints(self):
"""Convert the BB's corners to keypoints (clockwise, from top left).
Returns
-------
list of imgaug.augmentables.kps.Keypoint
Corners of the bounding box as keypoints.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.kps import Keypoint
return [
Keypoint(x=self.x1, y=self.y1),
Keypoint(x=self.x2, y=self.y1),
Keypoint(x=self.x2, y=self.y2),
Keypoint(x=self.x1, y=self.y2)
]
def to_polygon(self):
"""Convert this bounding box to a polygon covering the same area.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.polys.Polygon
The bounding box converted to a polygon.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.polys import Polygon
return Polygon([
(self.x1, self.y1),
(self.x2, self.y1),
(self.x2, self.y2),
(self.x1, self.y2)
], label=self.label)
# TODO also introduce similar area_almost_equals()
def coords_almost_equals(self, other, max_distance=1e-4):
"""Estimate if this and another BB have almost identical coordinates.
Added in 0.4.0.
Parameters
----------
other : imgaug.augmentables.bbs.BoundingBox or iterable
The other bounding box with which to compare this one.
If this is an ``iterable``, it is assumed to represent the top-left
and bottom-right coordinates of that bounding box, given as e.g.
an ``(2,2)`` ndarray or an ``(4,)`` ndarray or as a similar list.
max_distance : number, optional
The maximum euclidean distance between a corner on one bounding
box and the closest corner on the other bounding box. If the
distance is exceeded for any such pair, the two BBs are not
viewed as equal.
Returns
-------
bool
Whether the two bounding boxes have almost identical corner
coordinates.
"""
if isinstance(other, BoundingBox):
coords_b = other.coords.flat
elif ia.is_np_array(other):
# we use flat here in case other is (N,2) instead of (4,)
coords_b = other.flat
elif ia.is_iterable(other):
coords_b = list(ia.flatten(other))
else:
raise ValueError(
"Expected 'other' to be an iterable containing two "
"(x,y)-coordinate pairs or a BoundingBox. "
"Got type %s." % (type(other),))
coords_a = self.coords
return np.allclose(coords_a.flat, coords_b, atol=max_distance, rtol=0)
def almost_equals(self, other, max_distance=1e-4):
"""Compare this and another BB's label and coordinates.
This is the same as
:func:`~imgaug.augmentables.bbs.BoundingBox.coords_almost_equals` but
additionally compares the labels.
Added in 0.4.0.
Parameters
----------
other : imgaug.augmentables.bbs.BoundingBox or iterable
The other object to compare against. Expected to be a
``BoundingBox``.
max_distance : number, optional
See
:func:`~imgaug.augmentables.bbs.BoundingBox.coords_almost_equals`.
Returns
-------
bool
``True`` if the coordinates are almost equal and additionally
the labels are equal. Otherwise ``False``.
"""
if self.label != other.label:
return False
return self.coords_almost_equals(other, max_distance=max_distance)
@classmethod
def from_point_soup(cls, xy):
"""Convert a ``(2P,) or (P,2) ndarray`` to a BB instance.
This is the inverse of
:func:`~imgaug.BoundingBoxesOnImage.to_xyxy_array`.
Added in 0.4.0.
Parameters
----------
xy : (2P,) ndarray or (P, 2) array or iterable of number or iterable of iterable of number
Array containing ``P`` points in xy-form denoting a soup of
points around which to place a bounding box.
The array should usually be of dtype ``float32``.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Bounding box around the points.
"""
# pylint: disable=unsubscriptable-object
xy = np.array(xy, dtype=np.float32)
assert len(xy) > 0, (
"Expected to get at least one point to place a bounding box "
"around, got shape %s." % (xy.shape,))
assert xy.ndim == 1 or (xy.ndim == 2 and xy.shape[-1] == 2), (
"Expected input array of shape (P,) or (P, 2), "
"got shape %s." % (xy.shape,))
if xy.ndim == 1:
xy = xy.reshape((-1, 2))
x1, y1 = np.min(xy, axis=0)
x2, y2 = np.max(xy, axis=0)
return cls(x1=x1, y1=y1, x2=x2, y2=y2)
def copy(self, x1=None, y1=None, x2=None, y2=None, label=None):
"""Create a shallow copy of this BoundingBox instance.
Parameters
----------
x1 : None or number
If not ``None``, then the ``x1`` coordinate of the copied object
will be set to this value.
y1 : None or number
If not ``None``, then the ``y1`` coordinate of the copied object
will be set to this value.
x2 : None or number
If not ``None``, then the ``x2`` coordinate of the copied object
will be set to this value.
y2 : None or number
If not ``None``, then the ``y2`` coordinate of the copied object
will be set to this value.
label : None or string
If not ``None``, then the ``label`` of the copied object
will be set to this value.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Shallow copy.
"""
return BoundingBox(
x1=self.x1 if x1 is None else x1,
x2=self.x2 if x2 is None else x2,
y1=self.y1 if y1 is None else y1,
y2=self.y2 if y2 is None else y2,
label=copy.deepcopy(self.label) if label is None else label
)
def deepcopy(self, x1=None, y1=None, x2=None, y2=None, label=None):
"""
Create a deep copy of the BoundingBox object.
Parameters
----------
x1 : None or number
If not ``None``, then the ``x1`` coordinate of the copied object
will be set to this value.
y1 : None or number
If not ``None``, then the ``y1`` coordinate of the copied object
will be set to this value.
x2 : None or number
If not ``None``, then the ``x2`` coordinate of the copied object
will be set to this value.
y2 : None or number
If not ``None``, then the ``y2`` coordinate of the copied object
will be set to this value.
label : None or string
If not ``None``, then the ``label`` of the copied object
will be set to this value.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Deep copy.
"""
# TODO write specific copy routine with deepcopy for label and remove
# the deepcopy from copy()
return self.copy(x1=x1, y1=y1, x2=x2, y2=y2, label=label)
def __getitem__(self, indices):
"""Get the coordinate(s) with given indices.
Added in 0.4.0.
Returns
-------
ndarray
xy-coordinate(s) as ``ndarray``.
"""
return self.coords[indices]
def __iter__(self):
"""Iterate over the coordinates of this instance.
Added in 0.4.0.
Yields
------
ndarray
An ``(2,)`` ``ndarray`` denoting an xy-coordinate pair.
"""
return iter(self.coords)
def __repr__(self):
return self.__str__()
def __str__(self):
return "BoundingBox(x1=%.4f, y1=%.4f, x2=%.4f, y2=%.4f, label=%s)" % (
self.x1, self.y1, self.x2, self.y2, self.label)
class BoundingBoxesOnImage(IAugmentable):
"""Container for the list of all bounding boxes on a single image.
Parameters
----------
bounding_boxes : list of imgaug.augmentables.bbs.BoundingBox
List of bounding boxes on the image.
shape : tuple of int
The shape of the image on which the objects are placed, i.e. the
result of ``image.shape``.
Should include the number of channels, not only height and width.
Examples
--------
>>> import numpy as np
>>> from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
>>>
>>> image = np.zeros((100, 100))
>>> bbs = [
>>> BoundingBox(x1=10, y1=20, x2=20, y2=30),
>>> BoundingBox(x1=25, y1=50, x2=30, y2=70)
>>> ]
>>> bbs_oi = BoundingBoxesOnImage(bbs, shape=image.shape)
"""
def __init__(self, bounding_boxes, shape):
self.bounding_boxes = bounding_boxes
self.shape = _handle_on_image_shape(shape, self)
@property
def items(self):
"""Get the bounding boxes in this container.
Added in 0.4.0.
Returns
-------
list of BoundingBox
Bounding boxes within this container.
"""
return self.bounding_boxes
@items.setter
def items(self, value):
"""Set the bounding boxes in this container.
Added in 0.4.0.
Parameters
----------
value : list of BoundingBox
Bounding boxes within this container.
"""
self.bounding_boxes = value
# TODO remove this? here it is image height, but in BoundingBox it is
# bounding box height
@property
def height(self):
"""Get the height of the image on which the bounding boxes fall.
Returns
-------
int
Image height.
"""
return self.shape[0]
# TODO remove this? here it is image width, but in BoundingBox it is
# bounding box width
@property
def width(self):
"""Get the width of the image on which the bounding boxes fall.
Returns
-------
int
Image width.
"""
return self.shape[1]
@property
def empty(self):
"""Determine whether this instance contains zero bounding boxes.
Returns
-------
bool
True if this object contains zero bounding boxes.
"""
return len(self.bounding_boxes) == 0
def on_(self, image):
"""Project BBs from one image (shape) to a another one in-place.
Added in 0.4.0.
Parameters
----------
image : ndarray or tuple of int
New image onto which the bounding boxes are to be projected.
May also simply be that new image's shape tuple.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Object containing the same bounding boxes after projection to
the new image shape.
The object and its items may have been modified in-place.
"""
# pylint: disable=invalid-name
on_shape = normalize_imglike_shape(image)
if on_shape[0:2] == self.shape[0:2]:
self.shape = on_shape # channels may differ
return self
for i, item in enumerate(self.items):
self.bounding_boxes[i] = item.project_(self.shape, on_shape)
self.shape = on_shape
return self
def on(self, image):
"""Project bounding boxes from one image (shape) to a another one.
Parameters
----------
image : ndarray or tuple of int
New image onto which the bounding boxes are to be projected.
May also simply be that new image's shape tuple.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Object containing the same bounding boxes after projection to
the new image shape.
"""
# pylint: disable=invalid-name
return self.deepcopy().on_(image)
@classmethod
def from_xyxy_array(cls, xyxy, shape):
"""Convert an ``(N, 4) or (N, 2, 2) ndarray`` to a BBsOI instance.
This is the inverse of
:func:`~imgaug.BoundingBoxesOnImage.to_xyxy_array`.
Parameters
----------
xyxy : (N, 4) ndarray or (N, 2, 2) array
Array containing the corner coordinates of ``N`` bounding boxes.
Each bounding box is represented by its top-left and bottom-right
coordinates.
The array should usually be of dtype ``float32``.
shape : tuple of int
Shape of the image on which the bounding boxes are placed.
Should usually be ``(H, W, C)`` or ``(H, W)``.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Object containing a list of :class:`BoundingBox` instances
derived from the provided corner coordinates.
"""
# pylint: disable=unsubscriptable-object
xyxy = np.array(xyxy, dtype=np.float32)
# note that np.array([]) is (0,), not (0, 2)
if xyxy.shape[0] == 0:
return BoundingBoxesOnImage([], shape)
assert (
(xyxy.ndim == 2 and xyxy.shape[-1] == 4)
or (xyxy.ndim == 3 and xyxy.shape[1:3] == (2, 2))), (
"Expected input array of shape (N, 4) or (N, 2, 2), "
"got shape %s." % (xyxy.shape,))
xyxy = xyxy.reshape((-1, 2, 2))
boxes = [BoundingBox.from_point_soup(row) for row in xyxy]
return cls(boxes, shape)
@classmethod
def from_point_soups(cls, xy, shape):
"""Convert an ``(N, 2P) or (N, P, 2) ndarray`` to a BBsOI instance.
Added in 0.4.0.
Parameters
----------
xy : (N, 2P) ndarray or (N, P, 2) array or iterable of iterable of number or iterable of iterable of iterable of number
Array containing the corner coordinates of ``N`` bounding boxes.
Each bounding box is represented by a soup of ``P`` points.
If ``(N, P)`` then the second axis is expected to be in
xy-form (e.g. ``x1``, ``y1``, ``x2``, ``y2``, ...).
The final bounding box coordinates will be derived using ``min``
and ``max`` operations on the xy-values.
The array should usually be of dtype ``float32``.
shape : tuple of int
Shape of the image on which the bounding boxes are placed.
Should usually be ``(H, W, C)`` or ``(H, W)``.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Object containing a list of :class:`BoundingBox` instances
derived from the provided point soups.
"""
xy = np.array(xy, dtype=np.float32)
# from_xy_array() already checks the ndim/shape, so we don't have to
# do it here
boxes = [BoundingBox.from_point_soup(row) for row in xy]
return cls(boxes, shape)
def to_xyxy_array(self, dtype=np.float32):
"""Convert the ``BoundingBoxesOnImage`` object to an ``(N,4) ndarray``.
This is the inverse of
:func:`~imgaug.BoundingBoxesOnImage.from_xyxy_array`.
Parameters
----------
dtype : numpy.dtype, optional
Desired output datatype of the ndarray.
Returns
-------
ndarray
``(N,4) ndarray``, where ``N`` denotes the number of bounding
boxes and ``4`` denotes the top-left and bottom-right bounding
box corner coordinates in form ``(x1, y1, x2, y2)``.
"""
xyxy_array = np.zeros((len(self.bounding_boxes), 4), dtype=np.float32)
for i, box in enumerate(self.bounding_boxes):
xyxy_array[i] = [box.x1, box.y1, box.x2, box.y2]
return xyxy_array.astype(dtype)
def to_xy_array(self):
"""Convert the ``BoundingBoxesOnImage`` object to an ``(N,2) ndarray``.
Added in 0.4.0.
Returns
-------
ndarray
``(2*B,2) ndarray`` of xy-coordinates, where ``B`` denotes the
number of bounding boxes.
"""
return self.to_xyxy_array().reshape((-1, 2))
def fill_from_xyxy_array_(self, xyxy):
"""Modify the BB coordinates of this instance in-place.
.. note::
This currently expects exactly one entry in `xyxy` per bounding
in this instance. (I.e. two corner coordinates per instance.)
Otherwise, an ``AssertionError`` will be raised.
.. note::
This method will automatically flip x-coordinates if ``x1>x2``
for a bounding box. (Analogous for y-coordinates.)
Added in 0.4.0.
Parameters
----------
xyxy : (N, 4) ndarray or iterable of iterable of number
Coordinates of ``N`` bounding boxes on an image, given as
a ``(N,4)`` array of two corner xy-coordinates per bounding box.
``N`` must match the number of bounding boxes in this instance.
Returns
-------
BoundingBoxesOnImage
This instance itself, with updated bounding box coordinates.
Note that the instance was modified in-place.
"""
xyxy = np.array(xyxy, dtype=np.float32)
# note that np.array([]) is (0,), not (0, 4)
assert xyxy.shape[0] == 0 or (xyxy.ndim == 2 and xyxy.shape[-1] == 4), ( # pylint: disable=unsubscriptable-object
"Expected input array to have shape (N,4), "
"got shape %s." % (xyxy.shape,))
assert len(xyxy) == len(self.bounding_boxes), (
"Expected to receive an array with as many rows there are "
"bounding boxes in this instance. Got %d rows, expected %d." % (
len(xyxy), len(self.bounding_boxes)))
for bb, (x1, y1, x2, y2) in zip(self.bounding_boxes, xyxy):
bb.x1 = min([x1, x2])
bb.y1 = min([y1, y2])
bb.x2 = max([x1, x2])
bb.y2 = max([y1, y2])
return self
def fill_from_xy_array_(self, xy):
"""Modify the BB coordinates of this instance in-place.
See
:func:`~imgaug.augmentables.bbs.BoundingBoxesOnImage.fill_from_xyxy_array_`.
Added in 0.4.0.
Parameters
----------
xy : (2*B, 2) ndarray or iterable of iterable of number
Coordinates of ``B`` bounding boxes on an image, given as
a ``(2*B,2)`` array of two corner xy-coordinates per bounding box.
``B`` must match the number of bounding boxes in this instance.
Returns
-------
BoundingBoxesOnImage
This instance itself, with updated bounding box coordinates.
Note that the instance was modified in-place.
"""
xy = np.array(xy, dtype=np.float32)
return self.fill_from_xyxy_array_(xy.reshape((-1, 4)))
def draw_on_image(self, image, color=(0, 255, 0), alpha=1.0, size=1,
copy=True, raise_if_out_of_image=False, thickness=None):
"""Draw all bounding boxes onto a given image.
Parameters
----------
image : (H,W,3) ndarray
The image onto which to draw the bounding boxes.
This image should usually have the same shape as set in
``BoundingBoxesOnImage.shape``.
color : int or list of int or tuple of int or (3,) ndarray, optional
The RGB color of all bounding boxes.
If a single ``int`` ``C``, then that is equivalent to ``(C,C,C)``.
alpha : float, optional
Alpha/transparency of the bounding box.
size : int, optional
Thickness in pixels.
copy : bool, optional
Whether to copy the image before drawing the bounding boxes.
raise_if_out_of_image : bool, optional
Whether to raise an exception if any bounding box is outside of the
image.
thickness : None or int, optional
Deprecated.
Returns
-------
(H,W,3) ndarray
Image with drawn bounding boxes.
"""
# pylint: disable=redefined-outer-name
image = np.copy(image) if copy else image
for bb in self.bounding_boxes:
image = bb.draw_on_image(
image,
color=color,
alpha=alpha,
size=size,
copy=False,
raise_if_out_of_image=raise_if_out_of_image,
thickness=thickness
)
return image
def remove_out_of_image_(self, fully=True, partly=False):
"""Remove in-place all BBs that are fully/partially outside of the image.
Added in 0.4.0.
Parameters
----------
fully : bool, optional
Whether to remove bounding boxes that are fully outside of the
image.
partly : bool, optional
Whether to remove bounding boxes that are partially outside of
the image.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Reduced set of bounding boxes, with those that were
fully/partially outside of the image being removed.
The object and its items may have been modified in-place.
"""
self.bounding_boxes = [
bb
for bb
in self.bounding_boxes
if not bb.is_out_of_image(self.shape, fully=fully, partly=partly)]
return self
def remove_out_of_image(self, fully=True, partly=False):
"""Remove all BBs that are fully/partially outside of the image.
Parameters
----------
fully : bool, optional
Whether to remove bounding boxes that are fully outside of the
image.
partly : bool, optional
Whether to remove bounding boxes that are partially outside of
the image.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Reduced set of bounding boxes, with those that were
fully/partially outside of the image being removed.
"""
return self.copy().remove_out_of_image_(fully=fully, partly=partly)
def remove_out_of_image_fraction_(self, fraction):
"""Remove in-place all BBs with an OOI fraction of at least `fraction`.
'OOI' is the abbreviation for 'out of image'.
Added in 0.4.0.
Parameters
----------
fraction : number
Minimum out of image fraction that a bounding box has to have in
order to be removed. A fraction of ``1.0`` removes only bounding
boxes that are ``100%`` outside of the image. A fraction of ``0.0``
removes all bounding boxes.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Reduced set of bounding boxes, with those that had an out of image
fraction greater or equal the given one removed.
The object and its items may have been modified in-place.
"""
return _remove_out_of_image_fraction_(self, fraction)
def remove_out_of_image_fraction(self, fraction):
"""Remove all BBs with an out of image fraction of at least `fraction`.
Added in 0.4.0.
Parameters
----------
fraction : number
Minimum out of image fraction that a bounding box has to have in
order to be removed. A fraction of ``1.0`` removes only bounding
boxes that are ``100%`` outside of the image. A fraction of ``0.0``
removes all bounding boxes.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Reduced set of bounding boxes, with those that had an out of image
fraction greater or equal the given one removed.
"""
return self.copy().remove_out_of_image_fraction_(fraction)
@ia.deprecated(alt_func="BoundingBoxesOnImage.clip_out_of_image()",
comment="clip_out_of_image() has the exactly same "
"interface.")
def cut_out_of_image(self):
"""Clip off all parts from all BBs that are outside of the image."""
return self.clip_out_of_image()
def clip_out_of_image_(self):
"""
Clip off in-place all parts from all BBs that are outside of the image.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Bounding boxes, clipped to fall within the image dimensions.
The object and its items may have been modified in-place.
"""
# remove bbs that are not at least partially inside the image plane
self.bounding_boxes = [bb for bb in self.bounding_boxes
if bb.is_partly_within_image(self.shape)]
for i, bb in enumerate(self.bounding_boxes):
self.bounding_boxes[i] = bb.clip_out_of_image(self.shape)
return self
def clip_out_of_image(self):
"""Clip off all parts from all BBs that are outside of the image.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Bounding boxes, clipped to fall within the image dimensions.
"""
return self.deepcopy().clip_out_of_image_()
def shift_(self, x=0, y=0):
"""Move all BBs along the x/y-axis in-place.
The origin ``(0, 0)`` is at the top left of the image.
Added in 0.4.0.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Shifted bounding boxes.
The object and its items may have been modified in-place.
"""
for i, bb in enumerate(self.bounding_boxes):
self.bounding_boxes[i] = bb.shift_(x=x, y=y)
return self
def shift(self, x=0, y=0, top=None, right=None, bottom=None, left=None):
"""Move all BBs along the x/y-axis.
The origin ``(0, 0)`` is at the top left of the image.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
top : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
top (towards the bottom).
right : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
right (towads the left).
bottom : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
bottom (towards the top).
left : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
left (towards the right).
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Shifted bounding boxes.
"""
x, y = _normalize_shift_args(
x, y, top=top, right=right, bottom=bottom, left=left)
return self.deepcopy().shift_(x=x, y=y)
def to_keypoints_on_image(self):
"""Convert the bounding boxes to one ``KeypointsOnImage`` instance.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
A keypoints instance containing ``N*4`` coordinates for ``N``
bounding boxes. Order matches the order in ``bounding_boxes``.
"""
from .kps import KeypointsOnImage
# This currently uses 4 points instead of 2 points as the method
# is primarily used during augmentation and 4 points are overall
# the better choice there.
arr = np.zeros((len(self.bounding_boxes), 2*4), dtype=np.float32)
for i, box in enumerate(self.bounding_boxes):
arr[i] = [
box.x1, box.y1,
box.x2, box.y1,
box.x2, box.y2,
box.x1, box.y2
]
return KeypointsOnImage.from_xy_array(
arr.reshape((-1, 2)),
shape=self.shape
)
def invert_to_keypoints_on_image_(self, kpsoi):
"""Invert the output of ``to_keypoints_on_image()`` in-place.
This function writes in-place into this ``BoundingBoxesOnImage``
instance.
Added in 0.4.0.
Parameters
----------
kpsoi : imgaug.augmentables.kps.KeypointsOnImages
Keypoints to convert back to bounding boxes, i.e. the outputs
of ``to_keypoints_on_image()``.
Returns
-------
BoundingBoxesOnImage
Bounding boxes container with updated coordinates.
Note that the instance is also updated in-place.
"""
assert len(kpsoi.keypoints) == len(self.bounding_boxes) * 4, (
"Expected %d coordinates, got %d." % (
len(self.bounding_boxes) * 2, len(kpsoi.keypoints)))
for i, bb in enumerate(self.bounding_boxes):
xx = [kpsoi.keypoints[4*i+0].x, kpsoi.keypoints[4*i+1].x,
kpsoi.keypoints[4*i+2].x, kpsoi.keypoints[4*i+3].x]
yy = [kpsoi.keypoints[4*i+0].y, kpsoi.keypoints[4*i+1].y,
kpsoi.keypoints[4*i+2].y, kpsoi.keypoints[4*i+3].y]
bb.x1 = min(xx)
bb.y1 = min(yy)
bb.x2 = max(xx)
bb.y2 = max(yy)
self.shape = kpsoi.shape
return self
def to_polygons_on_image(self):
"""Convert the bounding boxes to one ``PolygonsOnImage`` instance.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
A ``PolygonsOnImage`` containing polygons. Each polygon covers
the same area as the corresponding bounding box.
"""
from .polys import PolygonsOnImage
polygons = [bb.to_polygon() for bb in self.bounding_boxes]
return PolygonsOnImage(polygons, shape=self.shape)
def copy(self, bounding_boxes=None, shape=None):
"""Create a shallow copy of the ``BoundingBoxesOnImage`` instance.
Parameters
----------
bounding_boxes : None or list of imgaug.augmntables.bbs.BoundingBox, optional
List of bounding boxes on the image.
If ``None``, the instance's bounding boxes will be copied.
shape : tuple of int, optional
The shape of the image on which the bounding boxes are placed.
If ``None``, the instance's shape will be copied.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Shallow copy.
"""
if bounding_boxes is None:
bounding_boxes = self.bounding_boxes[:]
if shape is None:
# use tuple() here in case the shape was provided as a list
shape = tuple(self.shape)
return BoundingBoxesOnImage(bounding_boxes, shape)
def deepcopy(self, bounding_boxes=None, shape=None):
"""Create a deep copy of the ``BoundingBoxesOnImage`` object.
Parameters
----------
bounding_boxes : None or list of imgaug.augmntables.bbs.BoundingBox, optional
List of bounding boxes on the image.
If ``None``, the instance's bounding boxes will be copied.
shape : tuple of int, optional
The shape of the image on which the bounding boxes are placed.
If ``None``, the instance's shape will be copied.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Deep copy.
"""
# Manual copy is far faster than deepcopy, so use manual copy here.
if bounding_boxes is None:
bounding_boxes = [bb.deepcopy() for bb in self.bounding_boxes]
if shape is None:
# use tuple() here in case the shape was provided as a list
shape = tuple(self.shape)
return BoundingBoxesOnImage(bounding_boxes, shape)
def __getitem__(self, indices):
"""Get the bounding box(es) with given indices.
Added in 0.4.0.
Returns
-------
list of imgaug.augmentables.bbs.BoundingBoxes
Bounding box(es) with given indices.
"""
return self.bounding_boxes[indices]
def __iter__(self):
"""Iterate over the bounding boxes in this container.
Added in 0.4.0.
Yields
------
BoundingBox
A bounding box in this container.
The order is identical to the order in the bounding box list
provided upon class initialization.
"""
return iter(self.bounding_boxes)
def __len__(self):
"""Get the number of items in this instance.
Added in 0.4.0.
Returns
-------
int
Number of items in this instance.
"""
return len(self.items)
def __repr__(self):
return self.__str__()
def __str__(self):
return (
"BoundingBoxesOnImage(%s, shape=%s)"
% (str(self.bounding_boxes), self.shape))
class _LabelOnImageDrawer(object):
# size refers to the thickness of the BB
# height is the height of the label rectangle, not the whole BB
def __init__(self, color=(0, 255, 0), color_text=None, color_bg=None,
size=1, alpha=1.0, raise_if_out_of_image=False,
height=30, size_text=20):
self.color = color
self.color_text = color_text
self.color_bg = color_bg
self.size = size
self.alpha = alpha
self.raise_if_out_of_image = raise_if_out_of_image
self.height = height
self.size_text = size_text
def draw_on_image_(self, image, bounding_box):
# pylint: disable=invalid-name, redefined-outer-name
if self.raise_if_out_of_image:
self._do_raise_if_out_of_image(image, bounding_box)
color_text, color_bg = self._preprocess_colors()
x1, y1, x2, y2 = self._compute_bg_corner_coords(image, bounding_box)
# cant draw anything if OOI
if x2 <= x1 or y2 <= y1:
return image
# can currently only draw on images with shape (H,W,C), not (H,W)
label_arr = self._draw_label_arr(bounding_box.label,
y2 - y1, x2 - x1, image.shape[-1],
image.dtype,
color_text, color_bg,
self.size_text)
image = self._blend_label_arr_with_image_(image, label_arr,
x1, y1, x2, y2)
return image
def draw_on_image(self, image, bounding_box):
return self.draw_on_image_(np.copy(image), bounding_box)
@classmethod
def _do_raise_if_out_of_image(cls, image, bounding_box):
if bounding_box.is_out_of_image(image):
raise Exception(
"Cannot draw bounding box x1=%.8f, y1=%.8f, x2=%.8f, y2=%.8f "
"on image with shape %s." % (
bounding_box.x1, bounding_box.y1,
bounding_box.x2, bounding_box.y2,
image.shape))
def _preprocess_colors(self):
color = np.uint8(self.color) if self.color is not None else None
color_bg = self.color_bg
if self.color_bg is not None:
color_bg = np.uint8(color_bg)
else:
assert color is not None, (
"Expected `color` to be set when `color_bg` is not set, "
"but it was None.")
color_bg = color
color_text = self.color_text
if self.color_text is not None:
color_text = np.uint8(color_text)
else:
# we follow the approach from https://stackoverflow.com/a/1855903
# here
gray = (0.299 * color_bg[0]
+ 0.587 * color_bg[1]
+ 0.114 * color_bg[2])
color_text = np.full((3,),
0 if gray > 128 else 255,
dtype=np.uint8)
return color_text, color_bg
def _compute_bg_corner_coords(self, image, bounding_box):
bb = bounding_box
offset = self.size
height, width = image.shape[0:2]
y1, x1, x2 = bb.y1_int, bb.x1_int, bb.x2_int
# dont use bb.y2 here! we want the label to be above the BB
y1 = y1 - 1 - self.height
y2 = y1 + self.height
x1 = x1 - offset + 1
x2 = x2 + offset
y1, y2 = np.clip([y1, y2], 0, height-1)
x1, x2 = np.clip([x1, x2], 0, width-1)
return x1, y1, x2, y2
@classmethod
def _draw_label_arr(cls, label, height, width, nb_channels, dtype,
color_text, color_bg, size_text):
label_arr = np.zeros((height, width, nb_channels), dtype=dtype)
label_arr[...] = color_bg.reshape((1, 1, -1))
label_arr = ia.draw_text(label_arr,
x=2, y=2,
text=str(label),
color=color_text,
size=size_text)
return label_arr
def _blend_label_arr_with_image_(self, image, label_arr, x1, y1, x2, y2):
alpha = self.alpha
if alpha >= 0.99:
image[y1:y2, x1:x2, :] = label_arr
else:
input_dtype = image.dtype
foreground = label_arr.astype(np.float64)
background = image[y1:y2, x1:x2, :].astype(np.float64)
blend = (1 - alpha) * background + alpha * foreground
blend = np.clip(blend, 0, 255).astype(input_dtype)
image[y1:y2, x1:x2, :] = blend
return image
================================================
FILE: imgaug/augmentables/heatmaps.py
================================================
"""Classes to represent heatmaps, i.e. float arrays of ``[0.0, 1.0]``."""
from __future__ import print_function, division, absolute_import
import numpy as np
import six.moves as sm
from .. import imgaug as ia
from .base import IAugmentable
class HeatmapsOnImage(IAugmentable):
"""Object representing heatmaps on a single image.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Array representing the heatmap(s) on a single image.
Multiple heatmaps may be provided, in which case ``C`` is expected to
denote the heatmap index.
The array must be of dtype ``float32``.
shape : tuple of int
Shape of the image on which the heatmap(s) is/are placed.
**Not** the shape of the heatmap(s) array, unless it is identical
to the image shape (note the likely difference between the arrays
in the number of channels).
This is expected to be ``(H, W)`` or ``(H, W, C)`` with ``C`` usually
being ``3``.
If there is no corresponding image, use ``(H_arr, W_arr)`` instead,
where ``H_arr`` is the height of the heatmap(s) array
(analogous ``W_arr``).
min_value : float, optional
Minimum value for the heatmaps that `arr` represents. This will
usually be ``0.0``.
max_value : float, optional
Maximum value for the heatmaps that `arr` represents. This will
usually be ``1.0``.
"""
def __init__(self, arr, shape, min_value=0.0, max_value=1.0):
"""Construct a new HeatmapsOnImage object."""
assert ia.is_np_array(arr), (
"Expected numpy array as heatmap input array, "
"got type %s" % (type(arr),))
# TODO maybe allow 0-sized heatmaps? in that case the min() and max()
# must be adjusted
assert arr.shape[0] > 0 and arr.shape[1] > 0, (
"Expected numpy array as heatmap with height and width greater "
"than 0, got shape %s." % (arr.shape,))
assert arr.dtype.name in ["float32"], (
"Heatmap input array expected to be of dtype float32, "
"got dtype %s." % (arr.dtype,))
assert arr.ndim in [2, 3], (
"Heatmap input array must be 2d or 3d, got shape %s." % (
arr.shape,))
assert len(shape) in [2, 3], (
"Argument 'shape' in HeatmapsOnImage expected to be 2d or 3d, "
"got shape %s." % (shape,))
assert min_value < max_value, (
"Expected min_value to be lower than max_value, "
"got %.4f and %.4f" % (min_value, max_value))
eps = np.finfo(arr.dtype).eps
components = arr.flat[0:50]
beyond_min = np.min(components) < min_value - eps
beyond_max = np.max(components) > max_value + eps
if beyond_min or beyond_max:
ia.warn(
"Value range of heatmap was chosen to be (%.8f, %.8f), but "
"found actual min/max of (%.8f, %.8f). Array will be "
"clipped to chosen value range." % (
min_value, max_value, np.min(arr), np.max(arr)))
arr = np.clip(arr, min_value, max_value)
if arr.ndim == 2:
arr = arr[..., np.newaxis]
self.arr_was_2d = True
else:
self.arr_was_2d = False
min_is_zero = 0.0 - eps < min_value < 0.0 + eps
max_is_one = 1.0 - eps < max_value < 1.0 + eps
if min_is_zero and max_is_one:
self.arr_0to1 = arr
else:
self.arr_0to1 = (arr - min_value) / (max_value - min_value)
self.shape = shape
self.min_value = min_value
self.max_value = max_value
def get_arr(self):
"""Get the heatmap's array in value range provided to ``__init__()``.
The :class:`HeatmapsOnImage` object saves heatmaps internally in the
value range ``[0.0, 1.0]``. This function converts the internal
representation to ``[min, max]``, where ``min`` and ``max`` are
provided to :func:`HeatmapsOnImage.__init__` upon instantiation of
the object.
Returns
-------
(H,W) ndarray or (H,W,C) ndarray
Heatmap array of dtype ``float32``.
"""
if self.arr_was_2d and self.arr_0to1.shape[2] == 1:
arr = self.arr_0to1[:, :, 0]
else:
arr = self.arr_0to1
eps = np.finfo(np.float32).eps
min_is_zero = 0.0 - eps < self.min_value < 0.0 + eps
max_is_one = 1.0 - eps < self.max_value < 1.0 + eps
if min_is_zero and max_is_one:
return np.copy(arr)
diff = self.max_value - self.min_value
return self.min_value + diff * arr
# TODO
# def find_global_maxima(self):
# raise NotImplementedError()
def draw(self, size=None, cmap="jet"):
"""Render the heatmaps as RGB images.
Parameters
----------
size : None or float or iterable of int or iterable of float, optional
Size of the rendered RGB image as ``(height, width)``.
See :func:`~imgaug.imgaug.imresize_single_image` for details.
If set to ``None``, no resizing is performed and the size of the
heatmaps array is used.
cmap : str or None, optional
Name of the ``matplotlib`` color map to use when convert the
heatmaps to RGB images.
If set to ``None``, no color map will be used and the heatmaps
will be converted to simple intensity maps.
Returns
-------
list of (H,W,3) ndarray
Rendered heatmaps as ``uint8`` arrays.
Always a **list** containing one RGB image per heatmap array
channel.
"""
heatmaps_uint8 = self.to_uint8()
heatmaps_drawn = []
for c in sm.xrange(heatmaps_uint8.shape[2]):
# We use c:c+1 here to get a (H,W,1) array. Otherwise imresize
# would have to re-attach an axis.
heatmap_c = heatmaps_uint8[..., c:c+1]
if size is not None:
heatmap_c_rs = ia.imresize_single_image(
heatmap_c, size, interpolation="nearest")
else:
heatmap_c_rs = heatmap_c
heatmap_c_rs = np.squeeze(heatmap_c_rs).astype(np.float32) / 255.0
if cmap is not None:
# import only when necessary (faster startup; optional
# dependency; less fragile -- see issue #225)
import matplotlib.pyplot as plt
cmap_func = plt.get_cmap(cmap)
heatmap_cmapped = cmap_func(heatmap_c_rs)
heatmap_cmapped = np.delete(heatmap_cmapped, 3, 2)
else:
heatmap_cmapped = np.tile(
heatmap_c_rs[..., np.newaxis], (1, 1, 3))
heatmap_cmapped = np.clip(
heatmap_cmapped * 255, 0, 255).astype(np.uint8)
heatmaps_drawn.append(heatmap_cmapped)
return heatmaps_drawn
def draw_on_image(self, image, alpha=0.75, cmap="jet", resize="heatmaps"):
"""Draw the heatmaps as overlays over an image.
Parameters
----------
image : (H,W,3) ndarray
Image onto which to draw the heatmaps.
Expected to be of dtype ``uint8``.
alpha : float, optional
Alpha/opacity value to use for the mixing of image and heatmaps.
Larger values mean that the heatmaps will be more visible and the
image less visible.
cmap : str or None, optional
Name of the ``matplotlib`` color map to use.
See :func:`HeatmapsOnImage.draw` for details.
resize : {'heatmaps', 'image'}, optional
In case of size differences between the image and heatmaps,
either the image or the heatmaps can be resized. This parameter
controls which of the two will be resized to the other's size.
Returns
-------
list of (H,W,3) ndarray
Rendered overlays as ``uint8`` arrays.
Always a **list** containing one RGB image per heatmap array
channel.
"""
# assert RGB image
assert image.ndim == 3, (
"Expected to draw on three-dimensional image, "
"got %d dimensions with shape %s instead." % (
image.ndim, image.shape))
assert image.shape[2] == 3, (
"Expected RGB image, got %d channels instead." % (image.shape[2],))
assert image.dtype.name == "uint8", (
"Expected uint8 image, got dtype %s." % (image.dtype.name,))
assert 0 - 1e-8 <= alpha <= 1.0 + 1e-8, (
"Expected 'alpha' to be in the interval [0.0, 1.0], got %.4f" % (
alpha))
assert resize in ["heatmaps", "image"], (
"Expected resize to be \"heatmaps\" or \"image\", "
"got %s instead." % (resize,))
if resize == "image":
image = ia.imresize_single_image(
image, self.arr_0to1.shape[0:2], interpolation="cubic")
heatmaps_drawn = self.draw(
size=image.shape[0:2] if resize == "heatmaps" else None,
cmap=cmap)
# TODO use blend_alpha here
mix = [
np.clip(
(1-alpha) * image + alpha * heatmap_i,
0, 255
).astype(np.uint8)
for heatmap_i
in heatmaps_drawn]
return mix
def invert(self):
"""Invert each component in the heatmap.
This shifts low values towards high values and vice versa.
This changes each value to::
v' = max - (v - min)
where ``v`` is the value at a spatial location, ``min`` is the
minimum value in the heatmap and ``max`` is the maximum value.
As the heatmap uses internally a ``0.0`` to ``1.0`` representation,
this simply becomes ``v' = 1.0 - v``.
This function can be useful e.g. when working with depth maps, where
algorithms might have an easier time representing the furthest away
points with zeros, requiring an inverted depth map.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Inverted heatmap.
"""
arr_inv = HeatmapsOnImage.from_0to1(
1 - self.arr_0to1,
shape=self.shape,
min_value=self.min_value,
max_value=self.max_value)
arr_inv.arr_was_2d = self.arr_was_2d
return arr_inv
def pad(self, top=0, right=0, bottom=0, left=0, mode="constant", cval=0.0):
"""Pad the heatmaps at their top/right/bottom/left side.
Parameters
----------
top : int, optional
Amount of pixels to add at the top side of the heatmaps.
Must be ``0`` or greater.
right : int, optional
Amount of pixels to add at the right side of the heatmaps.
Must be ``0`` or greater.
bottom : int, optional
Amount of pixels to add at the bottom side of the heatmaps.
Must be ``0`` or greater.
left : int, optional
Amount of pixels to add at the left side of the heatmaps.
Must be ``0`` or greater.
mode : string, optional
Padding mode to use. See :func:`~imgaug.imgaug.pad` for details.
cval : number, optional
Value to use for padding `mode` is ``constant``.
See :func:`~imgaug.imgaug.pad` for details.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Padded heatmaps of height ``H'=H+top+bottom`` and
width ``W'=W+left+right``.
"""
from ..augmenters import size as iasize
arr_0to1_padded = iasize.pad(
self.arr_0to1,
top=top,
right=right,
bottom=bottom,
left=left,
mode=mode,
cval=cval)
# TODO change to deepcopy()
return HeatmapsOnImage.from_0to1(
arr_0to1_padded,
shape=self.shape,
min_value=self.min_value,
max_value=self.max_value)
def pad_to_aspect_ratio(self, aspect_ratio, mode="constant", cval=0.0,
return_pad_amounts=False):
"""Pad the heatmaps until they match a target aspect ratio.
Depending on which dimension is smaller (height or width), only the
corresponding sides (left/right or top/bottom) will be padded. In
each case, both of the sides will be padded equally.
Parameters
----------
aspect_ratio : float
Target aspect ratio, given as width/height. E.g. ``2.0`` denotes
the image having twice as much width as height.
mode : str, optional
Padding mode to use.
See :func:`~imgaug.imgaug.pad` for details.
cval : number, optional
Value to use for padding if `mode` is ``constant``.
See :func:`~imgaug.imgaug.pad` for details.
return_pad_amounts : bool, optional
If ``False``, then only the padded instance will be returned.
If ``True``, a tuple with two entries will be returned, where
the first entry is the padded instance and the second entry are
the amounts by which each array side was padded. These amounts are
again a tuple of the form ``(top, right, bottom, left)``, with
each value being an integer.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Padded heatmaps as :class:`HeatmapsOnImage` instance.
tuple of int
Amounts by which the instance's array was padded on each side,
given as a tuple ``(top, right, bottom, left)``.
This tuple is only returned if `return_pad_amounts` was set to
``True``.
"""
from ..augmenters import size as iasize
arr_0to1_padded, pad_amounts = iasize.pad_to_aspect_ratio(
self.arr_0to1,
aspect_ratio=aspect_ratio,
mode=mode,
cval=cval,
return_pad_amounts=True)
# TODO change to deepcopy()
heatmaps = HeatmapsOnImage.from_0to1(
arr_0to1_padded,
shape=self.shape,
min_value=self.min_value,
max_value=self.max_value)
if return_pad_amounts:
return heatmaps, pad_amounts
return heatmaps
def avg_pool(self, block_size):
"""Average-pool the heatmap(s) array using a given block/kernel size.
Parameters
----------
block_size : int or tuple of int
Size of each block of values to pool, aka kernel size.
See :func:`~imgaug.imgaug.pool` for details.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Heatmaps after average pooling.
"""
arr_0to1_reduced = ia.avg_pool(self.arr_0to1, block_size, pad_cval=0.0)
return HeatmapsOnImage.from_0to1(
arr_0to1_reduced,
shape=self.shape,
min_value=self.min_value,
max_value=self.max_value)
def max_pool(self, block_size):
"""Max-pool the heatmap(s) array using a given block/kernel size.
Parameters
----------
block_size : int or tuple of int
Size of each block of values to pool, aka kernel size.
See :func:`~imgaug.imgaug.pool` for details.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Heatmaps after max-pooling.
"""
arr_0to1_reduced = ia.max_pool(self.arr_0to1, block_size)
return HeatmapsOnImage.from_0to1(
arr_0to1_reduced,
shape=self.shape,
min_value=self.min_value,
max_value=self.max_value)
@ia.deprecated(alt_func="HeatmapsOnImage.resize()",
comment="resize() has the exactly same interface.")
def scale(self, *args, **kwargs):
"""Resize the heatmap(s) array given a target size and interpolation."""
return self.resize(*args, **kwargs)
def resize(self, sizes, interpolation="cubic"):
"""Resize the heatmap(s) array given a target size and interpolation.
Parameters
----------
sizes : float or iterable of int or iterable of float
New size of the array in ``(height, width)``.
See :func:`~imgaug.imgaug.imresize_single_image` for details.
interpolation : None or str or int, optional
The interpolation to use during resize.
See :func:`~imgaug.imgaug.imresize_single_image` for details.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Resized heatmaps object.
"""
arr_0to1_resized = ia.imresize_single_image(
self.arr_0to1, sizes, interpolation=interpolation)
# cubic interpolation can lead to values outside of [0.0, 1.0],
# see https://github.com/opencv/opencv/issues/7195
# TODO area interpolation too?
arr_0to1_resized = np.clip(arr_0to1_resized, 0.0, 1.0)
return HeatmapsOnImage.from_0to1(
arr_0to1_resized,
shape=self.shape,
min_value=self.min_value,
max_value=self.max_value)
def to_uint8(self):
"""Convert this heatmaps object to an ``uint8`` array.
Returns
-------
(H,W,C) ndarray
Heatmap as an ``uint8`` array, i.e. with the discrete value
range ``[0, 255]``.
"""
# TODO this always returns (H,W,C), even if input ndarray was
# originally (H,W). Does it make sense here to also return
# (H,W) if self.arr_was_2d?
arr_0to255 = np.clip(np.round(self.arr_0to1 * 255), 0, 255)
arr_uint8 = arr_0to255.astype(np.uint8)
return arr_uint8
@staticmethod
def from_uint8(arr_uint8, shape, min_value=0.0, max_value=1.0):
"""Create a ``float``-based heatmaps object from an ``uint8`` array.
Parameters
----------
arr_uint8 : (H,W) ndarray or (H,W,C) ndarray
Heatmap(s) array, where ``H`` is height, ``W`` is width
and ``C`` is the number of heatmap channels.
Expected dtype is ``uint8``.
shape : tuple of int
Shape of the image on which the heatmap(s) is/are placed.
**Not** the shape of the heatmap(s) array, unless it is identical
to the image shape (note the likely difference between the arrays
in the number of channels).
If there is not a corresponding image, use the shape of the
heatmaps array.
min_value : float, optional
Minimum value of the float heatmaps that the input array
represents. This will usually be 0.0. In most other cases it will
be close to the interval ``[0.0, 1.0]``.
Calling :func:`~imgaug.HeatmapsOnImage.get_arr`, will automatically
convert the interval ``[0.0, 1.0]`` float array to this
``[min, max]`` interval.
max_value : float, optional
Minimum value of the float heatmaps that the input array
represents. This will usually be 1.0.
See parameter `min_value` for details.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Heatmaps object.
"""
arr_0to1 = arr_uint8.astype(np.float32) / 255.0
return HeatmapsOnImage.from_0to1(
arr_0to1, shape,
min_value=min_value,
max_value=max_value)
@staticmethod
def from_0to1(arr_0to1, shape, min_value=0.0, max_value=1.0):
"""Create a heatmaps object from a ``[0.0, 1.0]`` float array.
Parameters
----------
arr_0to1 : (H,W) or (H,W,C) ndarray
Heatmap(s) array, where ``H`` is the height, ``W`` is the width
and ``C`` is the number of heatmap channels.
Expected dtype is ``float32``.
shape : tuple of ints
Shape of the image on which the heatmap(s) is/are placed.
**Not** the shape of the heatmap(s) array, unless it is identical
to the image shape (note the likely difference between the arrays
in the number of channels).
If there is not a corresponding image, use the shape of the
heatmaps array.
min_value : float, optional
Minimum value of the float heatmaps that the input array
represents. This will usually be 0.0. In most other cases it will
be close to the interval ``[0.0, 1.0]``.
Calling :func:`~imgaug.HeatmapsOnImage.get_arr`, will automatically
convert the interval ``[0.0, 1.0]`` float array to this
``[min, max]`` interval.
max_value : float, optional
Minimum value of the float heatmaps that the input array
represents. This will usually be 1.0.
See parameter `min_value` for details.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Heatmaps object.
"""
heatmaps = HeatmapsOnImage(arr_0to1, shape,
min_value=0.0, max_value=1.0)
heatmaps.min_value = min_value
heatmaps.max_value = max_value
return heatmaps
# TODO change name to change_value_range()?
@classmethod
def change_normalization(cls, arr, source, target):
"""Change the value range of a heatmap array.
E.g. the value range may be changed from the interval ``[0.0, 1.0]``
to ``[-1.0, 1.0]``.
Parameters
----------
arr : ndarray
Heatmap array to modify.
source : tuple of float
Current value range of the input array, given as a
tuple ``(min, max)``, where both are ``float`` values.
target : tuple of float
Desired output value range of the array, given as a
tuple ``(min, max)``, where both are ``float`` values.
Returns
-------
ndarray
Input array, with value range projected to the desired target
value range.
"""
assert ia.is_np_array(arr), (
"Expected 'arr' to be an ndarray, got type %s." % (type(arr),))
def _validate_tuple(arg_name, arg_value):
assert isinstance(arg_value, tuple), (
"'%s' was not a HeatmapsOnImage instance, "
"expected type tuple then. Got type %s." % (
arg_name, type(arg_value),))
assert len(arg_value) == 2, (
"Expected tuple '%s' to contain exactly two entries, "
"got %d." % (arg_name, len(arg_value),))
assert arg_value[0] < arg_value[1], (
"Expected tuple '%s' to have two entries with "
"entry 1 < entry 2, got values %.4f and %.4f." % (
arg_name, arg_value[0], arg_value[1]))
if isinstance(source, HeatmapsOnImage):
source = (source.min_value, source.max_value)
else:
_validate_tuple("source", source)
if isinstance(target, HeatmapsOnImage):
target = (target.min_value, target.max_value)
else:
_validate_tuple("target", target)
# Check if source and target are the same (with a tiny bit of
# tolerance) if so, evade compuation and just copy the array instead.
# This is reasonable, as source and target will often both
# be (0.0, 1.0).
eps = np.finfo(arr.dtype).eps
mins_same = source[0] - 10*eps < target[0] < source[0] + 10*eps
maxs_same = source[1] - 10*eps < target[1] < source[1] + 10*eps
if mins_same and maxs_same:
return np.copy(arr)
min_source, max_source = source
min_target, max_target = target
diff_source = max_source - min_source
diff_target = max_target - min_target
arr_0to1 = (arr - min_source) / diff_source
arr_target = min_target + arr_0to1 * diff_target
return arr_target
# TODO make this a proper shallow-copy
def copy(self):
"""Create a shallow copy of the heatmaps object.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Shallow copy.
"""
return self.deepcopy()
def deepcopy(self):
"""Create a deep copy of the heatmaps object.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Deep copy.
"""
return HeatmapsOnImage(
self.get_arr(),
shape=self.shape,
min_value=self.min_value,
max_value=self.max_value)
================================================
FILE: imgaug/augmentables/kps.py
================================================
"""Classes to represent keypoints, i.e. points given as xy-coordinates."""
from __future__ import print_function, division, absolute_import
import numpy as np
import scipy.spatial.distance
import six.moves as sm
from .. import imgaug as ia
from .base import IAugmentable
from .utils import (
normalize_imglike_shape,
project_coords,
_remove_out_of_image_fraction_,
_handle_on_image_shape
)
def compute_geometric_median(points=None, eps=1e-5, X=None):
"""Estimate the geometric median of points in 2D.
Code from https://stackoverflow.com/a/30305181
Parameters
----------
points : (N,2) ndarray
Points in 2D. Second axis must be given in xy-form.
eps : float, optional
Distance threshold when to return the median.
X : None or (N,2) ndarray, optional
Deprecated.
Returns
-------
(2,) ndarray
Geometric median as xy-coordinate.
"""
# pylint: disable=invalid-name
if X is not None:
assert points is None
ia.warn_deprecated("Using 'X' is deprecated, use 'points' instead.")
points = X
y = np.mean(points, 0)
while True:
dist = scipy.spatial.distance.cdist(points, [y])
nonzeros = (dist != 0)[:, 0]
dist_inv = 1 / dist[nonzeros]
dist_inv_sum = np.sum(dist_inv)
dist_inv_norm = dist_inv / dist_inv_sum
T = np.sum(dist_inv_norm * points[nonzeros], 0)
num_zeros = len(points) - np.sum(nonzeros)
if num_zeros == 0:
y1 = T
elif num_zeros == len(points):
return y
else:
R = (T - y) * dist_inv_sum
r = np.linalg.norm(R)
rinv = 0 if r == 0 else num_zeros/r
y1 = max(0, 1-rinv)*T + min(1, rinv)*y
if scipy.spatial.distance.euclidean(y, y1) < eps:
return y1
y = y1
class Keypoint(object):
"""A single keypoint (aka landmark) on an image.
Parameters
----------
x : number
Coordinate of the keypoint on the x axis.
y : number
Coordinate of the keypoint on the y axis.
"""
def __init__(self, x, y):
self.x = x
self.y = y
@property
def coords(self):
"""Get the xy-coordinates as an ``(N,2)`` ndarray.
Added in 0.4.0.
Returns
-------
ndarray
An ``(N, 2)`` ``float32`` ndarray with ``N=1`` containing the
coordinates of this keypoints.
"""
arr = np.empty((1, 2), dtype=np.float32)
arr[0, :] = [self.x, self.y]
return arr
@property
def x_int(self):
"""Get the keypoint's x-coordinate, rounded to the closest integer.
Returns
-------
result : int
Keypoint's x-coordinate, rounded to the closest integer.
"""
return int(np.round(self.x))
@property
def y_int(self):
"""Get the keypoint's y-coordinate, rounded to the closest integer.
Returns
-------
result : int
Keypoint's y-coordinate, rounded to the closest integer.
"""
return int(np.round(self.y))
@property
def xy(self):
"""Get the keypoint's x- and y-coordinate as a single array.
Added in 0.4.0.
Returns
-------
ndarray
A ``(2,)`` ``ndarray`` denoting the xy-coordinate pair.
"""
return self.coords[0, :]
@property
def xy_int(self):
"""Get the keypoint's xy-coord, rounded to closest integer.
Added in 0.4.0.
Returns
-------
ndarray
A ``(2,)`` ``ndarray`` denoting the xy-coordinate pair.
"""
return np.round(self.xy).astype(np.int32)
def project_(self, from_shape, to_shape):
"""Project in-place the keypoint onto a new position on a new image.
E.g. if the keypoint is on its original image
at ``x=(10 of 100 pixels)`` and ``y=(20 of 100 pixels)`` and is
projected onto a new image with size ``(width=200, height=200)``, its
new position will be ``(20, 40)``.
This is intended for cases where the original image is resized.
It cannot be used for more complex changes (e.g. padding, cropping).
Added in 0.4.0.
Parameters
----------
from_shape : tuple of int
Shape of the original image. (Before resize.)
to_shape : tuple of int
Shape of the new image. (After resize.)
Returns
-------
imgaug.augmentables.kps.Keypoint
Keypoint object with new coordinates.
The instance of the keypoint may have been modified in-place.
"""
xy_proj = project_coords([(self.x, self.y)], from_shape, to_shape)
self.x, self.y = xy_proj[0]
return self
def project(self, from_shape, to_shape):
"""Project the keypoint onto a new position on a new image.
E.g. if the keypoint is on its original image
at ``x=(10 of 100 pixels)`` and ``y=(20 of 100 pixels)`` and is
projected onto a new image with size ``(width=200, height=200)``, its
new position will be ``(20, 40)``.
This is intended for cases where the original image is resized.
It cannot be used for more complex changes (e.g. padding, cropping).
Parameters
----------
from_shape : tuple of int
Shape of the original image. (Before resize.)
to_shape : tuple of int
Shape of the new image. (After resize.)
Returns
-------
imgaug.augmentables.kps.Keypoint
Keypoint object with new coordinates.
"""
return self.deepcopy().project_(from_shape, to_shape)
def is_out_of_image(self, image):
"""Estimate whether this point is outside of the given image plane.
Added in 0.4.0.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
bool
``True`` is the point is inside the image plane, ``False``
otherwise.
"""
shape = normalize_imglike_shape(image)
height, width = shape[0:2]
y_inside = (0 <= self.y < height)
x_inside = (0 <= self.x < width)
return not y_inside or not x_inside
def compute_out_of_image_fraction(self, image):
"""Compute fraction of the keypoint that is out of the image plane.
The fraction is always either ``1.0`` (point is outside of the image
plane) or ``0.0`` (point is inside the image plane). This method
exists for consistency with other augmentables, e.g. bounding boxes.
Added in 0.4.0.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
float
Either ``1.0`` (point is outside of the image plane) or
``0.0`` (point is inside of it).
"""
return float(self.is_out_of_image(image))
def shift_(self, x=0, y=0):
"""Move the keypoint around on an image in-place.
Added in 0.4.0.
Parameters
----------
x : number, optional
Move by this value on the x axis.
y : number, optional
Move by this value on the y axis.
Returns
-------
imgaug.augmentables.kps.Keypoint
Keypoint object with new coordinates.
The instance of the keypoint may have been modified in-place.
"""
self.x += x
self.y += y
return self
def shift(self, x=0, y=0):
"""Move the keypoint around on an image.
Parameters
----------
x : number, optional
Move by this value on the x axis.
y : number, optional
Move by this value on the y axis.
Returns
-------
imgaug.augmentables.kps.Keypoint
Keypoint object with new coordinates.
"""
return self.deepcopy().shift_(x, y)
def draw_on_image(self, image, color=(0, 255, 0), alpha=1.0, size=3,
copy=True, raise_if_out_of_image=False):
"""Draw the keypoint onto a given image.
The keypoint is drawn as a square.
Parameters
----------
image : (H,W,3) ndarray
The image onto which to draw the keypoint.
color : int or list of int or tuple of int or (3,) ndarray, optional
The RGB color of the keypoint.
If a single ``int`` ``C``, then that is equivalent to ``(C,C,C)``.
alpha : float, optional
The opacity of the drawn keypoint, where ``1.0`` denotes a fully
visible keypoint and ``0.0`` an invisible one.
size : int, optional
The size of the keypoint. If set to ``S``, each square will have
size ``S x S``.
copy : bool, optional
Whether to copy the image before drawing the keypoint.
raise_if_out_of_image : bool, optional
Whether to raise an exception if the keypoint is outside of the
image.
Returns
-------
image : (H,W,3) ndarray
Image with drawn keypoint.
"""
# pylint: disable=redefined-outer-name
if copy:
image = np.copy(image)
if image.ndim == 2:
assert ia.is_single_number(color), (
"Got a 2D image. Expected then 'color' to be a single number, "
"but got %s." % (str(color),))
elif image.ndim == 3 and ia.is_single_number(color):
color = [color] * image.shape[-1]
input_dtype = image.dtype
alpha_color = color
if alpha < 0.01:
# keypoint invisible, nothing to do
return image
if alpha > 0.99:
alpha = 1
else:
image = image.astype(np.float32, copy=False)
alpha_color = alpha * np.array(color)
height, width = image.shape[0:2]
y, x = self.y_int, self.x_int
x1 = max(x - size//2, 0)
x2 = min(x + 1 + size//2, width)
y1 = max(y - size//2, 0)
y2 = min(y + 1 + size//2, height)
x1_clipped, x2_clipped = np.clip([x1, x2], 0, width)
y1_clipped, y2_clipped = np.clip([y1, y2], 0, height)
x1_clipped_ooi = (x1_clipped < 0 or x1_clipped >= width)
x2_clipped_ooi = (x2_clipped < 0 or x2_clipped >= width+1)
y1_clipped_ooi = (y1_clipped < 0 or y1_clipped >= height)
y2_clipped_ooi = (y2_clipped < 0 or y2_clipped >= height+1)
x_ooi = (x1_clipped_ooi and x2_clipped_ooi)
y_ooi = (y1_clipped_ooi and y2_clipped_ooi)
x_zero_size = (x2_clipped - x1_clipped) < 1 # min size is 1px
y_zero_size = (y2_clipped - y1_clipped) < 1
if not x_ooi and not y_ooi and not x_zero_size and not y_zero_size:
if alpha == 1:
image[y1_clipped:y2_clipped, x1_clipped:x2_clipped] = color
else:
image[y1_clipped:y2_clipped, x1_clipped:x2_clipped] = (
(1 - alpha)
* image[y1_clipped:y2_clipped, x1_clipped:x2_clipped]
+ alpha_color
)
else:
if raise_if_out_of_image:
raise Exception(
"Cannot draw keypoint x=%.8f, y=%.8f on image with "
"shape %s." % (y, x, image.shape))
if image.dtype.name != input_dtype.name:
if input_dtype.name == "uint8":
image = np.clip(image, 0, 255, out=image)
image = image.astype(input_dtype, copy=False)
return image
def generate_similar_points_manhattan(self, nb_steps, step_size,
return_array=False):
"""Generate nearby points based on manhattan distance.
To generate the first neighbouring points, a distance of ``S`` (step
size) is moved from the center point (this keypoint) to the top,
right, bottom and left, resulting in four new points. From these new
points, the pattern is repeated. Overlapping points are ignored.
The resulting points have a shape similar to a square rotated
by ``45`` degrees.
Parameters
----------
nb_steps : int
The number of steps to move from the center point.
``nb_steps=1`` results in a total of ``5`` output points (one
center point + four neighbours).
step_size : number
The step size to move from every point to its neighbours.
return_array : bool, optional
Whether to return the generated points as a list of
:class:`Keypoint` or an array of shape ``(N,2)``, where ``N`` is
the number of generated points and the second axis contains the
x-/y-coordinates.
Returns
-------
list of imgaug.augmentables.kps.Keypoint or (N,2) ndarray
If `return_array` was ``False``, then a list of :class:`Keypoint`.
Otherwise a numpy array of shape ``(N,2)``, where ``N`` is the
number of generated points and the second axis contains
the x-/y-coordinates. The center keypoint (the one on which this
function was called) is always included.
"""
# TODO add test
# Points generates in manhattan style with S steps have a shape
# similar to a 45deg rotated square. The center line with the origin
# point has S+1+S = 1+2*S points (S to the left, S to the right).
# The lines above contain (S+1+S)-2 + (S+1+S)-2-2 + ... + 1 points.
# E.g. for S=2 it would be 3+1=4 and for S=3 it would be 5+3+1=9.
# Same for the lines below the center. Hence the total number of
# points is S+1+S + 2*(S^2).
nb_points = nb_steps + 1 + nb_steps + 2*(nb_steps**2)
points = np.zeros((nb_points, 2), dtype=np.float32)
# we start at the bottom-most line and move towards the top-most line
yy = np.linspace(
self.y - nb_steps * step_size,
self.y + nb_steps * step_size,
nb_steps + 1 + nb_steps)
# bottom-most line contains only one point
width = 1
nth_point = 0
for i_y, y in enumerate(yy):
if width == 1:
xx = [self.x]
else:
xx = np.linspace(
self.x - (width-1)//2 * step_size,
self.x + (width-1)//2 * step_size,
width)
for x in xx:
points[nth_point] = [x, y]
nth_point += 1
if i_y < nb_steps:
width += 2
else:
width -= 2
if return_array:
return points
return [self.deepcopy(x=point[0], y=point[1]) for point in points]
def coords_almost_equals(self, other, max_distance=1e-4):
"""Estimate if this and another KP have almost identical coordinates.
Added in 0.4.0.
Parameters
----------
other : imgaug.augmentables.kps.Keypoint or iterable
The other keypoint with which to compare this one.
If this is an ``iterable``, it is assumed to contain the
xy-coordinates of a keypoint.
max_distance : number, optional
The maximum euclidean distance between a this keypoint and the
other one. If the distance is exceeded, the two keypoints are not
viewed as equal.
Returns
-------
bool
Whether the two keypoints have almost identical coordinates.
"""
if ia.is_np_array(other):
# we use flat here in case other is (N,2) instead of (4,)
coords_b = other.flat
elif ia.is_iterable(other):
coords_b = list(ia.flatten(other))
else:
assert isinstance(other, Keypoint), (
"Expected 'other' to be an iterable containing one "
"(x,y)-coordinate pair or a Keypoint. "
"Got type %s." % (type(other),))
coords_b = other.coords.flat
coords_a = self.coords
return np.allclose(coords_a.flat, coords_b, atol=max_distance, rtol=0)
def almost_equals(self, other, max_distance=1e-4):
"""Compare this and another KP's coordinates.
.. note::
This method is currently identical to ``coords_almost_equals``.
It exists for consistency with ``BoundingBox`` and ``Polygons``.
Added in 0.4.0.
Parameters
----------
other : imgaug.augmentables.kps.Keypoint or iterable
The other object to compare against. Expected to be a
``Keypoint``.
max_distance : number, optional
See
:func:`~imgaug.augmentables.kps.Keypoint.coords_almost_equals`.
Returns
-------
bool
``True`` if the coordinates are almost equal. Otherwise ``False``.
"""
return self.coords_almost_equals(other, max_distance=max_distance)
def copy(self, x=None, y=None):
"""Create a shallow copy of the keypoint instance.
Parameters
----------
x : None or number, optional
Coordinate of the keypoint on the x axis.
If ``None``, the instance's value will be copied.
y : None or number, optional
Coordinate of the keypoint on the y axis.
If ``None``, the instance's value will be copied.
Returns
-------
imgaug.augmentables.kps.Keypoint
Shallow copy.
"""
return self.deepcopy(x=x, y=y)
def deepcopy(self, x=None, y=None):
"""Create a deep copy of the keypoint instance.
Parameters
----------
x : None or number, optional
Coordinate of the keypoint on the x axis.
If ``None``, the instance's value will be copied.
y : None or number, optional
Coordinate of the keypoint on the y axis.
If ``None``, the instance's value will be copied.
Returns
-------
imgaug.augmentables.kps.Keypoint
Deep copy.
"""
x = self.x if x is None else x
y = self.y if y is None else y
return Keypoint(x=x, y=y)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Keypoint(x=%.8f, y=%.8f)" % (self.x, self.y)
class KeypointsOnImage(IAugmentable):
"""Container for all keypoints on a single image.
Parameters
----------
keypoints : list of imgaug.augmentables.kps.Keypoint
List of keypoints on the image.
shape : tuple of int
The shape of the image on which the objects are placed, i.e. the
result of ``image.shape``.
Should include the number of channels, not only height and width.
Examples
--------
>>> import numpy as np
>>> from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
>>>
>>> image = np.zeros((70, 70))
>>> kps = [Keypoint(x=10, y=20), Keypoint(x=34, y=60)]
>>> kps_oi = KeypointsOnImage(kps, shape=image.shape)
"""
def __init__(self, keypoints, shape):
self.keypoints = keypoints
self.shape = _handle_on_image_shape(shape, self)
@property
def items(self):
"""Get the keypoints in this container.
Added in 0.4.0.
Returns
-------
list of Keypoint
Keypoints within this container.
"""
return self.keypoints
@items.setter
def items(self, value):
"""Set the keypoints in this container.
Added in 0.4.0.
Parameters
----------
value : list of Keypoint
Keypoints within this container.
"""
self.keypoints = value
@property
def height(self):
"""Get the image height.
Returns
-------
int
Image height.
"""
return self.shape[0]
@property
def width(self):
"""Get the image width.
Returns
-------
int
Image width.
"""
return self.shape[1]
@property
def empty(self):
"""Determine whether this object contains zero keypoints.
Returns
-------
bool
``True`` if this object contains zero keypoints.
"""
return len(self.keypoints) == 0
def on_(self, image):
"""Project all keypoints from one image shape to a new one in-place.
Added in 0.4.0.
Parameters
----------
image : ndarray or tuple of int
New image onto which the keypoints are to be projected.
May also simply be that new image's shape tuple.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Object containing all projected keypoints.
The object may have been modified in-place.
"""
# pylint: disable=invalid-name
on_shape = normalize_imglike_shape(image)
if on_shape[0:2] == self.shape[0:2]:
self.shape = on_shape # channels may differ
return self
for i, kp in enumerate(self.keypoints):
self.keypoints[i] = kp.project_(self.shape, on_shape)
self.shape = on_shape
return self
def on(self, image):
"""Project all keypoints from one image shape to a new one.
Parameters
----------
image : ndarray or tuple of int
New image onto which the keypoints are to be projected.
May also simply be that new image's shape tuple.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Object containing all projected keypoints.
"""
# pylint: disable=invalid-name
return self.deepcopy().on_(image)
def draw_on_image(self, image, color=(0, 255, 0), alpha=1.0, size=3,
copy=True, raise_if_out_of_image=False):
"""Draw all keypoints onto a given image.
Each keypoint is drawn as a square of provided color and size.
Parameters
----------
image : (H,W,3) ndarray
The image onto which to draw the keypoints.
This image should usually have the same shape as
set in ``KeypointsOnImage.shape``.
color : int or list of int or tuple of int or (3,) ndarray, optional
The RGB color of all keypoints.
If a single ``int`` ``C``, then that is equivalent to ``(C,C,C)``.
alpha : float, optional
The opacity of the drawn keypoint, where ``1.0`` denotes a fully
visible keypoint and ``0.0`` an invisible one.
size : int, optional
The size of each point. If set to ``C``, each square will have
size ``C x C``.
copy : bool, optional
Whether to copy the image before drawing the points.
raise_if_out_of_image : bool, optional
Whether to raise an exception if any keypoint is outside of the
image.
Returns
-------
(H,W,3) ndarray
Image with drawn keypoints.
"""
# pylint: disable=redefined-outer-name
image = np.copy(image) if copy else image
for keypoint in self.keypoints:
image = keypoint.draw_on_image(
image, color=color, alpha=alpha, size=size, copy=False,
raise_if_out_of_image=raise_if_out_of_image)
return image
def remove_out_of_image_fraction_(self, fraction):
"""Remove all KPs with an OOI fraction of at least `fraction` in-place.
'OOI' is the abbreviation for 'out of image'.
This method exists for consistency with other augmentables, e.g.
bounding boxes.
Added in 0.4.0.
Parameters
----------
fraction : number
Minimum out of image fraction that a keypoint has to have in
order to be removed. Note that any keypoint can only have a
fraction of either ``1.0`` (is outside) or ``0.0`` (is inside).
Set this to ``0.0+eps`` to remove all points that are outside of
the image. Setting this to ``0.0`` will remove all points.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Reduced set of keypoints, with those thathad an out of image
fraction greater or equal the given one removed.
The object may have been modified in-place.
"""
return _remove_out_of_image_fraction_(self, fraction)
def remove_out_of_image_fraction(self, fraction):
"""Remove all KPs with an out of image fraction of at least `fraction`.
This method exists for consistency with other augmentables, e.g.
bounding boxes.
Added in 0.4.0.
Parameters
----------
fraction : number
Minimum out of image fraction that a keypoint has to have in
order to be removed. Note that any keypoint can only have a
fraction of either ``1.0`` (is outside) or ``0.0`` (is inside).
Set this to ``0.0+eps`` to remove all points that are outside of
the image. Setting this to ``0.0`` will remove all points.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Reduced set of keypoints, with those thathad an out of image
fraction greater or equal the given one removed.
"""
return self.deepcopy().remove_out_of_image_fraction_(fraction)
def clip_out_of_image_(self):
"""Remove all KPs that are outside of the image plane.
This method exists for consistency with other augmentables, e.g.
bounding boxes.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Keypoints that are inside the image plane.
The object may have been modified in-place.
"""
# we could use anything >0 here as the fraction
return self.remove_out_of_image_fraction_(0.5)
def clip_out_of_image(self):
"""Remove all KPs that are outside of the image plane.
This method exists for consistency with other augmentables, e.g.
bounding boxes.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Keypoints that are inside the image plane.
"""
return self.deepcopy().clip_out_of_image_()
def shift_(self, x=0, y=0):
"""Move the keypoints on the x/y-axis in-place.
Added in 0.4.0.
Parameters
----------
x : number, optional
Move each keypoint by this value on the x axis.
y : number, optional
Move each keypoint by this value on the y axis.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Keypoints after moving them.
The object and its items may have been modified in-place.
"""
for i, keypoint in enumerate(self.keypoints):
self.keypoints[i] = keypoint.shift_(x=x, y=y)
return self
def shift(self, x=0, y=0):
"""Move the keypoints on the x/y-axis.
Parameters
----------
x : number, optional
Move each keypoint by this value on the x axis.
y : number, optional
Move each keypoint by this value on the y axis.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Keypoints after moving them.
"""
return self.deepcopy().shift_(x=x, y=y)
@ia.deprecated(alt_func="KeypointsOnImage.to_xy_array()")
def get_coords_array(self):
"""Convert all keypoint coordinates to an array of shape ``(N,2)``.
Returns
-------
(N, 2) ndarray
Array containing the coordinates of all keypoints.
``N`` denotes the number of keypoints. The second axis denotes
the x/y-coordinates.
"""
return self.to_xy_array()
def to_xy_array(self):
"""Convert all keypoint coordinates to an array of shape ``(N,2)``.
Returns
-------
(N, 2) ndarray
Array containing the coordinates of all keypoints.
``N`` denotes the number of keypoints. The second axis denotes
the x/y-coordinates.
"""
result = np.zeros((len(self.keypoints), 2), dtype=np.float32)
for i, keypoint in enumerate(self.keypoints):
result[i, 0] = keypoint.x
result[i, 1] = keypoint.y
return result
@staticmethod
@ia.deprecated(alt_func="KeypointsOnImage.from_xy_array()")
def from_coords_array(coords, shape):
"""Convert an ``(N,2)`` array to a ``KeypointsOnImage`` object.
Parameters
----------
coords : (N, 2) ndarray
Coordinates of ``N`` keypoints on an image, given as a ``(N,2)``
array of xy-coordinates.
shape : tuple
The shape of the image on which the keypoints are placed.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
:class:`KeypointsOnImage` object containing the array's keypoints.
"""
return KeypointsOnImage.from_xy_array(coords, shape)
@classmethod
def from_xy_array(cls, xy, shape):
"""Convert an ``(N,2)`` array to a ``KeypointsOnImage`` object.
Parameters
----------
xy : (N, 2) ndarray or iterable of iterable of number
Coordinates of ``N`` keypoints on an image, given as a ``(N,2)``
array of xy-coordinates.
shape : tuple of int or ndarray
The shape of the image on which the keypoints are placed.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
:class:`KeypointsOnImage` object containing the array's keypoints.
"""
xy = np.array(xy, dtype=np.float32)
# note that np.array([]) is (0,), not (0, 2)
if xy.shape[0] == 0: # pylint: disable=unsubscriptable-object
return KeypointsOnImage([], shape)
assert xy.ndim == 2 and xy.shape[-1] == 2, ( # pylint: disable=unsubscriptable-object
"Expected input array to have shape (N,2), "
"got shape %s." % (xy.shape,))
keypoints = [Keypoint(x=coord[0], y=coord[1]) for coord in xy]
return KeypointsOnImage(keypoints, shape)
def fill_from_xy_array_(self, xy):
"""Modify the keypoint coordinates of this instance in-place.
.. note::
This currently expects that `xy` contains exactly as many
coordinates as there are keypoints in this instance. Otherwise,
an ``AssertionError`` will be raised.
Added in 0.4.0.
Parameters
----------
xy : (N, 2) ndarray or iterable of iterable of number
Coordinates of ``N`` keypoints on an image, given as a ``(N,2)``
array of xy-coordinates. ``N`` must match the number of keypoints
in this instance.
Returns
-------
KeypointsOnImage
This instance itself, with updated keypoint coordinates.
Note that the instance was modified in-place.
"""
xy = np.array(xy, dtype=np.float32)
# note that np.array([]) is (0,), not (0, 2)
assert xy.shape[0] == 0 or (xy.ndim == 2 and xy.shape[-1] == 2), ( # pylint: disable=unsubscriptable-object
"Expected input array to have shape (N,2), "
"got shape %s." % (xy.shape,))
assert len(xy) == len(self.keypoints), (
"Expected to receive as many keypoint coordinates as there are "
"currently keypoints in this instance. Got %d, expected %d." % (
len(xy), len(self.keypoints)))
for kp, (x, y) in zip(self.keypoints, xy):
kp.x = x
kp.y = y
return self
# TODO add to_gaussian_heatmaps(), from_gaussian_heatmaps()
def to_keypoint_image(self, size=1):
"""Create an ``(H,W,N)`` image with keypoint coordinates set to ``255``.
This method generates a new ``uint8`` array of shape ``(H,W,N)``,
where ``H`` is the ``.shape`` height, ``W`` the ``.shape`` width and
``N`` is the number of keypoints. The array is filled with zeros.
The coordinate of the ``n``-th keypoint is set to ``255`` in the
``n``-th channel.
This function can be used as a helper when augmenting keypoints with
a method that only supports the augmentation of images.
Parameters
-------
size : int
Size of each (squared) point.
Returns
-------
(H,W,N) ndarray
Image in which the keypoints are marked. ``H`` is the height,
defined in ``KeypointsOnImage.shape[0]`` (analogous ``W``).
``N`` is the number of keypoints.
"""
height, width = self.shape[0:2]
image = np.zeros((height, width, len(self.keypoints)), dtype=np.uint8)
assert size % 2 != 0, (
"Expected 'size' to have an odd value, got %d instead." % (size,))
sizeh = max(0, (size-1)//2)
for i, keypoint in enumerate(self.keypoints):
# TODO for float values spread activation over several cells
# here and do voting at the end
y = keypoint.y_int
x = keypoint.x_int
x1 = np.clip(x - sizeh, 0, width-1)
x2 = np.clip(x + sizeh + 1, 0, width)
y1 = np.clip(y - sizeh, 0, height-1)
y2 = np.clip(y + sizeh + 1, 0, height)
if x1 < x2 and y1 < y2:
image[y1:y2, x1:x2, i] = 128
if 0 <= y < height and 0 <= x < width:
image[y, x, i] = 255
return image
@staticmethod
def from_keypoint_image(image, if_not_found_coords={"x": -1, "y": -1},
threshold=1, nb_channels=None):
"""Convert ``to_keypoint_image()`` outputs to ``KeypointsOnImage``.
This is the inverse of :func:`KeypointsOnImage.to_keypoint_image`.
Parameters
----------
image : (H,W,N) ndarray
The keypoints image. N is the number of keypoints.
if_not_found_coords : tuple or list or dict or None, optional
Coordinates to use for keypoints that cannot be found in `image`.
* If this is a ``list``/``tuple``, it must contain two ``int``
values.
* If it is a ``dict``, it must contain the keys ``x`` and
``y`` with each containing one ``int`` value.
* If this is ``None``, then the keypoint will not be added to the
final :class:`KeypointsOnImage` object.
threshold : int, optional
The search for keypoints works by searching for the argmax in
each channel. This parameters contains the minimum value that
the max must have in order to be viewed as a keypoint.
nb_channels : None or int, optional
Number of channels of the image on which the keypoints are placed.
Some keypoint augmenters require that information.
If set to ``None``, the keypoint's shape will be set
to ``(height, width)``, otherwise ``(height, width, nb_channels)``.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
The extracted keypoints.
"""
# pylint: disable=dangerous-default-value
assert image.ndim == 3, (
"Expected 'image' to have three dimensions, "
"got %d with shape %s instead." % (image.ndim, image.shape))
height, width, nb_keypoints = image.shape
drop_if_not_found = False
if if_not_found_coords is None:
drop_if_not_found = True
if_not_found_x = -1
if_not_found_y = -1
elif isinstance(if_not_found_coords, (tuple, list)):
assert len(if_not_found_coords) == 2, (
"Expected tuple 'if_not_found_coords' to contain exactly two "
"values, got %d values." % (len(if_not_found_coords),))
if_not_found_x = if_not_found_coords[0]
if_not_found_y = if_not_found_coords[1]
elif isinstance(if_not_found_coords, dict):
if_not_found_x = if_not_found_coords["x"]
if_not_found_y = if_not_found_coords["y"]
else:
raise Exception(
"Expected if_not_found_coords to be None or tuple or list "
"or dict, got %s." % (type(if_not_found_coords),))
keypoints = []
for i in sm.xrange(nb_keypoints):
maxidx_flat = np.argmax(image[..., i])
maxidx_ndim = np.unravel_index(maxidx_flat, (height, width))
found = (image[maxidx_ndim[0], maxidx_ndim[1], i] >= threshold)
if found:
x = maxidx_ndim[1] + 0.5
y = maxidx_ndim[0] + 0.5
keypoints.append(Keypoint(x=x, y=y))
else:
if drop_if_not_found:
# dont add the keypoint to the result list, i.e. drop it
pass
else:
keypoints.append(Keypoint(x=if_not_found_x,
y=if_not_found_y))
out_shape = (height, width)
if nb_channels is not None:
out_shape += (nb_channels,)
return KeypointsOnImage(keypoints, shape=out_shape)
def to_distance_maps(self, inverted=False):
"""Generate a ``(H,W,N)`` array of distance maps for ``N`` keypoints.
The ``n``-th distance map contains at every location ``(y, x)`` the
euclidean distance to the ``n``-th keypoint.
This function can be used as a helper when augmenting keypoints with a
method that only supports the augmentation of images.
Parameters
-------
inverted : bool, optional
If ``True``, inverted distance maps are returned where each
distance value d is replaced by ``d/(d+1)``, i.e. the distance
maps have values in the range ``(0.0, 1.0]`` with ``1.0`` denoting
exactly the position of the respective keypoint.
Returns
-------
(H,W,N) ndarray
A ``float32`` array containing ``N`` distance maps for ``N``
keypoints. Each location ``(y, x, n)`` in the array denotes the
euclidean distance at ``(y, x)`` to the ``n``-th keypoint.
If `inverted` is ``True``, the distance ``d`` is replaced
by ``d/(d+1)``. The height and width of the array match the
height and width in ``KeypointsOnImage.shape``.
"""
height, width = self.shape[0:2]
distance_maps = np.zeros((height, width, len(self.keypoints)),
dtype=np.float32)
yy = np.arange(0, height)
xx = np.arange(0, width)
grid_xx, grid_yy = np.meshgrid(xx, yy)
for i, keypoint in enumerate(self.keypoints):
y, x = keypoint.y, keypoint.x
distance_maps[:, :, i] = (grid_xx - x) ** 2 + (grid_yy - y) ** 2
distance_maps = np.sqrt(distance_maps)
if inverted:
return 1/(distance_maps+1)
return distance_maps
# TODO add option to if_not_found_coords to reuse old keypoint coords
@staticmethod
def from_distance_maps(distance_maps, inverted=False,
if_not_found_coords={"x": -1, "y": -1},
threshold=None, nb_channels=None):
"""Convert outputs of ``to_distance_maps()`` to ``KeypointsOnImage``.
This is the inverse of :func:`KeypointsOnImage.to_distance_maps`.
Parameters
----------
distance_maps : (H,W,N) ndarray
The distance maps. ``N`` is the number of keypoints.
inverted : bool, optional
Whether the given distance maps were generated in inverted mode
(i.e. :func:`KeypointsOnImage.to_distance_maps` was called with
``inverted=True``) or in non-inverted mode.
if_not_found_coords : tuple or list or dict or None, optional
Coordinates to use for keypoints that cannot be found
in `distance_maps`.
* If this is a ``list``/``tuple``, it must contain two ``int``
values.
* If it is a ``dict``, it must contain the keys ``x`` and
``y`` with each containing one ``int`` value.
* If this is ``None``, then the keypoint will not be added to the
final :class:`KeypointsOnImage` object.
threshold : float, optional
The search for keypoints works by searching for the
argmin (non-inverted) or argmax (inverted) in each channel. This
parameters contains the maximum (non-inverted) or
minimum (inverted) value to accept in order to view a hit as a
keypoint. Use ``None`` to use no min/max.
nb_channels : None or int, optional
Number of channels of the image on which the keypoints are placed.
Some keypoint augmenters require that information.
If set to ``None``, the keypoint's shape will be set
to ``(height, width)``, otherwise ``(height, width, nb_channels)``.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
The extracted keypoints.
"""
# pylint: disable=dangerous-default-value
assert distance_maps.ndim == 3, (
"Expected three-dimensional input, got %d dimensions and "
"shape %s." % (distance_maps.ndim, distance_maps.shape))
height, width, nb_keypoints = distance_maps.shape
drop_if_not_found = False
if if_not_found_coords is None:
drop_if_not_found = True
if_not_found_x = -1
if_not_found_y = -1
elif isinstance(if_not_found_coords, (tuple, list)):
assert len(if_not_found_coords) == 2, (
"Expected tuple/list 'if_not_found_coords' to contain "
"exactly two entries, got %d." % (len(if_not_found_coords),))
if_not_found_x = if_not_found_coords[0]
if_not_found_y = if_not_found_coords[1]
elif isinstance(if_not_found_coords, dict):
if_not_found_x = if_not_found_coords["x"]
if_not_found_y = if_not_found_coords["y"]
else:
raise Exception(
"Expected if_not_found_coords to be None or tuple or list or "
"dict, got %s." % (type(if_not_found_coords),))
keypoints = []
for i in sm.xrange(nb_keypoints):
# TODO introduce voting here among all distance values that have
# min/max values
if inverted:
hitidx_flat = np.argmax(distance_maps[..., i])
else:
hitidx_flat = np.argmin(distance_maps[..., i])
hitidx_ndim = np.unravel_index(hitidx_flat, (height, width))
if not inverted and threshold is not None:
found = (distance_maps[hitidx_ndim[0], hitidx_ndim[1], i]
< threshold)
elif inverted and threshold is not None:
found = (distance_maps[hitidx_ndim[0], hitidx_ndim[1], i]
>= threshold)
else:
found = True
if found:
keypoints.append(Keypoint(x=hitidx_ndim[1], y=hitidx_ndim[0]))
else:
if drop_if_not_found:
# dont add the keypoint to the result list, i.e. drop it
pass
else:
keypoints.append(Keypoint(x=if_not_found_x,
y=if_not_found_y))
out_shape = (height, width)
if nb_channels is not None:
out_shape += (nb_channels,)
return KeypointsOnImage(keypoints, shape=out_shape)
# TODO add to_keypoints_on_image_() and call that wherever possible
def to_keypoints_on_image(self):
"""Convert the keypoints to one ``KeypointsOnImage`` instance.
This method exists for consistency with ``BoundingBoxesOnImage``,
``PolygonsOnImage`` and ``LineStringsOnImage``.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Copy of this keypoints instance.
"""
return self.deepcopy()
def invert_to_keypoints_on_image_(self, kpsoi):
"""Invert the output of ``to_keypoints_on_image()`` in-place.
This function writes in-place into this ``KeypointsOnImage``
instance.
Added in 0.4.0.
Parameters
----------
kpsoi : imgaug.augmentables.kps.KeypointsOnImages
Keypoints to copy data from, i.e. the outputs of
``to_keypoints_on_image()``.
Returns
-------
KeypointsOnImage
Keypoints container with updated coordinates.
Note that the instance is also updated in-place.
"""
nb_points_exp = len(self.keypoints)
assert len(kpsoi.keypoints) == nb_points_exp, (
"Expected %d coordinates, got %d." % (
nb_points_exp, len(kpsoi.keypoints)))
for kp_target, kp_source in zip(self.keypoints, kpsoi.keypoints):
kp_target.x = kp_source.x
kp_target.y = kp_source.y
self.shape = kpsoi.shape
return self
def copy(self, keypoints=None, shape=None):
"""Create a shallow copy of the ``KeypointsOnImage`` object.
Parameters
----------
keypoints : None or list of imgaug.Keypoint, optional
List of keypoints on the image.
If ``None``, the instance's keypoints will be copied.
shape : tuple of int, optional
The shape of the image on which the keypoints are placed.
If ``None``, the instance's shape will be copied.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Shallow copy.
"""
if keypoints is None:
keypoints = self.keypoints[:]
if shape is None:
# use tuple() here in case the shape was provided as a list
shape = tuple(self.shape)
return KeypointsOnImage(keypoints, shape)
def deepcopy(self, keypoints=None, shape=None):
"""Create a deep copy of the ``KeypointsOnImage`` object.
Parameters
----------
keypoints : None or list of imgaug.Keypoint, optional
List of keypoints on the image.
If ``None``, the instance's keypoints will be copied.
shape : tuple of int, optional
The shape of the image on which the keypoints are placed.
If ``None``, the instance's shape will be copied.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Deep copy.
"""
# Manual copy is far faster than deepcopy, so use manual copy here.
if keypoints is None:
keypoints = [kp.deepcopy() for kp in self.keypoints]
if shape is None:
# use tuple() here in case the shape was provided as a list
shape = tuple(self.shape)
return KeypointsOnImage(keypoints, shape)
def __getitem__(self, indices):
"""Get the keypoint(s) with given indices.
Added in 0.4.0.
Returns
-------
list of imgaug.augmentables.kps.Keypoint
Keypoint(s) with given indices.
"""
return self.keypoints[indices]
def __iter__(self):
"""Iterate over the keypoints in this container.
Added in 0.4.0.
Yields
------
Keypoint
A keypoint in this container.
The order is identical to the order in the keypoint list
provided upon class initialization.
"""
return iter(self.items)
def __len__(self):
"""Get the number of items in this instance.
Added in 0.4.0.
Returns
-------
int
Number of items in this instance.
"""
return len(self.items)
def __repr__(self):
return self.__str__()
def __str__(self):
return "KeypointsOnImage(%s, shape=%s)" % (
str(self.keypoints), self.shape)
================================================
FILE: imgaug/augmentables/lines.py
================================================
"""Classes representing lines."""
from __future__ import print_function, division, absolute_import
import copy as copylib
import numpy as np
import skimage.draw
import skimage.measure
import cv2
from .. import imgaug as ia
from .base import IAugmentable
from .utils import (
normalize_imglike_shape,
project_coords_,
interpolate_points,
_remove_out_of_image_fraction_,
_normalize_shift_args,
_handle_on_image_shape
)
# TODO Add Line class and make LineString a list of Line elements
# TODO add to_distance_maps(), compute_hausdorff_distance(), intersects(),
# find_self_intersections(), is_self_intersecting(),
# remove_self_intersections()
class LineString(object):
"""Class representing line strings.
A line string is a collection of connected line segments, each
having a start and end point. Each point is given as its ``(x, y)``
absolute (sub-)pixel coordinates. The end point of each segment is
also the start point of the next segment.
The line string is not closed, i.e. start and end point are expected to
differ and will not be connected in drawings.
Parameters
----------
coords : iterable of tuple of number or ndarray
The points of the line string.
label : None or str, optional
The label of the line string.
"""
def __init__(self, coords, label=None):
"""Create a new LineString instance."""
# use the conditions here to avoid unnecessary copies of ndarray inputs
if ia.is_np_array(coords):
if coords.dtype.name != "float32":
coords = coords.astype(np.float32)
elif len(coords) == 0:
coords = np.zeros((0, 2), dtype=np.float32)
else:
assert ia.is_iterable(coords), (
"Expected 'coords' to be an iterable, "
"got type %s." % (type(coords),))
assert all([len(coords_i) == 2 for coords_i in coords]), (
"Expected 'coords' to contain (x,y) tuples, "
"got %s." % (str(coords),))
coords = np.float32(coords)
assert coords.ndim == 2 and coords.shape[-1] == 2, (
"Expected 'coords' to have shape (N, 2), got shape %s." % (
coords.shape,))
self.coords = coords
self.label = label
@property
def length(self):
"""Compute the total euclidean length of the line string.
Returns
-------
float
The length based on euclidean distance, i.e. the sum of the
lengths of each line segment.
"""
if len(self.coords) == 0:
return 0
return np.sum(self.compute_neighbour_distances())
@property
def xx(self):
"""Get an array of x-coordinates of all points of the line string.
Returns
-------
ndarray
``float32`` x-coordinates of the line string points.
"""
return self.coords[:, 0]
@property
def yy(self):
"""Get an array of y-coordinates of all points of the line string.
Returns
-------
ndarray
``float32`` y-coordinates of the line string points.
"""
return self.coords[:, 1]
@property
def xx_int(self):
"""Get an array of discrete x-coordinates of all points.
The conversion from ``float32`` coordinates to ``int32`` is done
by first rounding the coordinates to the closest integer and then
removing everything after the decimal point.
Returns
-------
ndarray
``int32`` x-coordinates of the line string points.
"""
return np.round(self.xx).astype(np.int32)
@property
def yy_int(self):
"""Get an array of discrete y-coordinates of all points.
The conversion from ``float32`` coordinates to ``int32`` is done
by first rounding the coordinates to the closest integer and then
removing everything after the decimal point.
Returns
-------
ndarray
``int32`` y-coordinates of the line string points.
"""
return np.round(self.yy).astype(np.int32)
@property
def height(self):
"""Compute the height of a bounding box encapsulating the line.
The height is computed based on the two points with lowest and
largest y-coordinates.
Returns
-------
float
The height of the line string.
"""
if len(self.coords) <= 1:
return 0
return np.max(self.yy) - np.min(self.yy)
@property
def width(self):
"""Compute the width of a bounding box encapsulating the line.
The width is computed based on the two points with lowest and
largest x-coordinates.
Returns
-------
float
The width of the line string.
"""
if len(self.coords) <= 1:
return 0
return np.max(self.xx) - np.min(self.xx)
def get_pointwise_inside_image_mask(self, image):
"""Determine per point whether it is inside of a given image plane.
Parameters
----------
image : ndarray or tuple of int
Either an image with shape ``(H,W,[C])`` or a ``tuple`` denoting
such an image shape.
Returns
-------
ndarray
``(N,) ``bool`` array with one value for each of the ``N`` points
indicating whether it is inside of the provided image
plane (``True``) or not (``False``).
"""
# pylint: disable=misplaced-comparison-constant
if len(self.coords) == 0:
return np.zeros((0,), dtype=bool)
shape = normalize_imglike_shape(image)
height, width = shape[0:2]
x_within = np.logical_and(0 <= self.xx, self.xx < width)
y_within = np.logical_and(0 <= self.yy, self.yy < height)
return np.logical_and(x_within, y_within)
# TODO add closed=False/True?
def compute_neighbour_distances(self):
"""Compute the euclidean distance between each two consecutive points.
Returns
-------
ndarray
``(N-1,)`` ``float32`` array of euclidean distances between point
pairs. Same order as in `coords`.
"""
if len(self.coords) <= 1:
return np.zeros((0,), dtype=np.float32)
return np.sqrt(
np.sum(
(self.coords[:-1, :] - self.coords[1:, :]) ** 2,
axis=1
)
)
# TODO change output to array
def compute_pointwise_distances(self, other, default=None):
"""Compute min distances between points of this and another line string.
Parameters
----------
other : tuple of number or imgaug.augmentables.kps.Keypoint or imgaug.augmentables.LineString
Other object to which to compute the distances.
default : any
Value to return if `other` contains no points.
Returns
-------
list of float or any
For each coordinate of this line string, the distance to any
closest location on `other`.
`default` if no distance could be computed.
"""
import shapely.geometry
from .kps import Keypoint
if isinstance(other, Keypoint):
other = shapely.geometry.Point((other.x, other.y))
elif isinstance(other, LineString):
if len(other.coords) == 0:
return default
if len(other.coords) == 1:
other = shapely.geometry.Point(other.coords[0, :])
else:
other = shapely.geometry.LineString(other.coords)
elif isinstance(other, tuple):
assert len(other) == 2, (
"Expected tuple 'other' to contain exactly two entries, "
"got %d." % (len(other),))
other = shapely.geometry.Point(other)
else:
raise ValueError(
"Expected Keypoint or LineString or tuple (x,y), "
"got type %s." % (type(other),))
return [shapely.geometry.Point(point).distance(other)
for point in self.coords]
def compute_distance(self, other, default=None):
"""Compute the minimal distance between the line string and `other`.
Parameters
----------
other : tuple of number or imgaug.augmentables.kps.Keypoint or imgaug.augmentables.LineString
Other object to which to compute the distance.
default : any
Value to return if this line string or `other` contain no points.
Returns
-------
float or any
Minimal distance to `other` or `default` if no distance could be
computed.
"""
# FIXME this computes distance pointwise, does not have to be identical
# with the actual min distance (e.g. edge center to other's point)
distances = self.compute_pointwise_distances(other, default=[])
if len(distances) == 0:
return default
return min(distances)
# TODO update BB's contains(), which can only accept Keypoint currently
def contains(self, other, max_distance=1e-4):
"""Estimate whether a point is on this line string.
This method uses a maximum distance to estimate whether a point is
on a line string.
Parameters
----------
other : tuple of number or imgaug.augmentables.kps.Keypoint
Point to check for.
max_distance : float
Maximum allowed euclidean distance between the point and the
closest point on the line. If the threshold is exceeded, the point
is not considered to fall on the line.
Returns
-------
bool
``True`` if the point is on the line string, ``False`` otherwise.
"""
return self.compute_distance(other, default=np.inf) < max_distance
def project_(self, from_shape, to_shape):
"""Project the line string onto a differently shaped image in-place.
E.g. if a point of the line string is on its original image at
``x=(10 of 100 pixels)`` and ``y=(20 of 100 pixels)`` and is projected
onto a new image with size ``(width=200, height=200)``, its new
position will be ``(x=20, y=40)``.
This is intended for cases where the original image is resized.
It cannot be used for more complex changes (e.g. padding, cropping).
Added in 0.4.0.
Parameters
----------
from_shape : tuple of int or ndarray
Shape of the original image. (Before resize.)
to_shape : tuple of int or ndarray
Shape of the new image. (After resize.)
Returns
-------
imgaug.augmentables.lines.LineString
Line string with new coordinates.
The object may have been modified in-place.
"""
self.coords = project_coords_(self.coords, from_shape, to_shape)
return self
def project(self, from_shape, to_shape):
"""Project the line string onto a differently shaped image.
E.g. if a point of the line string is on its original image at
``x=(10 of 100 pixels)`` and ``y=(20 of 100 pixels)`` and is projected
onto a new image with size ``(width=200, height=200)``, its new
position will be ``(x=20, y=40)``.
This is intended for cases where the original image is resized.
It cannot be used for more complex changes (e.g. padding, cropping).
Parameters
----------
from_shape : tuple of int or ndarray
Shape of the original image. (Before resize.)
to_shape : tuple of int or ndarray
Shape of the new image. (After resize.)
Returns
-------
imgaug.augmentables.lines.LineString
Line string with new coordinates.
"""
return self.deepcopy().project_(from_shape, to_shape)
def compute_out_of_image_fraction(self, image):
"""Compute fraction of polygon area outside of the image plane.
This estimates ``f = A_ooi / A``, where ``A_ooi`` is the area of the
polygon that is outside of the image plane, while ``A`` is the
total area of the bounding box.
Added in 0.4.0.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
float
Fraction of the polygon area that is outside of the image
plane. Returns ``0.0`` if the polygon is fully inside of
the image plane. If the polygon has an area of zero, the polygon
is treated similarly to a :class:`LineString`, i.e. the fraction
of the line that is inside the image plane is returned.
"""
length = self.length
if length == 0:
if len(self.coords) == 0:
return 0.0
points_ooi = ~self.get_pointwise_inside_image_mask(image)
return 1.0 if np.all(points_ooi) else 0.0
lss_clipped = self.clip_out_of_image(image)
length_after_clip = sum([ls.length for ls in lss_clipped])
inside_image_factor = length_after_clip / length
return 1.0 - inside_image_factor
def is_fully_within_image(self, image, default=False):
"""Estimate whether the line string is fully inside an image plane.
Parameters
----------
image : ndarray or tuple of int
Either an image with shape ``(H,W,[C])`` or a ``tuple`` denoting
such an image shape.
default : any
Default value to return if the line string contains no points.
Returns
-------
bool or any
``True`` if the line string is fully inside the image area.
``False`` otherwise.
Will return `default` if this line string contains no points.
"""
if len(self.coords) == 0:
return default
return np.all(self.get_pointwise_inside_image_mask(image))
def is_partly_within_image(self, image, default=False):
"""
Estimate whether the line string is at least partially inside the image.
Parameters
----------
image : ndarray or tuple of int
Either an image with shape ``(H,W,[C])`` or a ``tuple`` denoting
such an image shape.
default : any
Default value to return if the line string contains no points.
Returns
-------
bool or any
``True`` if the line string is at least partially inside the image
area. ``False`` otherwise.
Will return `default` if this line string contains no points.
"""
if len(self.coords) == 0:
return default
# check mask first to avoid costly computation of intersection points
# whenever possible
mask = self.get_pointwise_inside_image_mask(image)
if np.any(mask):
return True
return len(self.clip_out_of_image(image)) > 0
def is_out_of_image(self, image, fully=True, partly=False, default=True):
"""
Estimate whether the line is partially/fully outside of the image area.
Parameters
----------
image : ndarray or tuple of int
Either an image with shape ``(H,W,[C])`` or a tuple denoting
such an image shape.
fully : bool, optional
Whether to return ``True`` if the line string is fully outside
of the image area.
partly : bool, optional
Whether to return ``True`` if the line string is at least partially
outside fo the image area.
default : any
Default value to return if the line string contains no points.
Returns
-------
bool or any
``True`` if the line string is partially/fully outside of the image
area, depending on defined parameters.
``False`` otherwise.
Will return `default` if this line string contains no points.
"""
if len(self.coords) == 0:
return default
if self.is_fully_within_image(image):
return False
if self.is_partly_within_image(image):
return partly
return fully
def clip_out_of_image(self, image):
"""Clip off all parts of the line string that are outside of the image.
Parameters
----------
image : ndarray or tuple of int
Either an image with shape ``(H,W,[C])`` or a ``tuple`` denoting
such an image shape.
Returns
-------
list of imgaug.augmentables.lines.LineString
Line strings, clipped to the image shape.
The result may contain any number of line strins, including zero.
"""
if len(self.coords) == 0:
return []
inside_image_mask = self.get_pointwise_inside_image_mask(image)
ooi_mask = ~inside_image_mask
if len(self.coords) == 1:
if not np.any(inside_image_mask):
return []
return [self.copy()]
if np.all(inside_image_mask):
return [self.copy()]
# top, right, bottom, left image edges
# we subtract eps here, because intersection() works inclusively,
# i.e. not subtracting eps would be equivalent to 0<=x<=C for C being
# height or width
# don't set the eps too low, otherwise points at height/width seem
# to get rounded to height/width by shapely, which can cause problems
# when first clipping and then calling is_fully_within_image()
# returning false
height, width = normalize_imglike_shape(image)[0:2]
eps = 1e-3
edges = [
LineString([(0.0, 0.0), (width - eps, 0.0)]),
LineString([(width - eps, 0.0), (width - eps, height - eps)]),
LineString([(width - eps, height - eps), (0.0, height - eps)]),
LineString([(0.0, height - eps), (0.0, 0.0)])
]
intersections = self.find_intersections_with(edges)
points = []
gen = enumerate(zip(self.coords[:-1], self.coords[1:],
ooi_mask[:-1], ooi_mask[1:],
intersections))
for i, (line_start, line_end, ooi_start, ooi_end, inter_line) in gen:
points.append((line_start, False, ooi_start))
for p_inter in inter_line:
points.append((p_inter, True, False))
is_last = (i == len(self.coords) - 2)
if is_last and not ooi_end:
points.append((line_end, False, ooi_end))
lines = []
line = []
for i, (coord, was_added, ooi) in enumerate(points):
# remove any point that is outside of the image,
# also start a new line once such a point is detected
if ooi:
if len(line) > 0:
lines.append(line)
line = []
continue
if not was_added:
# add all points that were part of the original line string
# AND that are inside the image plane
line.append(coord)
else:
is_last_point = (i == len(points)-1)
# ooi is a numpy.bool_, hence the bool(.)
is_next_ooi = (not is_last_point
and bool(points[i+1][2]) is True)
# Add all points that were new (i.e. intersections), so
# long that they aren't essentially identical to other point.
# This prevents adding overlapping intersections multiple times.
# (E.g. when a line intersects with a corner of the image plane
# and also with one of its edges.)
p_prev = line[-1] if len(line) > 0 else None
# ignore next point if end reached or next point is out of image
p_next = None
if not is_last_point and not is_next_ooi:
p_next = points[i+1][0]
dist_prev = None
dist_next = None
if p_prev is not None:
dist_prev = np.linalg.norm(
np.float32(coord) - np.float32(p_prev))
if p_next is not None:
dist_next = np.linalg.norm(
np.float32(coord) - np.float32(p_next))
dist_prev_ok = (dist_prev is None or dist_prev > 1e-2)
dist_next_ok = (dist_next is None or dist_next > 1e-2)
if dist_prev_ok and dist_next_ok:
line.append(coord)
if len(line) > 0:
lines.append(line)
lines = [line for line in lines if len(line) > 0]
return [self.deepcopy(coords=line) for line in lines]
# TODO add tests for this
# TODO extend this to non line string geometries
def find_intersections_with(self, other):
"""Find all intersection points between this line string and `other`.
Parameters
----------
other : tuple of number or list of tuple of number or list of LineString or LineString
The other geometry to use during intersection tests.
Returns
-------
list of list of tuple of number
All intersection points. One list per pair of consecutive start
and end point, i.e. `N-1` lists of `N` points. Each list may
be empty or may contain multiple points.
"""
import shapely.geometry
geom = _convert_var_to_shapely_geometry(other)
result = []
for p_start, p_end in zip(self.coords[:-1], self.coords[1:]):
ls = shapely.geometry.LineString([p_start, p_end])
intersections = ls.intersection(geom)
intersections = list(_flatten_shapely_collection(intersections))
intersections_points = []
for inter in intersections:
if isinstance(inter, shapely.geometry.linestring.LineString):
# Since shapely 1.7a2 (tested in python 3.8),
# .intersection() apprently can return LINE STRING EMPTY
# (i.e. .coords is an empty list). Before that, the result
# of .intersection() was just []. Hence, we first check
# the length here.
if len(inter.coords) > 0:
inter_start = (inter.coords[0][0], inter.coords[0][1])
inter_end = (inter.coords[-1][0], inter.coords[-1][1])
intersections_points.extend([inter_start, inter_end])
else:
assert isinstance(inter, shapely.geometry.point.Point), (
"Expected to find shapely.geometry.point.Point or "
"shapely.geometry.linestring.LineString intersection, "
"actually found %s." % (type(inter),))
intersections_points.append((inter.x, inter.y))
# sort by distance to start point, this makes it later on easier
# to remove duplicate points
inter_sorted = sorted(
intersections_points,
key=lambda p, ps=p_start: np.linalg.norm(np.float32(p) - ps)
)
result.append(inter_sorted)
return result
def shift_(self, x=0, y=0):
"""Move this line string along the x/y-axis in-place.
The origin ``(0, 0)`` is at the top left of the image.
Added in 0.4.0.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
Returns
-------
result : imgaug.augmentables.lines.LineString
Shifted line string.
The object may have been modified in-place.
"""
self.coords[:, 0] += x
self.coords[:, 1] += y
return self
def shift(self, x=0, y=0, top=None, right=None, bottom=None, left=None):
"""Move this line string along the x/y-axis.
The origin ``(0, 0)`` is at the top left of the image.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
top : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
top (towards the bottom).
right : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
right (towards the left).
bottom : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
bottom (towards the top).
left : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
left (towards the right).
Returns
-------
result : imgaug.augmentables.lines.LineString
Shifted line string.
"""
x, y = _normalize_shift_args(
x, y, top=top, right=right, bottom=bottom, left=left)
return self.deepcopy().shift_(x=x, y=y)
def draw_mask(self, image_shape, size_lines=1, size_points=0,
raise_if_out_of_image=False):
"""Draw this line segment as a binary image mask.
Parameters
----------
image_shape : tuple of int
The shape of the image onto which to draw the line mask.
size_lines : int, optional
Thickness of the line segments.
size_points : int, optional
Size of the points in pixels.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
ndarray
Boolean line mask of shape `image_shape` (no channel axis).
"""
heatmap = self.draw_heatmap_array(
image_shape,
alpha_lines=1.0, alpha_points=1.0,
size_lines=size_lines, size_points=size_points,
antialiased=False,
raise_if_out_of_image=raise_if_out_of_image)
return heatmap > 0.5
def draw_lines_heatmap_array(self, image_shape, alpha=1.0,
size=1, antialiased=True,
raise_if_out_of_image=False):
"""Draw the line segments of this line string as a heatmap array.
Parameters
----------
image_shape : tuple of int
The shape of the image onto which to draw the line mask.
alpha : float, optional
Opacity of the line string. Higher values denote a more visible
line string.
size : int, optional
Thickness of the line segments.
antialiased : bool, optional
Whether to draw the line with anti-aliasing activated.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
ndarray
``float32`` array of shape `image_shape` (no channel axis) with
drawn line string. All values are in the interval ``[0.0, 1.0]``.
"""
assert len(image_shape) == 2 or (
len(image_shape) == 3 and image_shape[-1] == 1), (
"Expected (H,W) or (H,W,1) as image_shape, got %s." % (
image_shape,))
arr = self.draw_lines_on_image(
np.zeros(image_shape, dtype=np.uint8),
color=255, alpha=alpha, size=size,
antialiased=antialiased,
raise_if_out_of_image=raise_if_out_of_image
)
return arr.astype(np.float32) / 255.0
def draw_points_heatmap_array(self, image_shape, alpha=1.0,
size=1, raise_if_out_of_image=False):
"""Draw the points of this line string as a heatmap array.
Parameters
----------
image_shape : tuple of int
The shape of the image onto which to draw the point mask.
alpha : float, optional
Opacity of the line string points. Higher values denote a more
visible points.
size : int, optional
Size of the points in pixels.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
ndarray
``float32`` array of shape `image_shape` (no channel axis) with
drawn line string points. All values are in the
interval ``[0.0, 1.0]``.
"""
assert len(image_shape) == 2 or (
len(image_shape) == 3 and image_shape[-1] == 1), (
"Expected (H,W) or (H,W,1) as image_shape, got %s." % (
image_shape,))
arr = self.draw_points_on_image(
np.zeros(image_shape, dtype=np.uint8),
color=255, alpha=alpha, size=size,
raise_if_out_of_image=raise_if_out_of_image
)
return arr.astype(np.float32) / 255.0
def draw_heatmap_array(self, image_shape, alpha_lines=1.0, alpha_points=1.0,
size_lines=1, size_points=0, antialiased=True,
raise_if_out_of_image=False):
"""
Draw the line segments and points of the line string as a heatmap array.
Parameters
----------
image_shape : tuple of int
The shape of the image onto which to draw the line mask.
alpha_lines : float, optional
Opacity of the line string. Higher values denote a more visible
line string.
alpha_points : float, optional
Opacity of the line string points. Higher values denote a more
visible points.
size_lines : int, optional
Thickness of the line segments.
size_points : int, optional
Size of the points in pixels.
antialiased : bool, optional
Whether to draw the line with anti-aliasing activated.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
ndarray
``float32`` array of shape `image_shape` (no channel axis) with
drawn line segments and points. All values are in the
interval ``[0.0, 1.0]``.
"""
heatmap_lines = self.draw_lines_heatmap_array(
image_shape,
alpha=alpha_lines,
size=size_lines,
antialiased=antialiased,
raise_if_out_of_image=raise_if_out_of_image)
if size_points <= 0:
return heatmap_lines
heatmap_points = self.draw_points_heatmap_array(
image_shape,
alpha=alpha_points,
size=size_points,
raise_if_out_of_image=raise_if_out_of_image)
heatmap = np.dstack([heatmap_lines, heatmap_points])
return np.max(heatmap, axis=2)
# TODO only draw line on image of size BB around line, then paste into full
# sized image
def draw_lines_on_image(self, image, color=(0, 255, 0),
alpha=1.0, size=3,
antialiased=True,
raise_if_out_of_image=False):
"""Draw the line segments of this line string on a given image.
Parameters
----------
image : ndarray or tuple of int
The image onto which to draw.
Expected to be ``uint8`` and of shape ``(H, W, C)`` with ``C``
usually being ``3`` (other values are not tested).
If a tuple, expected to be ``(H, W, C)`` and will lead to a new
``uint8`` array of zeros being created.
color : int or iterable of int
Color to use as RGB, i.e. three values.
alpha : float, optional
Opacity of the line string. Higher values denote a more visible
line string.
size : int, optional
Thickness of the line segments.
antialiased : bool, optional
Whether to draw the line with anti-aliasing activated.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
ndarray
`image` with line drawn on it.
"""
# pylint: disable=invalid-name, misplaced-comparison-constant
from .. import dtypes as iadt
from ..augmenters import blend as blendlib
image_was_empty = False
if isinstance(image, tuple):
image_was_empty = True
image = np.zeros(image, dtype=np.uint8)
assert image.ndim in [2, 3], (
"Expected image or shape of form (H,W) or (H,W,C), "
"got shape %s." % (image.shape,))
if len(self.coords) <= 1 or alpha < 0 + 1e-4 or size < 1:
return np.copy(image)
if raise_if_out_of_image \
and self.is_out_of_image(image, partly=False, fully=True):
raise Exception(
"Cannot draw line string '%s' on image with shape %s, because "
"it would be out of bounds." % (
self.__str__(), image.shape))
if image.ndim == 2:
assert ia.is_single_number(color), (
"Got a 2D image. Expected then 'color' to be a single number, "
"but got %s." % (str(color),))
color = [color]
elif image.ndim == 3 and ia.is_single_number(color):
color = [color] * image.shape[-1]
image = image.astype(np.float32)
height, width = image.shape[0:2]
# We can't trivially exclude lines outside of the image here, because
# even if start and end point are outside, there can still be parts of
# the line inside the image.
# TODO Do this with edge-wise intersection tests
lines = []
for line_start, line_end in zip(self.coords[:-1], self.coords[1:]):
# note that line() expects order (y1, x1, y2, x2), hence ([1], [0])
lines.append((line_start[1], line_start[0],
line_end[1], line_end[0]))
# skimage.draw.line can only handle integers
lines = np.round(np.float32(lines)).astype(np.int32)
# size == 0 is already covered above
# Note here that we have to be careful not to draw lines two times
# at their intersection points, e.g. for (p0, p1), (p1, 2) we could
# end up drawing at p1 twice, leading to higher values if alpha is
# used.
color = np.float32(color)
heatmap = np.zeros(image.shape[0:2], dtype=np.float32)
for line in lines:
if antialiased:
rr, cc, val = skimage.draw.line_aa(*line)
else:
rr, cc = skimage.draw.line(*line)
val = 1.0
# mask check here, because line() can generate coordinates
# outside of the image plane
rr_mask = np.logical_and(0 <= rr, rr < height)
cc_mask = np.logical_and(0 <= cc, cc < width)
mask = np.logical_and(rr_mask, cc_mask)
if np.any(mask):
rr = rr[mask]
cc = cc[mask]
val = val[mask] if not ia.is_single_number(val) else val
heatmap[rr, cc] = val * alpha
if size > 1:
kernel = np.ones((size, size), dtype=np.uint8)
heatmap = cv2.dilate(heatmap, kernel)
if image_was_empty:
image_blend = image + heatmap * color
else:
image_color_shape = image.shape[0:2]
if image.ndim == 3:
image_color_shape = image_color_shape + (1,)
image_color = np.tile(color, image_color_shape)
image_blend = blendlib.blend_alpha(image_color, image, heatmap)
image_blend = iadt.restore_dtypes_(image_blend, np.uint8)
return image_blend
def draw_points_on_image(self, image, color=(0, 128, 0),
alpha=1.0, size=3,
copy=True, raise_if_out_of_image=False):
"""Draw the points of this line string onto a given image.
Parameters
----------
image : ndarray or tuple of int
The image onto which to draw.
Expected to be ``uint8`` and of shape ``(H, W, C)`` with ``C``
usually being ``3`` (other values are not tested).
If a tuple, expected to be ``(H, W, C)`` and will lead to a new
``uint8`` array of zeros being created.
color : iterable of int
Color to use as RGB, i.e. three values.
alpha : float, optional
Opacity of the line string points. Higher values denote a more
visible points.
size : int, optional
Size of the points in pixels.
copy : bool, optional
Whether it is allowed to draw directly in the input
array (``False``) or it has to be copied (``True``).
The routine may still have to copy, even if ``copy=False`` was
used. Always use the return value.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
ndarray
``float32`` array of shape `image_shape` (no channel axis) with
drawn line string points. All values are in the
interval ``[0.0, 1.0]``.
"""
from .kps import KeypointsOnImage
kpsoi = KeypointsOnImage.from_xy_array(self.coords, shape=image.shape)
image = kpsoi.draw_on_image(
image, color=color, alpha=alpha,
size=size, copy=copy,
raise_if_out_of_image=raise_if_out_of_image)
return image
def draw_on_image(self, image,
color=(0, 255, 0), color_lines=None, color_points=None,
alpha=1.0, alpha_lines=None, alpha_points=None,
size=1, size_lines=None, size_points=None,
antialiased=True,
raise_if_out_of_image=False):
"""Draw this line string onto an image.
Parameters
----------
image : ndarray
The `(H,W,C)` `uint8` image onto which to draw the line string.
color : iterable of int, optional
Color to use as RGB, i.e. three values.
The color of the line and points are derived from this value,
unless they are set.
color_lines : None or iterable of int
Color to use for the line segments as RGB, i.e. three values.
If ``None``, this value is derived from `color`.
color_points : None or iterable of int
Color to use for the points as RGB, i.e. three values.
If ``None``, this value is derived from ``0.5 * color``.
alpha : float, optional
Opacity of the line string. Higher values denote more visible
points.
The alphas of the line and points are derived from this value,
unless they are set.
alpha_lines : None or float, optional
Opacity of the line string. Higher values denote more visible
line string.
If ``None``, this value is derived from `alpha`.
alpha_points : None or float, optional
Opacity of the line string points. Higher values denote more
visible points.
If ``None``, this value is derived from `alpha`.
size : int, optional
Size of the line string.
The sizes of the line and points are derived from this value,
unless they are set.
size_lines : None or int, optional
Thickness of the line segments.
If ``None``, this value is derived from `size`.
size_points : None or int, optional
Size of the points in pixels.
If ``None``, this value is derived from ``3 * size``.
antialiased : bool, optional
Whether to draw the line with anti-aliasing activated.
This does currently not affect the point drawing.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
ndarray
Image with line string drawn on it.
"""
def _assert_not_none(arg_name, arg_value):
assert arg_value is not None, (
"Expected '%s' to not be None, got type %s." % (
arg_name, type(arg_value),))
_assert_not_none("color", color)
_assert_not_none("alpha", alpha)
_assert_not_none("size", size)
color_lines = color_lines if color_lines is not None \
else np.float32(color)
color_points = color_points if color_points is not None \
else np.float32(color) * 0.5
alpha_lines = alpha_lines if alpha_lines is not None \
else np.float32(alpha)
alpha_points = alpha_points if alpha_points is not None \
else np.float32(alpha)
size_lines = size_lines if size_lines is not None else size
size_points = size_points if size_points is not None else size * 3
image = self.draw_lines_on_image(
image, color=np.array(color_lines).astype(np.uint8),
alpha=alpha_lines, size=size_lines,
antialiased=antialiased,
raise_if_out_of_image=raise_if_out_of_image)
image = self.draw_points_on_image(
image, color=np.array(color_points).astype(np.uint8),
alpha=alpha_points, size=size_points,
copy=False,
raise_if_out_of_image=raise_if_out_of_image)
return image
def extract_from_image(self, image, size=1, pad=True, pad_max=None,
antialiased=True, prevent_zero_size=True):
"""Extract all image pixels covered by the line string.
This will only extract pixels overlapping with the line string.
As a rectangular image array has to be returned, non-overlapping
pixels will be set to zero.
This function will by default zero-pad the image if the line string is
partially/fully outside of the image. This is for consistency with
the same methods for bounding boxes and polygons.
Parameters
----------
image : ndarray
The image of shape `(H,W,[C])` from which to extract the pixels
within the line string.
size : int, optional
Thickness of the line.
pad : bool, optional
Whether to zero-pad the image if the object is partially/fully
outside of it.
pad_max : None or int, optional
The maximum number of pixels that may be zero-paded on any side,
i.e. if this has value ``N`` the total maximum of added pixels
is ``4*N``.
This option exists to prevent extremely large images as a result of
single points being moved very far away during augmentation.
antialiased : bool, optional
Whether to apply anti-aliasing to the line string.
prevent_zero_size : bool, optional
Whether to prevent height or width of the extracted image from
becoming zero. If this is set to ``True`` and height or width of
the line string is below ``1``, the height/width will be increased
to ``1``. This can be useful to prevent problems, e.g. with image
saving or plotting. If it is set to ``False``, images will be
returned as ``(H', W')`` or ``(H', W', 3)`` with ``H`` or ``W``
potentially being ``0``.
Returns
-------
(H',W') ndarray or (H',W',C) ndarray
Pixels overlapping with the line string. Zero-padded if the
line string is partially/fully outside of the image and
``pad=True``. If `prevent_zero_size` is activated, it is
guarantueed that ``H'>0`` and ``W'>0``, otherwise only
``H'>=0`` and ``W'>=0``.
"""
from .bbs import BoundingBox
assert image.ndim in [2, 3], (
"Expected image of shape (H,W,[C]), got shape %s." % (
image.shape,))
if len(self.coords) == 0 or size <= 0:
if prevent_zero_size:
return np.zeros((1, 1) + image.shape[2:], dtype=image.dtype)
return np.zeros((0, 0) + image.shape[2:], dtype=image.dtype)
xx = self.xx_int
yy = self.yy_int
# this would probably work if drawing was subpixel-accurate
# x1 = np.min(self.coords[:, 0]) - (size / 2)
# y1 = np.min(self.coords[:, 1]) - (size / 2)
# x2 = np.max(self.coords[:, 0]) + (size / 2)
# y2 = np.max(self.coords[:, 1]) + (size / 2)
# this works currently with non-subpixel-accurate drawing
sizeh = (size - 1) / 2
x1 = np.min(xx) - sizeh
y1 = np.min(yy) - sizeh
x2 = np.max(xx) + 1 + sizeh
y2 = np.max(yy) + 1 + sizeh
bb = BoundingBox(x1=x1, y1=y1, x2=x2, y2=y2)
if len(self.coords) == 1:
return bb.extract_from_image(image, pad=pad, pad_max=pad_max,
prevent_zero_size=prevent_zero_size)
heatmap = self.draw_lines_heatmap_array(
image.shape[0:2], alpha=1.0, size=size, antialiased=antialiased)
if image.ndim == 3:
heatmap = np.atleast_3d(heatmap)
image_masked = image.astype(np.float32) * heatmap
extract = bb.extract_from_image(image_masked, pad=pad, pad_max=pad_max,
prevent_zero_size=prevent_zero_size)
return np.clip(np.round(extract), 0, 255).astype(np.uint8)
def concatenate(self, other):
"""Concatenate this line string with another one.
This will add a line segment between the end point of this line string
and the start point of `other`.
Parameters
----------
other : imgaug.augmentables.lines.LineString or ndarray or iterable of tuple of number
The points to add to this line string.
Returns
-------
imgaug.augmentables.lines.LineString
New line string with concatenated points.
The `label` of this line string will be kept.
"""
if not isinstance(other, LineString):
other = LineString(other)
return self.deepcopy(
coords=np.concatenate([self.coords, other.coords], axis=0))
# TODO add tests
def subdivide(self, points_per_edge):
"""Derive a new line string with ``N`` interpolated points per edge.
The interpolated points have (per edge) regular distances to each
other.
For each edge between points ``A`` and ``B`` this adds points
at ``A + (i/(1+N)) * (B - A)``, where ``i`` is the index of the added
point and ``N`` is the number of points to add per edge.
Calling this method two times will split each edge at its center
and then again split each newly created edge at their center.
It is equivalent to calling `subdivide(3)`.
Parameters
----------
points_per_edge : int
Number of points to interpolate on each edge.
Returns
-------
imgaug.augmentables.lines.LineString
Line string with subdivided edges.
"""
if len(self.coords) <= 1 or points_per_edge < 1:
return self.deepcopy()
coords = interpolate_points(self.coords, nb_steps=points_per_edge,
closed=False)
return self.deepcopy(coords=coords)
def to_keypoints(self):
"""Convert the line string points to keypoints.
Returns
-------
list of imgaug.augmentables.kps.Keypoint
Points of the line string as keypoints.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.kps import Keypoint
return [Keypoint(x=x, y=y) for (x, y) in self.coords]
def to_bounding_box(self):
"""Generate a bounding box encapsulating the line string.
Returns
-------
None or imgaug.augmentables.bbs.BoundingBox
Bounding box encapsulating the line string.
``None`` if the line string contained no points.
"""
from .bbs import BoundingBox
# we don't have to mind the case of len(.) == 1 here, because
# zero-sized BBs are considered valid
if len(self.coords) == 0:
return None
return BoundingBox(x1=np.min(self.xx), y1=np.min(self.yy),
x2=np.max(self.xx), y2=np.max(self.yy),
label=self.label)
def to_polygon(self):
"""Generate a polygon from the line string points.
Returns
-------
imgaug.augmentables.polys.Polygon
Polygon with the same corner points as the line string.
Note that the polygon might be invalid, e.g. contain less
than ``3`` points or have self-intersections.
"""
from .polys import Polygon
return Polygon(self.coords, label=self.label)
def to_heatmap(self, image_shape, size_lines=1, size_points=0,
antialiased=True, raise_if_out_of_image=False):
"""Generate a heatmap object from the line string.
This is similar to
:func:`~imgaug.augmentables.lines.LineString.draw_lines_heatmap_array`,
executed with ``alpha=1.0``. The result is wrapped in a
:class:`~imgaug.augmentables.heatmaps.HeatmapsOnImage` object instead
of just an array. No points are drawn.
Parameters
----------
image_shape : tuple of int
The shape of the image onto which to draw the line mask.
size_lines : int, optional
Thickness of the line.
size_points : int, optional
Size of the points in pixels.
antialiased : bool, optional
Whether to draw the line with anti-aliasing activated.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Heatmap object containing drawn line string.
"""
from .heatmaps import HeatmapsOnImage
return HeatmapsOnImage(
self.draw_heatmap_array(
image_shape, size_lines=size_lines, size_points=size_points,
antialiased=antialiased,
raise_if_out_of_image=raise_if_out_of_image),
shape=image_shape
)
def to_segmentation_map(self, image_shape, size_lines=1, size_points=0,
raise_if_out_of_image=False):
"""Generate a segmentation map object from the line string.
This is similar to
:func:`~imgaug.augmentables.lines.LineString.draw_mask`.
The result is wrapped in a ``SegmentationMapsOnImage`` object
instead of just an array.
Parameters
----------
image_shape : tuple of int
The shape of the image onto which to draw the line mask.
size_lines : int, optional
Thickness of the line.
size_points : int, optional
Size of the points in pixels.
raise_if_out_of_image : bool, optional
Whether to raise an error if the line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
imgaug.augmentables.segmaps.SegmentationMapsOnImage
Segmentation map object containing drawn line string.
"""
from .segmaps import SegmentationMapsOnImage
return SegmentationMapsOnImage(
self.draw_mask(
image_shape, size_lines=size_lines, size_points=size_points,
raise_if_out_of_image=raise_if_out_of_image),
shape=image_shape
)
# TODO make this non-approximate
def coords_almost_equals(self, other, max_distance=1e-4, points_per_edge=8):
"""Compare this and another LineString's coordinates.
This is an approximate method based on pointwise distances and can
in rare corner cases produce wrong outputs.
Parameters
----------
other : imgaug.augmentables.lines.LineString or tuple of number or ndarray or list of ndarray or list of tuple of number
The other line string or its coordinates.
max_distance : float, optional
Max distance of any point from the other line string before
the two line strings are evaluated to be unequal.
points_per_edge : int, optional
How many points to interpolate on each edge.
Returns
-------
bool
Whether the two LineString's coordinates are almost identical,
i.e. the max distance is below the threshold.
If both have no coordinates, ``True`` is returned.
If only one has no coordinates, ``False`` is returned.
Beyond that, the number of points is not evaluated.
"""
if isinstance(other, LineString):
pass
elif isinstance(other, tuple):
other = LineString([other])
else:
other = LineString(other)
if len(self.coords) == 0 and len(other.coords) == 0:
return True
if 0 in [len(self.coords), len(other.coords)]:
# only one of the two line strings has no coords
return False
self_subd = self.subdivide(points_per_edge)
other_subd = other.subdivide(points_per_edge)
dist_self2other = self_subd.compute_pointwise_distances(other_subd)
dist_other2self = other_subd.compute_pointwise_distances(self_subd)
dist = max(np.max(dist_self2other), np.max(dist_other2self))
return dist < max_distance
def almost_equals(self, other, max_distance=1e-4, points_per_edge=8):
"""Compare this and another line string.
Parameters
----------
other: imgaug.augmentables.lines.LineString
The other object to compare against. Expected to be a
``LineString``.
max_distance : float, optional
See :func:`~imgaug.augmentables.lines.LineString.coords_almost_equals`.
points_per_edge : int, optional
See :func:`~imgaug.augmentables.lines.LineString.coords_almost_equals`.
Returns
-------
bool
``True`` if the coordinates are almost equal and additionally
the labels are equal. Otherwise ``False``.
"""
if self.label != other.label:
return False
return self.coords_almost_equals(
other, max_distance=max_distance, points_per_edge=points_per_edge)
def copy(self, coords=None, label=None):
"""Create a shallow copy of this line string.
Parameters
----------
coords : None or iterable of tuple of number or ndarray
If not ``None``, then the coords of the copied object will be set
to this value.
label : None or str
If not ``None``, then the label of the copied object will be set to
this value.
Returns
-------
imgaug.augmentables.lines.LineString
Shallow copy.
"""
return LineString(coords=self.coords if coords is None else coords,
label=self.label if label is None else label)
def deepcopy(self, coords=None, label=None):
"""Create a deep copy of this line string.
Parameters
----------
coords : None or iterable of tuple of number or ndarray
If not ``None``, then the coords of the copied object will be set
to this value.
label : None or str
If not ``None``, then the label of the copied object will be set to
this value.
Returns
-------
imgaug.augmentables.lines.LineString
Deep copy.
"""
return LineString(
coords=np.copy(self.coords) if coords is None else coords,
label=copylib.deepcopy(self.label) if label is None else label)
def __getitem__(self, indices):
"""Get the coordinate(s) with given indices.
Added in 0.4.0.
Returns
-------
ndarray
xy-coordinate(s) as ``ndarray``.
"""
return self.coords[indices]
def __iter__(self):
"""Iterate over the coordinates of this instance.
Added in 0.4.0.
Yields
------
ndarray
An ``(2,)`` ``ndarray`` denoting an xy-coordinate pair.
"""
return iter(self.coords)
def __repr__(self):
return self.__str__()
def __str__(self):
points_str = ", ".join(
["(%.2f, %.2f)" % (x, y) for x, y in self.coords])
return "LineString([%s], label=%s)" % (points_str, self.label)
# TODO
# distance
# hausdorff_distance
# is_fully_within_image()
# is_partly_within_image()
# is_out_of_image()
# draw()
# draw_mask()
# extract_from_image()
# to_keypoints()
# intersects(other)
# concat(other)
# is_self_intersecting()
# remove_self_intersections()
class LineStringsOnImage(IAugmentable):
"""Object that represents all line strings on a single image.
Parameters
----------
line_strings : list of imgaug.augmentables.lines.LineString
List of line strings on the image.
shape : tuple of int or ndarray
The shape of the image on which the objects are placed.
Either an image with shape ``(H,W,[C])`` or a ``tuple`` denoting
such an image shape.
Examples
--------
>>> import numpy as np
>>> from imgaug.augmentables.lines import LineString, LineStringsOnImage
>>>
>>> image = np.zeros((100, 100))
>>> lss = [
>>> LineString([(0, 0), (10, 0)]),
>>> LineString([(10, 20), (30, 30), (50, 70)])
>>> ]
>>> lsoi = LineStringsOnImage(lss, shape=image.shape)
"""
def __init__(self, line_strings, shape):
assert ia.is_iterable(line_strings), (
"Expected 'line_strings' to be an iterable, got type '%s'." % (
type(line_strings),))
assert all([isinstance(v, LineString) for v in line_strings]), (
"Expected iterable of LineString, got types: %s." % (
", ".join([str(type(v)) for v in line_strings])
))
self.line_strings = line_strings
self.shape = _handle_on_image_shape(shape, self)
@property
def items(self):
"""Get the line strings in this container.
Added in 0.4.0.
Returns
-------
list of LineString
Line strings within this container.
"""
return self.line_strings
@items.setter
def items(self, value):
"""Set the line strings in this container.
Added in 0.4.0.
Parameters
----------
value : list of LineString
Line strings within this container.
"""
self.line_strings = value
@property
def empty(self):
"""Estimate whether this object contains zero line strings.
Returns
-------
bool
``True`` if this object contains zero line strings.
"""
return len(self.line_strings) == 0
def on_(self, image):
"""Project the line strings from one image shape to a new one in-place.
Added in 0.4.0.
Parameters
----------
image : ndarray or tuple of int
The new image onto which to project.
Either an image with shape ``(H,W,[C])`` or a tuple denoting
such an image shape.
Returns
-------
imgaug.augmentables.lines.LineStrings
Object containing all projected line strings.
The object and its items may have been modified in-place.
"""
# pylint: disable=invalid-name
on_shape = normalize_imglike_shape(image)
if on_shape[0:2] == self.shape[0:2]:
self.shape = on_shape # channels may differ
return self
for i, item in enumerate(self.items):
self.line_strings[i] = item.project_(self.shape, on_shape)
self.shape = on_shape
return self
def on(self, image):
"""Project the line strings from one image shape to a new one.
Parameters
----------
image : ndarray or tuple of int
The new image onto which to project.
Either an image with shape ``(H,W,[C])`` or a tuple denoting
such an image shape.
Returns
-------
imgaug.augmentables.lines.LineStrings
Object containing all projected line strings.
"""
# pylint: disable=invalid-name
return self.deepcopy().on_(image)
@classmethod
def from_xy_arrays(cls, xy, shape):
"""Convert an ``(N,M,2)`` ndarray to a ``LineStringsOnImage`` object.
This is the inverse of
:func:`~imgaug.augmentables.lines.LineStringsOnImage.to_xy_array`.
Parameters
----------
xy : (N,M,2) ndarray or iterable of (M,2) ndarray
Array containing the point coordinates ``N`` line strings
with each ``M`` points given as ``(x,y)`` coordinates.
``M`` may differ if an iterable of arrays is used.
Each array should usually be of dtype ``float32``.
shape : tuple of int
``(H,W,[C])`` shape of the image on which the line strings are
placed.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Object containing a list of ``LineString`` objects following the
provided point coordinates.
"""
lss = []
for xy_ls in xy:
lss.append(LineString(xy_ls))
return cls(lss, shape)
def to_xy_arrays(self, dtype=np.float32):
"""Convert this object to an iterable of ``(M,2)`` arrays of points.
This is the inverse of
:func:`~imgaug.augmentables.lines.LineStringsOnImage.from_xy_array`.
Parameters
----------
dtype : numpy.dtype, optional
Desired output datatype of the ndarray.
Returns
-------
list of ndarray
The arrays of point coordinates, each given as ``(M,2)``.
"""
from .. import dtypes as iadt
return [iadt.restore_dtypes_(np.copy(ls.coords), dtype)
for ls in self.line_strings]
def draw_on_image(self, image,
color=(0, 255, 0), color_lines=None, color_points=None,
alpha=1.0, alpha_lines=None, alpha_points=None,
size=1, size_lines=None, size_points=None,
antialiased=True,
raise_if_out_of_image=False):
"""Draw all line strings onto a given image.
Parameters
----------
image : ndarray
The ``(H,W,C)`` ``uint8`` image onto which to draw the line
strings.
color : iterable of int, optional
Color to use as RGB, i.e. three values.
The color of the lines and points are derived from this value,
unless they are set.
color_lines : None or iterable of int
Color to use for the line segments as RGB, i.e. three values.
If ``None``, this value is derived from `color`.
color_points : None or iterable of int
Color to use for the points as RGB, i.e. three values.
If ``None``, this value is derived from ``0.5 * color``.
alpha : float, optional
Opacity of the line strings. Higher values denote more visible
points.
The alphas of the line and points are derived from this value,
unless they are set.
alpha_lines : None or float, optional
Opacity of the line strings. Higher values denote more visible
line string.
If ``None``, this value is derived from `alpha`.
alpha_points : None or float, optional
Opacity of the line string points. Higher values denote more
visible points.
If ``None``, this value is derived from `alpha`.
size : int, optional
Size of the line strings.
The sizes of the line and points are derived from this value,
unless they are set.
size_lines : None or int, optional
Thickness of the line segments.
If ``None``, this value is derived from `size`.
size_points : None or int, optional
Size of the points in pixels.
If ``None``, this value is derived from ``3 * size``.
antialiased : bool, optional
Whether to draw the lines with anti-aliasing activated.
This does currently not affect the point drawing.
raise_if_out_of_image : bool, optional
Whether to raise an error if a line string is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
ndarray
Image with line strings drawn on it.
"""
# TODO improve efficiency here by copying only once
for ls in self.line_strings:
image = ls.draw_on_image(
image,
color=color, color_lines=color_lines, color_points=color_points,
alpha=alpha, alpha_lines=alpha_lines, alpha_points=alpha_points,
size=size, size_lines=size_lines, size_points=size_points,
antialiased=antialiased,
raise_if_out_of_image=raise_if_out_of_image
)
return image
def remove_out_of_image_(self, fully=True, partly=False):
"""
Remove all LS that are fully/partially outside of an image in-place.
Added in 0.4.0.
Parameters
----------
fully : bool, optional
Whether to remove line strings that are fully outside of the image.
partly : bool, optional
Whether to remove line strings that are partially outside of the
image.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Reduced set of line strings. Those that are fully/partially
outside of the given image plane are removed.
The object and its items may have been modified in-place.
"""
self.line_strings = [
ls for ls in self.line_strings
if not ls.is_out_of_image(self.shape, fully=fully, partly=partly)]
return self
def remove_out_of_image(self, fully=True, partly=False):
"""
Remove all line strings that are fully/partially outside of an image.
Parameters
----------
fully : bool, optional
Whether to remove line strings that are fully outside of the image.
partly : bool, optional
Whether to remove line strings that are partially outside of the
image.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Reduced set of line strings. Those that are fully/partially
outside of the given image plane are removed.
"""
return self.copy().remove_out_of_image_(fully=fully, partly=partly)
def remove_out_of_image_fraction_(self, fraction):
"""Remove all LS with an OOI fraction of at least `fraction` in-place.
'OOI' is the abbreviation for 'out of image'.
Added in 0.4.0.
Parameters
----------
fraction : number
Minimum out of image fraction that a line string has to have in
order to be removed. A fraction of ``1.0`` removes only line
strings that are ``100%`` outside of the image. A fraction of
``0.0`` removes all line strings.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Reduced set of line strings, with those that had an out of image
fraction greater or equal the given one removed.
The object and its items may have been modified in-place.
"""
return _remove_out_of_image_fraction_(self, fraction)
def remove_out_of_image_fraction(self, fraction):
"""Remove all LS with an out of image fraction of at least `fraction`.
Parameters
----------
fraction : number
Minimum out of image fraction that a line string has to have in
order to be removed. A fraction of ``1.0`` removes only line
strings that are ``100%`` outside of the image. A fraction of
``0.0`` removes all line strings.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Reduced set of line strings, with those that had an out of image
fraction greater or equal the given one removed.
"""
return self.copy().remove_out_of_image_fraction_(fraction)
def clip_out_of_image_(self):
"""
Clip off all parts of the LSs that are outside of an image in-place.
.. note::
The result can contain fewer line strings than the input did. That
happens when a polygon is fully outside of the image plane.
.. note::
The result can also contain *more* line strings than the input
did. That happens when distinct parts of a line string are only
connected by line segments that are outside of the image plane and
hence will be clipped off, resulting in two or more unconnected
line string parts that are left in the image plane.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Line strings, clipped to fall within the image dimensions.
The count of output line strings may differ from the input count.
"""
self.line_strings = [
ls_clipped
for ls in self.line_strings
for ls_clipped in ls.clip_out_of_image(self.shape)]
return self
def clip_out_of_image(self):
"""
Clip off all parts of the line strings that are outside of an image.
.. note::
The result can contain fewer line strings than the input did. That
happens when a polygon is fully outside of the image plane.
.. note::
The result can also contain *more* line strings than the input
did. That happens when distinct parts of a line string are only
connected by line segments that are outside of the image plane and
hence will be clipped off, resulting in two or more unconnected
line string parts that are left in the image plane.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Line strings, clipped to fall within the image dimensions.
The count of output line strings may differ from the input count.
"""
return self.copy().clip_out_of_image_()
def shift_(self, x=0, y=0):
"""Move the line strings along the x/y-axis in-place.
The origin ``(0, 0)`` is at the top left of the image.
Added in 0.4.0.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Shifted line strings.
The object and its items may have been modified in-place.
"""
for i, ls in enumerate(self.line_strings):
self.line_strings[i] = ls.shift_(x=x, y=y)
return self
def shift(self, x=0, y=0, top=None, right=None, bottom=None, left=None):
"""Move the line strings along the x/y-axis.
The origin ``(0, 0)`` is at the top left of the image.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
top : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
top (towards the bottom).
right : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
right (towads the left).
bottom : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
bottom (towards the top).
left : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
left (towards the right).
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Shifted line strings.
"""
x, y = _normalize_shift_args(
x, y, top=top, right=right, bottom=bottom, left=left)
return self.deepcopy().shift_(x=x, y=y)
def to_xy_array(self):
"""Convert all line string coordinates to one array of shape ``(N,2)``.
Added in 0.4.0.
Returns
-------
(N, 2) ndarray
Array containing all xy-coordinates of all line strings within this
instance.
"""
if self.empty:
return np.zeros((0, 2), dtype=np.float32)
return np.concatenate([ls.coords for ls in self.line_strings])
def fill_from_xy_array_(self, xy):
"""Modify the corner coordinates of all line strings in-place.
.. note::
This currently expects that `xy` contains exactly as many
coordinates as the line strings within this instance have corner
points. Otherwise, an ``AssertionError`` will be raised.
Added in 0.4.0.
Parameters
----------
xy : (N, 2) ndarray or iterable of iterable of number
XY-Coordinates of ``N`` corner points. ``N`` must match the
number of corner points in all line strings within this instance.
Returns
-------
LineStringsOnImage
This instance itself, with updated coordinates.
Note that the instance was modified in-place.
"""
xy = np.array(xy, dtype=np.float32)
# note that np.array([]) is (0,), not (0, 2)
assert xy.shape[0] == 0 or (xy.ndim == 2 and xy.shape[-1] == 2), ( # pylint: disable=unsubscriptable-object
"Expected input array to have shape (N,2), "
"got shape %s." % (xy.shape,))
counter = 0
for ls in self.line_strings:
nb_points = len(ls.coords)
assert counter + nb_points <= len(xy), (
"Received fewer points than there are corner points in "
"all line strings. Got %d points, expected %d." % (
len(xy),
sum([len(ls_.coords) for ls_ in self.line_strings])))
ls.coords[:, ...] = xy[counter:counter+nb_points]
counter += nb_points
assert counter == len(xy), (
"Expected to get exactly as many xy-coordinates as there are "
"points in all line strings polygons within this instance. "
"Got %d points, could only assign %d points." % (
len(xy), counter,))
return self
def to_keypoints_on_image(self):
"""Convert the line strings to one ``KeypointsOnImage`` instance.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
A keypoints instance containing ``N`` coordinates for a total
of ``N`` points in the ``coords`` attributes of all line strings.
Order matches the order in ``line_strings`` and ``coords``
attributes.
"""
from . import KeypointsOnImage
if self.empty:
return KeypointsOnImage([], shape=self.shape)
coords = np.concatenate(
[ls.coords for ls in self.line_strings],
axis=0)
return KeypointsOnImage.from_xy_array(coords,
shape=self.shape)
def invert_to_keypoints_on_image_(self, kpsoi):
"""Invert the output of ``to_keypoints_on_image()`` in-place.
This function writes in-place into this ``LineStringsOnImage``
instance.
Added in 0.4.0.
Parameters
----------
kpsoi : imgaug.augmentables.kps.KeypointsOnImages
Keypoints to convert back to line strings, i.e. the outputs
of ``to_keypoints_on_image()``.
Returns
-------
LineStringsOnImage
Line strings container with updated coordinates.
Note that the instance is also updated in-place.
"""
lss = self.line_strings
coordss = [ls.coords for ls in lss]
nb_points_exp = sum([len(coords) for coords in coordss])
assert len(kpsoi.keypoints) == nb_points_exp, (
"Expected %d coordinates, got %d." % (
nb_points_exp, len(kpsoi.keypoints)))
xy_arr = kpsoi.to_xy_array()
counter = 0
for ls in lss:
coords = ls.coords
coords[:, :] = xy_arr[counter:counter+len(coords), :]
counter += len(coords)
self.shape = kpsoi.shape
return self
def copy(self, line_strings=None, shape=None):
"""Create a shallow copy of this object.
Parameters
----------
line_strings : None or list of imgaug.augmentables.lines.LineString, optional
List of line strings on the image.
If not ``None``, then the ``line_strings`` attribute of the copied
object will be set to this value.
shape : None or tuple of int or ndarray, optional
The shape of the image on which the objects are placed.
Either an image with shape ``(H,W,[C])`` or a tuple denoting
such an image shape.
If not ``None``, then the ``shape`` attribute of the copied object
will be set to this value.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Shallow copy.
"""
if line_strings is None:
line_strings = self.line_strings[:]
if shape is None:
# use tuple() here in case the shape was provided as a list
shape = tuple(self.shape)
return LineStringsOnImage(line_strings, shape)
def deepcopy(self, line_strings=None, shape=None):
"""Create a deep copy of the object.
Parameters
----------
line_strings : None or list of imgaug.augmentables.lines.LineString, optional
List of line strings on the image.
If not ``None``, then the ``line_strings`` attribute of the copied
object will be set to this value.
shape : None or tuple of int or ndarray, optional
The shape of the image on which the objects are placed.
Either an image with shape ``(H,W,[C])`` or a tuple denoting
such an image shape.
If not ``None``, then the ``shape`` attribute of the copied object
will be set to this value.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage
Deep copy.
"""
# Manual copy is far faster than deepcopy, so use manual copy here.
if line_strings is None:
line_strings = [ls.deepcopy() for ls in self.line_strings]
if shape is None:
# use tuple() here in case the shape was provided as a list
shape = tuple(self.shape)
return LineStringsOnImage(line_strings, shape)
def __getitem__(self, indices):
"""Get the line string(s) with given indices.
Added in 0.4.0.
Returns
-------
list of imgaug.augmentables.lines.LineString
Line string(s) with given indices.
"""
return self.line_strings[indices]
def __iter__(self):
"""Iterate over the line strings in this container.
Added in 0.4.0.
Yields
------
LineString
A line string in this container.
The order is identical to the order in the line string list
provided upon class initialization.
"""
return iter(self.line_strings)
def __len__(self):
"""Get the number of items in this instance.
Added in 0.4.0.
Returns
-------
int
Number of items in this instance.
"""
return len(self.items)
def __repr__(self):
return self.__str__()
def __str__(self):
return "LineStringsOnImage(%s, shape=%s)" % (
str(self.line_strings), self.shape)
def _is_point_on_line(line_start, line_end, point, eps=1e-4):
dist_s2e = np.linalg.norm(np.float32(line_start) - np.float32(line_end))
dist_s2p2e = (
np.linalg.norm(np.float32(line_start) - np.float32(point))
+ np.linalg.norm(np.float32(point) - np.float32(line_end))
)
return -eps < (dist_s2p2e - dist_s2e) < eps
def _flatten_shapely_collection(collection):
import shapely.geometry
if not isinstance(collection, list):
collection = [collection]
for item in collection:
if hasattr(item, "geoms"):
for subitem in _flatten_shapely_collection(item.geoms):
# MultiPoint.geoms actually returns a GeometrySequence
if isinstance(subitem, shapely.geometry.base.GeometrySequence):
for subsubel in subitem:
yield subsubel
else:
yield _flatten_shapely_collection(subitem)
else:
yield item
def _convert_var_to_shapely_geometry(var):
import shapely.geometry
if isinstance(var, tuple):
geom = shapely.geometry.Point(var[0], var[1])
elif isinstance(var, list):
assert len(var) > 0, (
"Expected list to contain at least one coordinate, "
"got %d coordinates." % (len(var),))
if isinstance(var[0], tuple):
geom = shapely.geometry.LineString(var)
elif all([isinstance(v, LineString) for v in var]):
geom = shapely.geometry.MultiLineString([
shapely.geometry.LineString(ls.coords) for ls in var
])
else:
raise ValueError(
"Could not convert list-input to shapely geometry. Invalid "
"datatype. List elements had datatypes: %s." % (
", ".join([str(type(v)) for v in var]),))
elif isinstance(var, LineString):
geom = shapely.geometry.LineString(var.coords)
else:
raise ValueError(
"Could not convert input to shapely geometry. Invalid datatype. "
"Got: %s" % (type(var),))
return geom
================================================
FILE: imgaug/augmentables/normalization.py
================================================
"""Functions dealing with normalization of user input data to imgaug classes."""
from __future__ import print_function, division, absolute_import
import functools
import numpy as np
from .. import imgaug as ia
from .. import dtypes as iadt
from .base import IAugmentable
def _preprocess_shapes(shapes):
if shapes is None:
return None
if ia.is_np_array(shapes):
assert shapes.ndim in [3, 4], (
"Expected array 'shapes' to be 3- or 4-dimensional, got %d "
"dimensions and shape %s instead." % (shapes.ndim, shapes.shape))
return [image.shape for image in shapes]
assert isinstance(shapes, list), (
"Expected 'shapes' to be None or ndarray or list, got type %s "
"instead." % (type(shapes),))
result = []
for shape_i in shapes:
if isinstance(shape_i, tuple):
result.append(shape_i)
else:
assert ia.is_np_array(shape_i), (
"Expected each entry in list 'shapes' to be either a "
"tuple or an ndarray, got type %s." % (type(shape_i),))
result.append(shape_i.shape)
return result
def _assert_exactly_n_shapes(shapes, n, from_ntype, to_ntype):
if shapes is None:
raise ValueError(
"Tried to convert data of form '%s' to '%s'. This required %d "
"corresponding image shapes, but argument 'shapes' was set to "
"None. This can happen e.g. if no images were provided in a "
"Batch, as these would usually be used to automatically derive "
"image shapes." % (from_ntype, to_ntype, n))
if len(shapes) != n:
raise ValueError(
"Tried to convert data of form '%s' to '%s'. This required "
"exactly %d corresponding image shapes, but instead %d were "
"provided. This can happen e.g. if more images were provided "
"than corresponding augmentables, e.g. 10 images but only 5 "
"segmentation maps. It can also happen if there was a "
"misunderstanding about how an augmentable input would be "
"parsed. E.g. if a list of N (x,y)-tuples was provided as "
"keypoints and the expectation was that this would be parsed "
"as one keypoint per image for N images, but instead it was "
"parsed as N keypoints on 1 image (i.e. 'shapes' would have to "
"contain 1 shape, but N would be provided). To avoid this, it "
"is recommended to provide imgaug standard classes, e.g. "
"KeypointsOnImage for keypoints instead of lists of "
"tuples." % (from_ntype, to_ntype, n, len(shapes)))
def _assert_single_array_ndim(arr, ndim, shape_str, to_ntype):
if arr.ndim != ndim:
raise ValueError(
"Tried to convert an array to list of %s. Expected "
"that array to be of shape %s, i.e. %d-dimensional, but "
"got %d dimensions instead." % (
to_ntype, shape_str, ndim, arr.ndim,))
def _assert_many_arrays_ndim(arrs, ndim, shape_str, to_ntype):
# For polygons, this can be a list of lists of arrays, hence we must
# flatten the lists here.
# itertools.chain.from_iterable() seems to flatten the arrays too, so it
# cannot be used here.
iterable_type_str = "iterable"
if len(arrs) == 0:
arrs_flat = []
elif ia.is_np_array(arrs[0]):
arrs_flat = arrs
else:
iterable_type_str = "iterable of iterable"
arrs_flat = [arr for arrs_sublist in arrs for arr in arrs_sublist]
if any([arr.ndim != ndim for arr in arrs_flat]):
raise ValueError(
"Tried to convert an %s of arrays to a list of "
"%s. Expected each array to be of shape %s, "
"i.e. to be %d-dimensional, but got dimensions %s "
"instead (array shapes: %s)." % (
iterable_type_str, to_ntype, shape_str, ndim,
", ".join([str(arr.ndim) for arr in arrs_flat]),
", ".join([str(arr.shape) for arr in arrs_flat])))
def _assert_single_array_last_dim_exactly(arr, size, to_ntype):
if arr.shape[-1] != size:
raise ValueError(
"Tried to convert an array to a list of %s. Expected the array's "
"last dimension to have size %d, but got %d instead (array "
"shape: %s)." % (
to_ntype, size, arr.shape[-1], str(arr.shape)))
def _assert_many_arrays_last_dim_exactly(arrs, size, to_ntype):
# For polygons, this can be a list of lists of arrays, hence we must
# flatten the lists here.
# itertools.chain.from_iterable() seems to flatten the arrays too, so it
# cannot be used here.
iterable_type_str = "iterable"
if len(arrs) == 0:
arrs_flat = []
elif ia.is_np_array(arrs[0]):
arrs_flat = arrs
else:
iterable_type_str = "iterable of iterable"
arrs_flat = [arr for arrs_sublist in arrs for arr in arrs_sublist]
if any([arr.shape[-1] != size for arr in arrs_flat]):
raise ValueError(
"Tried to convert an %s of array to a list of %s. Expected the "
"arrays' last dimensions to have size %d, but got %s instead "
"(array shapes: %s)." % (
iterable_type_str, to_ntype, size,
", ".join([str(arr.shape[-1]) for arr in arrs_flat]),
", ".join([str(arr.shape) for arr in arrs_flat])))
def normalize_images(images):
if images is None:
return None
if ia.is_np_array(images):
if images.ndim == 2:
return images[np.newaxis, ..., np.newaxis]
if images.ndim == 3:
return images[..., np.newaxis]
return images
if ia.is_iterable(images):
result = []
for image in images:
assert image.ndim in [2, 3], (
"Got a list of arrays as argument 'images'. Expected each "
"array in that list to have 2 or 3 dimensions, i.e. shape "
"(H,W) or (H,W,C). Got %d dimensions "
"instead." % (image.ndim,))
if image.ndim == 2:
result.append(image[..., np.newaxis])
else:
result.append(image)
return result
raise ValueError(
"Expected argument 'images' to be any of the following: "
"None or array or iterable of array. Got type: %s." % (
type(images),))
def normalize_heatmaps(inputs, shapes=None):
# TODO get rid of this deferred import
from imgaug.augmentables.heatmaps import HeatmapsOnImage
shapes = _preprocess_shapes(shapes)
ntype = estimate_heatmaps_norm_type(inputs)
_assert_exactly_n_shapes_partial = functools.partial(
_assert_exactly_n_shapes,
from_ntype=ntype, to_ntype="List[HeatmapsOnImage]", shapes=shapes)
if ntype == "None":
return None
if ntype == "array[float]":
_assert_single_array_ndim(inputs, 4, "(N,H,W,C)", "HeatmapsOnImage")
_assert_exactly_n_shapes_partial(n=len(inputs))
return [HeatmapsOnImage(attr_i, shape=shape_i)
for attr_i, shape_i in zip(inputs, shapes)]
if ntype == "HeatmapsOnImage":
return [inputs]
if ntype == "iterable[empty]":
return None
if ntype == "iterable-array[float]":
_assert_many_arrays_ndim(inputs, 3, "(H,W,C)", "HeatmapsOnImage")
_assert_exactly_n_shapes_partial(n=len(inputs))
return [HeatmapsOnImage(attr_i, shape=shape_i)
for attr_i, shape_i in zip(inputs, shapes)]
assert ntype == "iterable-HeatmapsOnImage", (
"Got unknown normalization type '%s'." % (ntype,))
return inputs # len allowed to differ from len of images
def normalize_segmentation_maps(inputs, shapes=None):
# TODO get rid of this deferred import
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
shapes = _preprocess_shapes(shapes)
ntype = estimate_segmaps_norm_type(inputs)
_assert_exactly_n_shapes_partial = functools.partial(
_assert_exactly_n_shapes,
from_ntype=ntype, to_ntype="List[SegmentationMapsOnImage]",
shapes=shapes)
if ntype == "None":
return None
if ntype in ["array[int]", "array[uint]", "array[bool]"]:
_assert_single_array_ndim(inputs, 4, "(N,H,W,#SegmapsPerImage)",
"SegmentationMapsOnImage")
_assert_exactly_n_shapes_partial(n=len(inputs))
if ntype == "array[bool]":
return [SegmentationMapsOnImage(attr_i, shape=shape)
for attr_i, shape in zip(inputs, shapes)]
return [SegmentationMapsOnImage(attr_i, shape=shape)
for attr_i, shape in zip(inputs, shapes)]
if ntype == "SegmentationMapsOnImage":
return [inputs]
if ntype == "iterable[empty]":
return None
if ntype in ["iterable-array[int]",
"iterable-array[uint]",
"iterable-array[bool]"]:
_assert_many_arrays_ndim(inputs, 3, "(H,W,#SegmapsPerImage)",
"SegmentationMapsOnImage")
_assert_exactly_n_shapes_partial(n=len(inputs))
if ntype == "iterable-array[bool]":
return [SegmentationMapsOnImage(attr_i, shape=shape)
for attr_i, shape in zip(inputs, shapes)]
return [SegmentationMapsOnImage(attr_i, shape=shape)
for attr_i, shape in zip(inputs, shapes)]
assert ntype == "iterable-SegmentationMapsOnImage", (
"Got unknown normalization type '%s'." % (ntype,))
return inputs # len allowed to differ from len of images
def normalize_keypoints(inputs, shapes=None):
# TODO get rid of this deferred import
from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
shapes = _preprocess_shapes(shapes)
ntype = estimate_keypoints_norm_type(inputs)
_assert_exactly_n_shapes_partial = functools.partial(
_assert_exactly_n_shapes,
from_ntype=ntype, to_ntype="List[KeypointsOnImage]",
shapes=shapes)
if ntype == "None":
return inputs
if ntype in ["array[float]", "array[int]", "array[uint]"]:
_assert_single_array_ndim(inputs, 3, "(N,K,2)", "KeypointsOnImage")
_assert_single_array_last_dim_exactly(inputs, 2, "KeypointsOnImage")
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
KeypointsOnImage.from_xy_array(attr_i, shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
if ntype == "tuple[number,size=2]":
_assert_exactly_n_shapes_partial(n=1)
return [KeypointsOnImage([Keypoint(x=inputs[0], y=inputs[1])],
shape=shapes[0])]
if ntype == "Keypoint":
_assert_exactly_n_shapes_partial(n=1)
return [KeypointsOnImage([inputs], shape=shapes[0])]
if ntype == "KeypointsOnImage":
return [inputs]
if ntype == "iterable[empty]":
return None
if ntype in ["iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]"]:
_assert_many_arrays_ndim(inputs, 2, "(K,2)", "KeypointsOnImage")
_assert_many_arrays_last_dim_exactly(inputs, 2, "KeypointsOnImage")
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
KeypointsOnImage.from_xy_array(attr_i, shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
if ntype == "iterable-tuple[number,size=2]":
_assert_exactly_n_shapes_partial(n=1)
return [KeypointsOnImage([Keypoint(x=x, y=y) for x, y in inputs],
shape=shapes[0])]
if ntype == "iterable-Keypoint":
_assert_exactly_n_shapes_partial(n=1)
return [KeypointsOnImage(inputs, shape=shapes[0])]
if ntype == "iterable-KeypointsOnImage":
return inputs
if ntype == "iterable-iterable[empty]":
return None
if ntype == "iterable-iterable-tuple[number,size=2]":
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
KeypointsOnImage.from_xy_array(
np.array(attr_i, dtype=np.float32),
shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
assert ntype == "iterable-iterable-Keypoint", (
"Got unknown normalization type '%s'." % (ntype,))
_assert_exactly_n_shapes_partial(n=len(inputs))
return [KeypointsOnImage(attr_i, shape=shape)
for attr_i, shape
in zip(inputs, shapes)]
def normalize_bounding_boxes(inputs, shapes=None):
# TODO get rid of this deferred import
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
shapes = _preprocess_shapes(shapes)
ntype = estimate_bounding_boxes_norm_type(inputs)
_assert_exactly_n_shapes_partial = functools.partial(
_assert_exactly_n_shapes,
from_ntype=ntype, to_ntype="List[BoundingBoxesOnImage]",
shapes=shapes)
if ntype == "None":
return None
if ntype in ["array[float]", "array[int]", "array[uint]"]:
_assert_single_array_ndim(inputs, 3, "(N,B,4)", "BoundingBoxesOnImage")
_assert_single_array_last_dim_exactly(
inputs, 4, "BoundingBoxesOnImage")
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
BoundingBoxesOnImage.from_xyxy_array(attr_i, shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
if ntype == "tuple[number,size=4]":
_assert_exactly_n_shapes_partial(n=1)
return [
BoundingBoxesOnImage(
[BoundingBox(
x1=inputs[0], y1=inputs[1],
x2=inputs[2], y2=inputs[3])],
shape=shapes[0])
]
if ntype == "BoundingBox":
_assert_exactly_n_shapes_partial(n=1)
return [BoundingBoxesOnImage([inputs], shape=shapes[0])]
if ntype == "BoundingBoxesOnImage":
return [inputs]
if ntype == "iterable[empty]":
return None
if ntype in ["iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]"]:
_assert_many_arrays_ndim(inputs, 2, "(B,4)", "BoundingBoxesOnImage")
_assert_many_arrays_last_dim_exactly(inputs, 4, "BoundingBoxesOnImage")
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
BoundingBoxesOnImage.from_xyxy_array(attr_i, shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
if ntype == "iterable-tuple[number,size=4]":
_assert_exactly_n_shapes_partial(n=1)
return [
BoundingBoxesOnImage(
[BoundingBox(x1=x1, y1=y1, x2=x2, y2=y2)
for x1, y1, x2, y2 in inputs],
shape=shapes[0])
]
if ntype == "iterable-BoundingBox":
_assert_exactly_n_shapes_partial(n=1)
return [BoundingBoxesOnImage(inputs, shape=shapes[0])]
if ntype == "iterable-BoundingBoxesOnImage":
return inputs
if ntype == "iterable-iterable[empty]":
return None
if ntype == "iterable-iterable-tuple[number,size=4]":
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
BoundingBoxesOnImage.from_xyxy_array(
np.array(attr_i, dtype=np.float32),
shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
assert ntype == "iterable-iterable-BoundingBox", (
"Got unknown normalization type '%s'." % (ntype,))
_assert_exactly_n_shapes_partial(n=len(inputs))
return [BoundingBoxesOnImage(attr_i, shape=shape)
for attr_i, shape
in zip(inputs, shapes)]
def normalize_polygons(inputs, shapes=None):
# TODO get rid of this deferred import
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
return _normalize_polygons_and_line_strings(
cls_single=Polygon,
cls_oi=PolygonsOnImage,
axis_names=["#polys", "#points"],
estimate_ntype_func=estimate_polygons_norm_type,
inputs=inputs, shapes=shapes
)
def normalize_line_strings(inputs, shapes=None):
# TODO get rid of this deferred import
from imgaug.augmentables.lines import LineString, LineStringsOnImage
return _normalize_polygons_and_line_strings(
cls_single=LineString,
cls_oi=LineStringsOnImage,
axis_names=["#lines", "#points"],
estimate_ntype_func=estimate_line_strings_norm_type,
inputs=inputs, shapes=shapes
)
def _normalize_polygons_and_line_strings(cls_single, cls_oi, axis_names,
estimate_ntype_func,
inputs, shapes=None):
cls_single_name = cls_single.__name__
cls_oi_name = cls_oi.__name__
axis_names_4_str = "(N,%s,%s,2)" % (axis_names[0], axis_names[1])
axis_names_3_str = "(%s,%s,2)" % (axis_names[0], axis_names[1])
axis_names_2_str = "(%s,2)" % (axis_names[1],)
shapes = _preprocess_shapes(shapes)
ntype = estimate_ntype_func(inputs)
_assert_exactly_n_shapes_partial = functools.partial(
_assert_exactly_n_shapes,
from_ntype=ntype, to_ntype=("List[%s]" % (cls_oi_name,)),
shapes=shapes)
if ntype == "None":
return None
if ntype in ["array[float]", "array[int]", "array[uint]"]:
_assert_single_array_ndim(inputs, 4, axis_names_4_str,
cls_oi_name)
_assert_single_array_last_dim_exactly(inputs, 2, cls_oi_name)
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
cls_oi(
[cls_single(points) for points in attr_i],
shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
if ntype == cls_single_name:
_assert_exactly_n_shapes_partial(n=1)
return [cls_oi([inputs], shape=shapes[0])]
if ntype == cls_oi_name:
return [inputs]
if ntype == "iterable[empty]":
return None
if ntype in ["iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]"]:
_assert_many_arrays_ndim(inputs, 3, axis_names_3_str,
cls_oi_name)
_assert_many_arrays_last_dim_exactly(inputs, 2, cls_oi_name)
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
cls_oi([cls_single(points) for points in attr_i], shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
if ntype == "iterable-tuple[number,size=2]":
_assert_exactly_n_shapes_partial(n=1)
return [cls_oi([cls_single(inputs)], shape=shapes[0])]
if ntype == "iterable-Keypoint":
_assert_exactly_n_shapes_partial(n=1)
return [cls_oi([cls_single(inputs)], shape=shapes[0])]
if ntype == ("iterable-%s" % (cls_single_name,)):
_assert_exactly_n_shapes_partial(n=1)
return [cls_oi(inputs, shape=shapes[0])]
if ntype == ("iterable-%s" % (cls_oi_name,)):
return inputs
if ntype == "iterable-iterable[empty]":
return None
if ntype in ["iterable-iterable-array[float]",
"iterable-iterable-array[int]",
"iterable-iterable-array[uint]"]:
_assert_many_arrays_ndim(inputs, 2, axis_names_2_str, cls_oi_name)
_assert_many_arrays_last_dim_exactly(inputs, 2, cls_oi_name)
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
cls_oi(
[cls_single(points) for points in attr_i],
shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
if ntype == "iterable-iterable-tuple[number,size=2]":
_assert_exactly_n_shapes_partial(n=1)
return [
cls_oi([cls_single(attr_i) for attr_i in inputs],
shape=shapes[0])
]
if ntype == "iterable-iterable-Keypoint":
_assert_exactly_n_shapes_partial(n=1)
return [
cls_oi([cls_single(attr_i) for attr_i in inputs],
shape=shapes[0])
]
if ntype == ("iterable-iterable-%s" % (cls_single_name,)):
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
cls_oi(attr_i, shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
if ntype == "iterable-iterable-iterable[empty]":
return None
assert ntype in ["iterable-iterable-iterable-tuple[number,size=2]",
"iterable-iterable-iterable-Keypoint"], (
"Got unknown normalization type '%s'." % (ntype,))
_assert_exactly_n_shapes_partial(n=len(inputs))
return [
cls_oi(
[cls_single(points) for points in attr_i],
shape=shape)
for attr_i, shape
in zip(inputs, shapes)
]
def invert_normalize_images(images, images_old):
if images_old is None:
assert images is None, (
"Expected (normalized) 'images' to be None due to (unnormalized) "
"'images_old' being None. Got type %s instead." % (type(images),))
return None
if ia.is_np_array(images_old):
if not ia.is_np_array(images):
# Images were turned from array to list during augmentation.
# This can happen for e.g. crop operations.
# We will proceed as if the old images were a list.
# One could also generate an array-output if all shapes and dtypes
# in `images` are the same. This was not done here, because
# (a) that would incur a performance penalty and (b) it would
# lead to less consistent outputs.
if images_old.ndim == 2:
# dont interpret first axis as N if `images_old` was a single
# image
return invert_normalize_images(images, [images_old])
return invert_normalize_images(images, list(images_old))
if images_old.ndim == 2:
assert images.shape[0] == 1, (
"Expected normalized images of shape (N,H,W,C) to have "
"N=1 due to the unnormalized images being a single 2D "
"image. Got instead N=%d and shape %s." % (
images.shape[0], images.shape))
assert images.shape[3] == 1, (
"Expected normalized images of shape (N,H,W,C) to have "
"C=1 due to the unnormalized images being a single 2D "
"image. Got instead C=%d and shape %s." % (
images.shape[3], images.shape))
return images[0, ..., 0]
if images_old.ndim == 3:
assert images.shape[3] == 1, (
"Expected normalized images of shape (N,H,W,C) to have "
"C=1 due to unnormalized images being a single 3D image. "
"Got instead C=%d and shape %s" % (
images.shape[3], images.shape))
return images[..., 0]
return images
if ia.is_iterable(images_old):
result = []
for image, image_old in zip(images, images_old):
if image_old.ndim == 2:
assert image.shape[2] == 1, (
"Expected each image of shape (H,W,C) to have C=1 due to "
"the corresponding unnormalized image being a 2D image. "
"Got instead C=%d and shape %s." % (
image.shape[2], image.shape))
result.append(image[:, :, 0])
else:
assert image_old.ndim == 3, (
"Expected 'image_old' to be three-dimensional, got %d "
"dimensions and shape %s." % (
image_old.ndim, image_old.shape))
result.append(image)
return result
raise ValueError(
"Expected argument 'images_old' to be any of the following: "
"None or array or iterable of array. Got type: %s." % (
type(images_old),))
def invert_normalize_heatmaps(heatmaps, heatmaps_old):
ntype = estimate_heatmaps_norm_type(heatmaps_old)
if ntype == "None":
assert heatmaps is None, (
"Expected (normalized) 'heatmaps' to be None due (unnormalized) "
"'heatmaps_old' being None. Got type %s instead." % (
type(heatmaps),))
return heatmaps
if ntype == "array[float]":
assert len(heatmaps) == heatmaps_old.shape[0], (
"Expected as many heatmaps after normalization as before "
"normalization. Got %d (after) and %d (before)." % (
len(heatmaps), heatmaps_old.shape[0]))
input_dtype = heatmaps_old.dtype
return restore_dtype_and_merge(
[hm_i.arr_0to1 for hm_i in heatmaps],
input_dtype)
if ntype == "HeatmapsOnImage":
assert len(heatmaps) == 1, (
"Expected as many heatmaps after normalization as before "
"normalization. Got %d (after) and %d (before)." % (
len(heatmaps), 1))
return heatmaps[0]
if ntype == "iterable[empty]":
assert heatmaps is None, (
"Expected heatmaps after normalization to be None, due to the "
"heatmaps before normalization being an empty iterable. "
"Got type %s instead." % (type(heatmaps),))
return []
if ntype == "iterable-array[float]":
nonempty, _, _ = find_first_nonempty(heatmaps_old)
input_dtype = nonempty.dtype
return [restore_dtype_and_merge(hm_i.arr_0to1, input_dtype)
for hm_i in heatmaps]
assert ntype == "iterable-HeatmapsOnImage", (
"Got unknown normalization type '%s'." % (ntype,))
return heatmaps
def invert_normalize_segmentation_maps(segmentation_maps,
segmentation_maps_old):
ntype = estimate_segmaps_norm_type(segmentation_maps_old)
if ntype == "None":
assert segmentation_maps is None, (
"Expected (normalized) 'segmentation_maps' to be None due "
"(unnormalized) 'segmentation_maps_old' being None. Got type %s "
"instead." % (type(segmentation_maps),))
return segmentation_maps
if ntype in ["array[int]", "array[uint]", "array[bool]"]:
assert len(segmentation_maps) == segmentation_maps_old.shape[0], (
"Expected as many segmentation maps after normalization as before "
"normalization. Got %d (after) and %d (before)." % (
len(segmentation_maps), segmentation_maps_old.shape[0]))
input_dtype = segmentation_maps_old.dtype
return restore_dtype_and_merge(
[segmap_i.get_arr() for segmap_i in segmentation_maps],
input_dtype)
if ntype == "SegmentationMapsOnImage":
assert len(segmentation_maps) == 1, (
"Expected as many segmentation maps after normalization as before "
"normalization. Got %d (after) and %d (before)." % (
len(segmentation_maps), 1))
return segmentation_maps[0]
if ntype == "iterable[empty]":
assert segmentation_maps is None, (
"Expected segmentation maps after normalization to be None, due "
"to the segmentation maps before normalization being an empty "
"iterable. Got type %s instead." % (type(segmentation_maps),))
return []
if ntype in ["iterable-array[int]",
"iterable-array[uint]",
"iterable-array[bool]"]:
nonempty, _, _ = find_first_nonempty(segmentation_maps_old)
input_dtype = nonempty.dtype
return [restore_dtype_and_merge(segmap_i.get_arr(), input_dtype)
for segmap_i in segmentation_maps]
assert ntype == "iterable-SegmentationMapsOnImage", (
"Got unknown normalization type '%s'." % (ntype,))
return segmentation_maps
def invert_normalize_keypoints(keypoints, keypoints_old):
ntype = estimate_keypoints_norm_type(keypoints_old)
if ntype == "None":
assert keypoints is None, (
"Expected (normalized) 'keypoints' to be None due (unnormalized) "
"'keypoints_old' being None. Got type %s instead." % (
type(keypoints),))
return keypoints
if ntype in ["array[float]", "array[int]", "array[uint]"]:
assert len(keypoints) == 1, (
"Expected a single KeypointsOnImage instance after normalization "
"due to getting a single ndarray before normalization. "
"Got %d instances instead." % (len(keypoints),))
input_dtype = keypoints_old.dtype
return restore_dtype_and_merge(
[kpsoi.to_xy_array() for kpsoi in keypoints],
input_dtype)
if ntype == "tuple[number,size=2]":
assert len(keypoints) == 1, (
"Expected a single KeypointsOnImage instance after normalization "
"due to getting a single (x,y) tuple before normalization. "
"Got %d instances instead." % (len(keypoints),))
assert len(keypoints[0].keypoints) == 1, (
"Expected a KeypointsOnImage instance containing a single "
"Keypoint after normalization due to getting a single (x,y) tuple "
"before normalization. Got %d keypoints instead." % (
len(keypoints[0].keypoints)
))
return (keypoints[0].keypoints[0].x,
keypoints[0].keypoints[0].y)
if ntype == "Keypoint":
assert len(keypoints) == 1, (
"Expected a single KeypointsOnImage instance after normalization "
"due to getting a single Keypoint before normalization. "
"Got %d instances instead." % (len(keypoints),))
assert len(keypoints[0].keypoints) == 1, (
"Expected a KeypointsOnImage instance containing a single "
"Keypoint after normalization due to getting a single Keypoint "
"before normalization. Got %d keypoints instead." % (
len(keypoints[0].keypoints)
))
return keypoints[0].keypoints[0]
if ntype == "KeypointsOnImage":
assert len(keypoints) == 1, (
"Expected a single KeypointsOnImage instance after normalization "
"due to getting a single KeypointsOnImage before normalization. "
"Got %d instances instead." % (len(keypoints),))
return keypoints[0]
if ntype == "iterable[empty]":
assert keypoints is None, (
"Expected keypoints after normalization to be None, due "
"to the keypoints before normalization being an empty "
"iterable. Got type %s instead." % (type(keypoints),))
return []
if ntype in ["iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]"]:
nonempty, _, _ = find_first_nonempty(keypoints_old)
input_dtype = nonempty.dtype
return [
restore_dtype_and_merge(kps_i.to_xy_array(), input_dtype)
for kps_i in keypoints]
if ntype == "iterable-tuple[number,size=2]":
assert len(keypoints) == 1, (
"Expected a single KeypointsOnImage instance after normalization "
"due to getting an iterable of (x,y) tuples before "
"normalization. Got %d instances instead." % (len(keypoints),))
return [
(kp.x, kp.y) for kp in keypoints[0].keypoints]
if ntype == "iterable-Keypoint":
assert len(keypoints) == 1, (
"Expected a single KeypointsOnImage instance after normalization "
"due to getting an iterable of Keypoint before "
"normalization. Got %d instances instead." % (len(keypoints),))
return keypoints[0].keypoints
if ntype == "iterable-KeypointsOnImage":
return keypoints
if ntype == "iterable-iterable[empty]":
assert keypoints is None, (
"Expected keypoints after normalization to be None, due "
"to the keypoints before normalization being an empty "
"iterable of iterables. Got type %s instead." % (type(keypoints),))
return keypoints_old[:]
if ntype == "iterable-iterable-tuple[number,size=2]":
return [
[(kp.x, kp.y) for kp in kpsoi.keypoints]
for kpsoi in keypoints]
assert ntype == "iterable-iterable-Keypoint", (
"Got unknown normalization type '%s'." % (ntype,))
return [kpsoi.keypoints[:] for kpsoi in keypoints]
def invert_normalize_bounding_boxes(bounding_boxes, bounding_boxes_old):
ntype = estimate_normalization_type(bounding_boxes_old)
if ntype == "None":
assert bounding_boxes is None, (
"Expected (normalized) 'bounding_boxes' to be None due "
"(unnormalized) 'bounding_boxes_old' being None. Got type %s "
"instead." % (type(bounding_boxes),))
return bounding_boxes
if ntype in ["array[float]", "array[int]", "array[uint]"]:
assert len(bounding_boxes) == 1, (
"Expected a single BoundingBoxesOnImage instance after "
"normalization due to getting a single ndarray before "
"normalization. Got %d instances instead." % (
len(bounding_boxes),))
input_dtype = bounding_boxes_old.dtype
return restore_dtype_and_merge([
bbsoi.to_xyxy_array() for bbsoi in bounding_boxes
], input_dtype)
if ntype == "tuple[number,size=4]":
assert len(bounding_boxes) == 1, (
"Expected a single BoundingBoxesOnImage instance after "
"normalization due to getting a single (x1,y1,x2,y2) tuple before "
"normalization. Got %d instances instead." % (
len(bounding_boxes),))
assert len(bounding_boxes[0].bounding_boxes) == 1, (
"Expected a BoundingBoxesOnImage instance containing a single "
"BoundingBox after normalization due to getting a single "
"(x1,y1,x2,y2) tuple before normalization. Got %d bounding boxes "
"instead." % (len(bounding_boxes[0].bounding_boxes)))
bb = bounding_boxes[0].bounding_boxes[0]
return bb.x1, bb.y1, bb.x2, bb.y2
if ntype == "BoundingBox":
assert len(bounding_boxes) == 1, (
"Expected a single BoundingBoxesOnImage instance after "
"normalization due to getting a single BoundingBox before "
"normalization. Got %d instances instead." % (
len(bounding_boxes),))
assert len(bounding_boxes[0].bounding_boxes) == 1, (
"Expected a BoundingBoxesOnImage instance containing a single "
"BoundingBox after normalization due to getting a single "
"BoundingBox before normalization. Got %d bounding boxes "
"instead." % (len(bounding_boxes[0].bounding_boxes)))
return bounding_boxes[0].bounding_boxes[0]
if ntype == "BoundingBoxesOnImage":
assert len(bounding_boxes) == 1, (
"Expected a single BoundingBoxesOnImage instance after "
"normalization due to getting a single BoundingBoxesOnImage "
"before normalization. Got %d instances instead." % (
len(bounding_boxes),))
return bounding_boxes[0]
if ntype == "iterable[empty]":
assert bounding_boxes is None, (
"Expected bounding boxes after normalization to be None, due "
"to the bounding boxes before normalization being an empty "
"iterable. Got type %s instead." % (type(bounding_boxes),))
return []
if ntype in ["iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]"]:
nonempty, _, _ = find_first_nonempty(bounding_boxes_old)
input_dtype = nonempty.dtype
return [
restore_dtype_and_merge(bbsoi.to_xyxy_array(), input_dtype)
for bbsoi in bounding_boxes]
if ntype == "iterable-tuple[number,size=4]":
assert len(bounding_boxes) == 1, (
"Expected a single BoundingBoxesOnImage instance after "
"normalization due to getting a an iterable of (x1,y1,x2,y2) "
"tuples before normalization. Got %d instances instead." % (
len(bounding_boxes),))
return [
(bb.x1, bb.y1, bb.x2, bb.y2)
for bb in bounding_boxes[0].bounding_boxes]
if ntype == "iterable-BoundingBox":
assert len(bounding_boxes) == 1, (
"Expected a single BoundingBoxesOnImage instance after "
"normalization due to getting an iterable of BoundingBox before "
"normalization. Got %d instances instead." % (
len(bounding_boxes),))
return bounding_boxes[0].bounding_boxes
if ntype == "iterable-BoundingBoxesOnImage":
return bounding_boxes
if ntype == "iterable-iterable[empty]":
assert bounding_boxes is None, (
"Expected bounding boxes after normalization to be None, due "
"to the bounding boxes before normalization being an empty "
"iterable of iterables. Got type %s instead." % (
type(bounding_boxes),))
return bounding_boxes_old[:]
if ntype == "iterable-iterable-tuple[number,size=4]":
return [
[(bb.x1, bb.y1, bb.x2, bb.y2) for bb in bbsoi.bounding_boxes]
for bbsoi in bounding_boxes]
assert ntype == "iterable-iterable-BoundingBox", (
"Got unknown normalization type '%s'." % (ntype,))
return [bbsoi.bounding_boxes[:] for bbsoi in bounding_boxes]
def invert_normalize_polygons(polygons, polygons_old):
return _invert_normalize_polygons_and_line_strings(
polygons, polygons_old, estimate_polygons_norm_type,
"Polygon",
"PolygonsOnImage",
lambda psoi: psoi.polygons,
lambda poly: poly.exterior)
def invert_normalize_line_strings(line_strings, line_strings_old):
return _invert_normalize_polygons_and_line_strings(
line_strings, line_strings_old, estimate_line_strings_norm_type,
"LineString",
"LineStringsOnImage",
lambda lsoi: lsoi.line_strings,
lambda ls: ls.coords)
def _invert_normalize_polygons_and_line_strings(inputs, inputs_old,
estimate_ntype_func,
cls_single_name,
cls_oi_name,
get_entities_func,
get_points_func):
# TODO get rid of this deferred import
from imgaug.augmentables.kps import Keypoint
ntype = estimate_ntype_func(inputs_old)
if ntype == "None":
assert inputs is None, (
"Expected (normalized) polygons/line strings to be None due "
"(unnormalized) polygons/line strings being None. Got type %s "
"instead." % (type(inputs),))
return inputs
if ntype in ["array[float]", "array[int]", "array[uint]"]:
input_dtype = inputs_old.dtype
return restore_dtype_and_merge([
[get_points_func(entity) for entity in get_entities_func(oi)]
for oi in inputs
], input_dtype)
if ntype == cls_single_name:
assert len(inputs) == 1, (
"Expected a single %s instance after normalization "
"due to getting a single %s before normalization. "
"Got %d instances instead." % (
cls_oi_name, cls_single_name, len(inputs),))
assert len(get_entities_func(inputs[0])) == 1, (
"Expected a %s instance containing a single "
"%s after normalization due to getting a single %s "
"before normalization. Got %d instances instead." % (
cls_oi_name, cls_single_name, cls_single_name,
len(get_entities_func(inputs[0]))))
return get_entities_func(inputs[0])[0]
if ntype == cls_oi_name:
assert len(inputs) == 1, (
"Expected a single %s instance after normalization "
"due to getting a single %s before normalization. "
"Got %d instances instead." % (
cls_oi_name, cls_oi_name, len(inputs),))
return inputs[0]
if ntype == "iterable[empty]":
assert inputs is None, (
"Expected polygons/line strings after normalization to be None, "
"due to the polygons/line strings before normalization being an "
"empty iterable. Got type %s instead." % (type(inputs),))
return []
if ntype in ["iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]"]:
nonempty, _, _ = find_first_nonempty(inputs_old)
input_dtype = nonempty.dtype
return [
restore_dtype_and_merge(
[get_points_func(entity) for entity in get_entities_func(oi)],
input_dtype)
for oi in inputs
]
if ntype == "iterable-tuple[number,size=2]":
assert len(inputs) == 1, (
"Expected a single %s instance after normalization "
"due to getting an iterable of (x,y) tuples before "
"normalization. Got %d instances instead." % (
cls_oi_name, len(inputs),))
assert len(get_entities_func(inputs[0])) == 1, (
"Expected a %s instance after normalization "
"containing a single %s instance due to getting an iterable "
"of (x,y) tuples before normalization. "
"Got a %s with %d %s instances instead." % (
cls_oi_name, cls_single_name, cls_oi_name, cls_single_name,
len(inputs),))
return [(point[0], point[1])
for point in get_points_func(get_entities_func(inputs[0])[0])]
if ntype == "iterable-Keypoint":
assert len(inputs) == 1, (
"Expected a single %s instance after normalization "
"due to getting an iterable of Keypoint before "
"normalization. Got %d instances instead." % (
cls_oi_name, len(inputs),))
assert len(get_entities_func(inputs[0])) == 1, (
"Expected a %s instance after normalization "
"containing a single %s instance due to getting an iterable "
"of Keypoint before normalization. "
"Got a %s with %d %s instances instead." % (
cls_oi_name, cls_single_name, cls_oi_name, cls_single_name,
len(inputs),))
return [Keypoint(x=point[0], y=point[1])
for point in get_points_func(get_entities_func(inputs[0])[0])]
if ntype == ("iterable-%s" % (cls_single_name,)):
assert len(inputs) == 1, (
"Expected a single %s instance after normalization "
"due to getting an iterable of %s before "
"normalization. Got %d instances instead." % (
cls_oi_name, cls_single_name, len(inputs),))
assert len(get_entities_func(inputs[0])) == len(inputs_old), (
"Expected a %s instance after normalization "
"containing a single %s instance due to getting an iterable "
"of %s before normalization. "
"Got a %s with %d %s instances instead." % (
cls_oi_name, cls_single_name, cls_single_name, cls_oi_name,
cls_single_name, len(inputs),))
return get_entities_func(inputs[0])
if ntype == ("iterable-%s" % (cls_oi_name,)):
return inputs
if ntype == "iterable-iterable[empty]":
assert inputs is None, (
"Expected polygons/line strings after normalization to be None, "
"due to the polygons/line strings before normalization being an "
"empty iterable of iterables. Got type %s instead." % (
type(inputs),))
return inputs_old[:]
if ntype in ["iterable-iterable-array[float]",
"iterable-iterable-array[int]",
"iterable-iterable-array[uint]"]:
nonempty, _, _ = find_first_nonempty(inputs_old)
input_dtype = nonempty.dtype
return [
[restore_dtype_and_merge(get_points_func(entity), input_dtype)
for entity in get_entities_func(oi)]
for oi in inputs
]
if ntype == "iterable-iterable-tuple[number,size=2]":
assert len(inputs) == 1, (
"Expected a single %s instance after normalization "
"due to getting an iterable of iterables of (x,y) tuples before "
"normalization. Got %d instances instead." % (
cls_oi_name, len(inputs),))
return [
[(point[0], point[1]) for point in get_points_func(entity)]
for entity in get_entities_func(inputs[0])]
if ntype == "iterable-iterable-Keypoint":
assert len(inputs) == 1, (
"Expected a single %s instance after normalization "
"due to getting an iterable of iterables of Keypoint before "
"normalization. Got %d instances instead." % (
cls_oi_name, len(inputs),))
return [
[Keypoint(x=point[0], y=point[1])
for point in get_points_func(entity)]
for entity in get_entities_func(inputs[0])]
if ntype == ("iterable-iterable-%s" % (cls_single_name,)):
return [get_entities_func(oi) for oi in inputs]
if ntype == "iterable-iterable-iterable[empty]":
return inputs_old[:]
if ntype == "iterable-iterable-iterable-tuple[number,size=2]":
return [
[
[
(point[0], point[1])
for point in get_points_func(entity)
]
for entity in get_entities_func(oi)
]
for oi in inputs]
assert ntype == "iterable-iterable-iterable-Keypoint", (
"Got unknown normalization type '%s'." % (ntype,))
return [
[
[
Keypoint(x=point[0], y=point[1])
for point in get_points_func(entity)
]
for entity in get_entities_func(oi)
]
for oi in inputs]
def _assert_is_of_norm_type(type_str, valid_type_strs, arg_name):
assert type_str in valid_type_strs, (
"Got an unknown datatype for argument '%s'. "
"Expected datatypes were: %s. Got: %s." % (
arg_name, ", ".join(valid_type_strs), type_str))
def estimate_heatmaps_norm_type(heatmaps):
type_str = estimate_normalization_type(heatmaps)
valid_type_strs = [
"None",
"array[float]",
"HeatmapsOnImage",
"iterable[empty]",
"iterable-array[float]",
"iterable-HeatmapsOnImage"
]
_assert_is_of_norm_type(type_str, valid_type_strs, "heatmaps")
return type_str
def estimate_segmaps_norm_type(segmentation_maps):
type_str = estimate_normalization_type(segmentation_maps)
valid_type_strs = [
"None",
"array[int]",
"array[uint]",
"array[bool]",
"SegmentationMapsOnImage",
"iterable[empty]",
"iterable-array[int]",
"iterable-array[uint]",
"iterable-array[bool]",
"iterable-SegmentationMapsOnImage"
]
_assert_is_of_norm_type(
type_str, valid_type_strs, "segmentation_maps")
return type_str
def estimate_keypoints_norm_type(keypoints):
type_str = estimate_normalization_type(keypoints)
valid_type_strs = [
"None",
"array[float]",
"array[int]",
"array[uint]",
"tuple[number,size=2]",
"Keypoint",
"KeypointsOnImage",
"iterable[empty]",
"iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]",
"iterable-tuple[number,size=2]",
"iterable-Keypoint",
"iterable-KeypointsOnImage",
"iterable-iterable[empty]",
"iterable-iterable-tuple[number,size=2]",
"iterable-iterable-Keypoint"
]
_assert_is_of_norm_type(type_str, valid_type_strs, "keypoints")
return type_str
def estimate_bounding_boxes_norm_type(bounding_boxes):
type_str = estimate_normalization_type(bounding_boxes)
valid_type_strs = [
"None",
"array[float]",
"array[int]",
"array[uint]",
"tuple[number,size=4]",
"BoundingBox",
"BoundingBoxesOnImage",
"iterable[empty]",
"iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]",
"iterable-tuple[number,size=4]",
"iterable-BoundingBox",
"iterable-BoundingBoxesOnImage",
"iterable-iterable[empty]",
"iterable-iterable-tuple[number,size=4]",
"iterable-iterable-BoundingBox"
]
_assert_is_of_norm_type(
type_str, valid_type_strs, "bounding_boxes")
return type_str
def estimate_polygons_norm_type(polygons):
return _estimate_polygons_and_line_segments_norm_type(
polygons, "Polygon", "PolygonsOnImage", "polygons")
def estimate_line_strings_norm_type(line_strings):
return _estimate_polygons_and_line_segments_norm_type(
line_strings, "LineString", "LineStringsOnImage", "line_strings")
def _estimate_polygons_and_line_segments_norm_type(inputs, cls_single_name,
cls_oi_name,
augmentable_name):
type_str = estimate_normalization_type(inputs)
valid_type_strs = [
"None",
"array[float]",
"array[int]",
"array[uint]",
cls_single_name,
cls_oi_name,
"iterable[empty]",
"iterable-array[float]",
"iterable-array[int]",
"iterable-array[uint]",
"iterable-tuple[number,size=2]",
"iterable-Keypoint",
"iterable-%s" % (cls_single_name,),
"iterable-%s" % (cls_oi_name,),
"iterable-iterable[empty]",
"iterable-iterable-array[float]",
"iterable-iterable-array[int]",
"iterable-iterable-array[uint]",
"iterable-iterable-tuple[number,size=2]",
"iterable-iterable-Keypoint",
"iterable-iterable-%s" % (cls_single_name,),
"iterable-iterable-iterable[empty]",
"iterable-iterable-iterable-tuple[number,size=2]",
"iterable-iterable-iterable-Keypoint"
]
_assert_is_of_norm_type(type_str, valid_type_strs, augmentable_name)
return type_str
def estimate_normalization_type(inputs):
nonempty, success, parents = find_first_nonempty(inputs)
type_str = _nonempty_info_to_type_str(nonempty, success, parents)
return type_str
def restore_dtype_and_merge(arr, input_dtype):
if isinstance(arr, list):
arr = [restore_dtype_and_merge(arr_i, input_dtype)
for arr_i in arr]
shapes = [arr_i.shape for arr_i in arr]
if len(set(shapes)) == 1:
arr = np.array(arr)
if ia.is_np_array(arr):
arr = iadt.restore_dtypes_(arr, input_dtype)
return arr
def _is_iterable(obj):
return (
ia.is_iterable(obj)
and not isinstance(obj, IAugmentable) # not e.g. KeypointsOnImage
and not hasattr(obj, "coords") # not BBs, Polys, LS
and not ia.is_string(obj)
)
def find_first_nonempty(attr, parents=None):
if parents is None:
parents = []
if attr is None or ia.is_np_array(attr):
return attr, True, parents
# we exclude strings here, as otherwise we would get the first
# character, while we want to get the whole string
if _is_iterable(attr):
if len(attr) == 0:
return None, False, parents
# this prevents the loop below from becoming infinite if the
# element in the iterable is identical with the iterable,
# as is the case for e.g. strings
if attr[0] is attr:
return attr, True, parents
# Usually in case of empty lists, all lists should have similar
# depth. We are a bit more tolerant here and pick the deepest one.
# Only parents would really need to be tracked here, we could
# ignore nonempty and success as they will always have the same
# values (if only empty lists exist).
nonempty_deepest = None
success_deepest = False
parents_deepest = parents
for attr_i in attr:
nonempty, success, parents_found = find_first_nonempty(
attr_i, parents=parents+[attr])
if success:
# on any nonempty hit we return immediately as we assume
# that the datatypes do not change between child branches
return nonempty, success, parents_found
if len(parents_found) > len(parents_deepest):
nonempty_deepest = nonempty
success_deepest = success
parents_deepest = parents_found
return nonempty_deepest, success_deepest, parents_deepest
return attr, True, parents
def _nonempty_info_to_type_str(nonempty, success, parents):
assert len(parents) <= 4, "Expected 'parents' to be <=4, got %d." % (
len(parents),)
parent_iters = ""
if len(parents) > 0:
parent_iters = "%s-" % ("-".join(["iterable"] * len(parents)),)
if not success:
return "%siterable[empty]" % (parent_iters,)
is_parent_tuple = (
len(parents) >= 1
and isinstance(parents[-1], tuple)
)
if is_parent_tuple:
is_only_numbers_in_tuple = (
len(parents[-1]) > 0
and all([ia.is_single_number(val) for val in parents[-1]])
)
if is_only_numbers_in_tuple:
parent_iters = "-".join(["iterable"] * (len(parents)-1))
tpl_name = "tuple[number,size=%d]" % (len(parents[-1]),)
return "-".join([parent_iters, tpl_name]).lstrip("-")
if nonempty is None:
return "None"
if ia.is_np_array(nonempty):
kind = nonempty.dtype.kind
kind_map = {"f": "float", "u": "uint", "i": "int", "b": "bool"}
return "%sarray[%s]" % (
parent_iters, kind_map[kind] if kind in kind_map else kind)
# even int, str etc. are objects in python, so anything left should
# offer a __class__ attribute
assert isinstance(nonempty, object), (
"Expected 'nonempty' to be an object, got type %s." % (
type(nonempty),))
return "%s%s" % (parent_iters, nonempty.__class__.__name__)
================================================
FILE: imgaug/augmentables/polys.py
================================================
"""Classes dealing with polygons."""
from __future__ import print_function, division, absolute_import
import traceback
import collections
import numpy as np
import scipy.spatial.distance
import six.moves as sm
import skimage.draw
import skimage.measure
from .. import imgaug as ia
from .. import random as iarandom
from .base import IAugmentable
from .utils import (
normalize_imglike_shape,
interpolate_points,
_remove_out_of_image_fraction_,
project_coords_,
_normalize_shift_args,
_handle_on_image_shape
)
def recover_psois_(psois, psois_orig, recoverer, random_state):
"""Apply a polygon recoverer to input polygons in-place.
Parameters
----------
psois : list of imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.polys.PolygonsOnImage
The possibly broken polygons, e.g. after augmentation.
The `recoverer` is applied to them.
psois_orig : list of imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.polys.PolygonsOnImage
Original polygons that were later changed to `psois`.
They are an extra input to `recoverer`.
recoverer : imgaug.augmentables.polys._ConcavePolygonRecoverer
The polygon recoverer used to repair broken input polygons.
random_state : None or int or RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
An RNG to use during the polygon recovery.
Returns
-------
list of imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.polys.PolygonsOnImage
List of repaired polygons. Note that this is `psois`, which was
changed in-place.
"""
input_was_list = True
if not isinstance(psois, list):
input_was_list = False
psois = [psois]
psois_orig = [psois_orig]
for i, psoi in enumerate(psois):
for j, polygon in enumerate(psoi.polygons):
poly_rec = recoverer.recover_from(
polygon.exterior, psois_orig[i].polygons[j],
random_state)
# Don't write into `polygon.exterior[...] = ...` because the
# shapes might have changed. We could also first check if the
# shapes are identical and only then write in-place, but as the
# array for `poly_rec.exterior` was already created, that would
# not provide any benefits.
polygon.exterior = poly_rec.exterior
if not input_was_list:
return psois[0]
return psois
# TODO somehow merge with BoundingBox
# TODO add functions: simplify() (eg via shapely.ops.simplify()),
# extend(all_sides=0, top=0, right=0, bottom=0, left=0),
# intersection(other, default=None), union(other), iou(other), to_heatmap, to_mask
class Polygon(object):
"""Class representing polygons.
Each polygon is parameterized by its corner points, given as absolute
x- and y-coordinates with sub-pixel accuracy.
Parameters
----------
exterior : list of imgaug.augmentables.kps.Keypoint or list of tuple of float or (N,2) ndarray
List of points defining the polygon. May be either a ``list`` of
:class:`~imgaug.augmentables.kps.Keypoint` objects or a ``list`` of
``tuple`` s in xy-form or a numpy array of shape (N,2) for ``N``
points in xy-form.
All coordinates are expected to be the absolute subpixel-coordinates
on the image, given as ``float`` s, e.g. ``x=10.7`` and ``y=3.4`` for a
point at coordinates ``(10.7, 3.4)``. Their order is expected to be
clock-wise. They are expected to not be closed (i.e. first and last
coordinate differ).
label : None or str, optional
Label of the polygon, e.g. a string representing the class.
"""
def __init__(self, exterior, label=None):
"""Create a new Polygon instance."""
# TODO get rid of this deferred import
from imgaug.augmentables.kps import Keypoint
if isinstance(exterior, list):
if not exterior:
# for empty lists, make sure that the shape is (0, 2) and
# not (0,) as that is also expected when the input is a numpy
# array
self.exterior = np.zeros((0, 2), dtype=np.float32)
elif isinstance(exterior[0], Keypoint):
# list of Keypoint
self.exterior = np.float32([[point.x, point.y]
for point in exterior])
else:
# list of tuples (x, y)
# TODO just np.float32(exterior) here?
self.exterior = np.float32([[point[0], point[1]]
for point in exterior])
else:
assert ia.is_np_array(exterior), (
"Expected exterior to be a list of tuples (x, y) or "
"an (N, 2) array, got type %s" % (exterior,))
assert exterior.ndim == 2 and exterior.shape[1] == 2, (
"Expected exterior to be a list of tuples (x, y) or "
"an (N, 2) array, got an array of shape %s" % (
exterior.shape,))
# TODO deal with int inputs here?
self.exterior = np.float32(exterior)
# Remove last point if it is essentially the same as the first
# point (polygons are always assumed to be closed anyways). This also
# prevents problems with shapely, which seems to add the last point
# automatically.
is_closed = (
len(self.exterior) >= 2
and np.allclose(self.exterior[0, :], self.exterior[-1, :]))
if is_closed:
self.exterior = self.exterior[:-1]
self.label = label
@property
def coords(self):
"""Alias for attribute ``exterior``.
Added in 0.4.0.
Returns
-------
ndarray
An ``(N, 2)`` ``float32`` ndarray containing the coordinates of
this polygon. This identical to the attribute ``exterior``.
"""
return self.exterior
@property
def xx(self):
"""Get the x-coordinates of all points on the exterior.
Returns
-------
(N,2) ndarray
``float32`` x-coordinates array of all points on the exterior.
"""
return self.exterior[:, 0]
@property
def yy(self):
"""Get the y-coordinates of all points on the exterior.
Returns
-------
(N,2) ndarray
``float32`` y-coordinates array of all points on the exterior.
"""
return self.exterior[:, 1]
@property
def xx_int(self):
"""Get the discretized x-coordinates of all points on the exterior.
The conversion from ``float32`` coordinates to ``int32`` is done
by first rounding the coordinates to the closest integer and then
removing everything after the decimal point.
Returns
-------
(N,2) ndarray
``int32`` x-coordinates of all points on the exterior.
"""
return np.int32(np.round(self.xx))
@property
def yy_int(self):
"""Get the discretized y-coordinates of all points on the exterior.
The conversion from ``float32`` coordinates to ``int32`` is done
by first rounding the coordinates to the closest integer and then
removing everything after the decimal point.
Returns
-------
(N,2) ndarray
``int32`` y-coordinates of all points on the exterior.
"""
return np.int32(np.round(self.yy))
@property
def is_valid(self):
"""Estimate whether the polygon has a valid geometry.
To to be considered valid, the polygon must be made up of at
least ``3`` points and have a concave shape, i.e. line segments may
not intersect or overlap. Multiple consecutive points are allowed to
have the same coordinates.
Returns
-------
bool
``True`` if polygon has at least ``3`` points and is concave,
otherwise ``False``.
"""
if len(self.exterior) < 3:
return False
return self.to_shapely_polygon().is_valid
@property
def area(self):
"""Compute the area of the polygon.
Returns
-------
number
Area of the polygon.
"""
if len(self.exterior) < 3:
return 0.0
poly = self.to_shapely_polygon()
return poly.area
@property
def height(self):
"""Compute the height of a bounding box encapsulating the polygon.
The height is computed based on the two exterior coordinates with
lowest and largest x-coordinates.
Returns
-------
number
Height of the polygon.
"""
yy = self.yy
return max(yy) - min(yy)
@property
def width(self):
"""Compute the width of a bounding box encapsulating the polygon.
The width is computed based on the two exterior coordinates with
lowest and largest x-coordinates.
Returns
-------
number
Width of the polygon.
"""
xx = self.xx
return max(xx) - min(xx)
def project_(self, from_shape, to_shape):
"""Project the polygon onto an image with different shape in-place.
The relative coordinates of all points remain the same.
E.g. a point at ``(x=20, y=20)`` on an image
``(width=100, height=200)`` will be projected on a new
image ``(width=200, height=100)`` to ``(x=40, y=10)``.
This is intended for cases where the original image is resized.
It cannot be used for more complex changes (e.g. padding, cropping).
Added in 0.4.0.
Parameters
----------
from_shape : tuple of int
Shape of the original image. (Before resize.)
to_shape : tuple of int
Shape of the new image. (After resize.)
Returns
-------
imgaug.augmentables.polys.Polygon
Polygon object with new coordinates.
The object may have been modified in-place.
"""
self.exterior = project_coords_(self.coords, from_shape, to_shape)
return self
def project(self, from_shape, to_shape):
"""Project the polygon onto an image with different shape.
The relative coordinates of all points remain the same.
E.g. a point at ``(x=20, y=20)`` on an image
``(width=100, height=200)`` will be projected on a new
image ``(width=200, height=100)`` to ``(x=40, y=10)``.
This is intended for cases where the original image is resized.
It cannot be used for more complex changes (e.g. padding, cropping).
Parameters
----------
from_shape : tuple of int
Shape of the original image. (Before resize.)
to_shape : tuple of int
Shape of the new image. (After resize.)
Returns
-------
imgaug.augmentables.polys.Polygon
Polygon object with new coordinates.
"""
return self.deepcopy().project_(from_shape, to_shape)
def find_closest_point_index(self, x, y, return_distance=False):
"""Find the index of the exterior point closest to given coordinates.
"Closeness" is here defined based on euclidean distance.
This method will raise an ``AssertionError`` if the exterior contains
no points.
Parameters
----------
x : number
X-coordinate around which to search for close points.
y : number
Y-coordinate around which to search for close points.
return_distance : bool, optional
Whether to also return the distance of the closest point.
Returns
-------
int
Index of the closest point.
number
Euclidean distance to the closest point.
This value is only returned if `return_distance` was set
to ``True``.
"""
assert len(self.exterior) > 0, (
"Cannot find the closest point on a polygon which's exterior "
"contains no points.")
distances = []
for x2, y2 in self.exterior:
dist = (x2 - x) ** 2 + (y2 - y) ** 2
distances.append(dist)
distances = np.sqrt(distances)
closest_idx = np.argmin(distances)
if return_distance:
return closest_idx, distances[closest_idx]
return closest_idx
def compute_out_of_image_area(self, image):
"""Compute the area of the BB that is outside of the image plane.
Added in 0.4.0.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
float
Total area of the bounding box that is outside of the image plane.
Can be ``0.0``.
"""
polys_clipped = self.clip_out_of_image(image)
if len(polys_clipped) == 0:
return self.area
return self.area - sum([poly.area for poly in polys_clipped])
def compute_out_of_image_fraction(self, image):
"""Compute fraction of polygon area outside of the image plane.
This estimates ``f = A_ooi / A``, where ``A_ooi`` is the area of the
polygon that is outside of the image plane, while ``A`` is the
total area of the bounding box.
Added in 0.4.0.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape
and must contain at least two integers.
Returns
-------
float
Fraction of the polygon area that is outside of the image
plane. Returns ``0.0`` if the polygon is fully inside of
the image plane or has zero points. If the polygon has an area
of zero, the polygon is treated similarly to a :class:`LineString`,
i.e. the fraction of the line that is outside the image plane is
returned.
"""
area = self.area
if area == 0:
return self.to_line_string().compute_out_of_image_fraction(image)
return self.compute_out_of_image_area(image) / area
# TODO keep this method? it is almost an alias for is_out_of_image()
def is_fully_within_image(self, image):
"""Estimate whether the polygon is fully inside an image plane.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape and
must contain at least two ``int`` s.
Returns
-------
bool
``True`` if the polygon is fully inside the image area.
``False`` otherwise.
"""
return not self.is_out_of_image(image, fully=True, partly=True)
# TODO keep this method? it is almost an alias for is_out_of_image()
def is_partly_within_image(self, image):
"""Estimate whether the polygon is at least partially inside an image.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape and
must contain at least two ``int`` s.
Returns
-------
bool
``True`` if the polygon is at least partially inside the image area.
``False`` otherwise.
"""
return not self.is_out_of_image(image, fully=True, partly=False)
def is_out_of_image(self, image, fully=True, partly=False):
"""Estimate whether the polygon is partially/fully outside of an image.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape and
must contain at least two ``int`` s.
fully : bool, optional
Whether to return ``True`` if the polygon is fully outside of the
image area.
partly : bool, optional
Whether to return ``True`` if the polygon is at least partially
outside fo the image area.
Returns
-------
bool
``True`` if the polygon is partially/fully outside of the image
area, depending on defined parameters.
``False`` otherwise.
"""
# TODO this is inconsistent with line strings, which return a default
# value in these cases
if len(self.exterior) == 0:
raise Exception("Cannot determine whether the polygon is inside "
"the image, because it contains no points.")
# The line string is identical to the edge of the polygon.
# If the edge is fully inside the image, we know that the polygon must
# be fully inside the image.
# If the edge is partially outside of the image, we know that the
# polygon is partially outside of the image.
# Only if the edge is fully outside of the image we cannot be sure if
# the polygon's inner area overlaps with the image (e.g. if the
# polygon contains the whole image in it).
ls = self.to_line_string()
if ls.is_fully_within_image(image):
return False
if ls.is_out_of_image(image, fully=False, partly=True):
return partly
# LS is fully outside of the image. Estimate whether there is any
# intersection with the image plane. If so, we know that there is
# partial overlap (full overlap would mean that the LS was fully inside
# the image).
polys = self.clip_out_of_image(image)
if len(polys) > 0:
return partly
return fully
@ia.deprecated(alt_func="Polygon.clip_out_of_image()",
comment="clip_out_of_image() has the exactly same "
"interface.")
def cut_out_of_image(self, image):
"""Cut off all parts of the polygon that are outside of an image."""
return self.clip_out_of_image(image)
# TODO this currently can mess up the order of points - change somehow to
# keep the order
def clip_out_of_image(self, image):
"""Cut off all parts of the polygon that are outside of an image.
This operation may lead to new points being created.
As a single polygon may be split into multiple new polygons, the result
is always a list, which may contain more than one output polygon.
This operation will return an empty list if the polygon is completely
outside of the image plane.
Parameters
----------
image : (H,W,...) ndarray or tuple of int
Image dimensions to use for the clipping of the polygon.
If an ``ndarray``, its shape will be used.
If a ``tuple``, it is assumed to represent the image shape and must
contain at least two ``int`` s.
Returns
-------
list of imgaug.augmentables.polys.Polygon
Polygon, clipped to fall within the image dimensions.
Returned as a ``list``, because the clipping can split the polygon
into multiple parts. The list may also be empty, if the polygon was
fully outside of the image plane.
"""
# load shapely lazily, which makes the dependency more optional
import shapely.geometry
# Shapely polygon conversion requires at least 3 coordinates
if len(self.exterior) == 0:
return []
if len(self.exterior) in [1, 2]:
ls = self.to_line_string(closed=False)
ls_clipped = ls.clip_out_of_image(image)
assert len(ls_clipped) <= 1
if len(ls_clipped) == 0:
return []
return [self.deepcopy(exterior=ls_clipped[0].coords)]
h, w = image.shape[0:2] if ia.is_np_array(image) else image[0:2]
poly_shapely = self.to_shapely_polygon()
poly_image = shapely.geometry.Polygon([(0, 0), (w, 0), (w, h), (0, h)])
multipoly_inter_shapely = poly_shapely.intersection(poly_image)
ignore_types = (shapely.geometry.LineString,
shapely.geometry.MultiLineString,
shapely.geometry.point.Point,
shapely.geometry.MultiPoint)
if isinstance(multipoly_inter_shapely, shapely.geometry.Polygon):
multipoly_inter_shapely = shapely.geometry.MultiPolygon(
[multipoly_inter_shapely])
elif isinstance(multipoly_inter_shapely,
shapely.geometry.MultiPolygon):
# we got a multipolygon from shapely, no need to change anything
# anymore
pass
elif isinstance(multipoly_inter_shapely, ignore_types):
# polygons that become (one or more) lines/points after clipping
# are here ignored
multipoly_inter_shapely = shapely.geometry.MultiPolygon([])
elif isinstance(multipoly_inter_shapely,
shapely.geometry.GeometryCollection):
# Shapely returns GEOMETRYCOLLECTION EMPTY if there is nothing
# remaining after the clip.
assert multipoly_inter_shapely.is_empty
return []
else:
raise Exception(
"Got an unexpected result of type %s from Shapely for "
"image (%d, %d) and polygon %s. This is an internal error. "
"Please report." % (
type(multipoly_inter_shapely), h, w, self.exterior)
)
polygons = []
for poly_inter_shapely in multipoly_inter_shapely.geoms:
polygons.append(Polygon.from_shapely(poly_inter_shapely,
label=self.label))
# Shapely changes the order of points, we try here to preserve it as
# much as possible.
# Note here, that all points of the new polygon might have high
# distance to the points on the old polygon. This can happen if the
# polygon overlaps with the image plane, but all of its points are
# outside of the image plane. The new polygon will not be made up of
# any of the old points.
polygons_reordered = []
for polygon in polygons:
best_idx = None
best_dist = None
for x, y in self.exterior:
point_idx, dist = polygon.find_closest_point_index(
x=x, y=y, return_distance=True)
if best_idx is None or dist < best_dist:
best_idx = point_idx
best_dist = dist
if best_idx is not None:
polygon_reordered = \
polygon.change_first_point_by_index(best_idx)
polygons_reordered.append(polygon_reordered)
return polygons_reordered
def shift_(self, x=0, y=0):
"""Move this polygon along the x/y-axis in-place.
The origin ``(0, 0)`` is at the top left of the image.
Added in 0.4.0.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
Returns
-------
imgaug.augmentables.polys.Polygon
Shifted polygon.
The object may have been modified in-place.
"""
self.exterior[:, 0] += x
self.exterior[:, 1] += y
return self
def shift(self, x=0, y=0, top=None, right=None, bottom=None, left=None):
"""Move this polygon along the x/y-axis.
The origin ``(0, 0)`` is at the top left of the image.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
top : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
top (towards the bottom).
right : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
right (towards the left).
bottom : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
bottom (towards the top).
left : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift this object *from* the
left (towards the right).
Returns
-------
imgaug.augmentables.polys.Polygon
Shifted polygon.
"""
x, y = _normalize_shift_args(
x, y, top=top, right=right, bottom=bottom, left=left)
return self.deepcopy().shift_(x=x, y=y)
# TODO separate this into draw_face_on_image() and draw_border_on_image()
# TODO add tests for line thickness
def draw_on_image(self,
image,
color=(0, 255, 0), color_face=None,
color_lines=None, color_points=None,
alpha=1.0, alpha_face=None,
alpha_lines=None, alpha_points=None,
size=1, size_lines=None, size_points=None,
raise_if_out_of_image=False):
"""Draw the polygon on an image.
Parameters
----------
image : (H,W,C) ndarray
The image onto which to draw the polygon. Usually expected to be
of dtype ``uint8``, though other dtypes are also handled.
color : iterable of int, optional
The color to use for the whole polygon.
Must correspond to the channel layout of the image. Usually RGB.
The values for `color_face`, `color_lines` and `color_points`
will be derived from this color if they are set to ``None``.
This argument has no effect if `color_face`, `color_lines`
and `color_points` are all set anything other than ``None``.
color_face : None or iterable of int, optional
The color to use for the inner polygon area (excluding perimeter).
Must correspond to the channel layout of the image. Usually RGB.
If this is ``None``, it will be derived from ``color * 1.0``.
color_lines : None or iterable of int, optional
The color to use for the line (aka perimeter/border) of the
polygon.
Must correspond to the channel layout of the image. Usually RGB.
If this is ``None``, it will be derived from ``color * 0.5``.
color_points : None or iterable of int, optional
The color to use for the corner points of the polygon.
Must correspond to the channel layout of the image. Usually RGB.
If this is ``None``, it will be derived from ``color * 0.5``.
alpha : float, optional
The opacity of the whole polygon, where ``1.0`` denotes a
completely visible polygon and ``0.0`` an invisible one.
The values for `alpha_face`, `alpha_lines` and `alpha_points`
will be derived from this alpha value if they are set to ``None``.
This argument has no effect if `alpha_face`, `alpha_lines`
and `alpha_points` are all set anything other than ``None``.
alpha_face : None or number, optional
The opacity of the polygon's inner area (excluding the perimeter),
where ``1.0`` denotes a completely visible inner area and ``0.0``
an invisible one.
If this is ``None``, it will be derived from ``alpha * 0.5``.
alpha_lines : None or number, optional
The opacity of the polygon's line (aka perimeter/border),
where ``1.0`` denotes a completely visible line and ``0.0`` an
invisible one.
If this is ``None``, it will be derived from ``alpha * 1.0``.
alpha_points : None or number, optional
The opacity of the polygon's corner points, where ``1.0`` denotes
completely visible corners and ``0.0`` invisible ones.
If this is ``None``, it will be derived from ``alpha * 1.0``.
size : int, optional
Size of the polygon.
The sizes of the line and points are derived from this value,
unless they are set.
size_lines : None or int, optional
Thickness of the polygon's line (aka perimeter/border).
If ``None``, this value is derived from `size`.
size_points : int, optional
Size of the points in pixels.
If ``None``, this value is derived from ``3 * size``.
raise_if_out_of_image : bool, optional
Whether to raise an error if the polygon is fully
outside of the image. If set to ``False``, no error will be
raised and only the parts inside the image will be drawn.
Returns
-------
(H,W,C) ndarray
Image with the polygon drawn on it. Result dtype is the same as the
input dtype.
"""
# pylint: disable=invalid-name
def _assert_not_none(arg_name, arg_value):
assert arg_value is not None, (
"Expected '%s' to not be None, got type %s." % (
arg_name, type(arg_value),))
def _default_to(var, default):
if var is None:
return default
return var
_assert_not_none("color", color)
_assert_not_none("alpha", alpha)
_assert_not_none("size", size)
# FIXME due to the np.array(.) and the assert at ndim==2 below, this
# will always fail on 2D images?
color_face = _default_to(color_face, np.array(color))
color_lines = _default_to(color_lines, np.array(color) * 0.5)
color_points = _default_to(color_points, np.array(color) * 0.5)
alpha_face = _default_to(alpha_face, alpha * 0.5)
alpha_lines = _default_to(alpha_lines, alpha)
alpha_points = _default_to(alpha_points, alpha)
size_lines = _default_to(size_lines, size)
size_points = _default_to(size_points, size * 3)
if image.ndim == 2:
assert ia.is_single_number(color_face), (
"Got a 2D image. Expected then 'color_face' to be a single "
"number, but got %s." % (str(color_face),))
color_face = [color_face]
elif image.ndim == 3 and ia.is_single_number(color_face):
color_face = [color_face] * image.shape[-1]
if alpha_face < 0.01:
alpha_face = 0
elif alpha_face > 0.99:
alpha_face = 1
if raise_if_out_of_image and self.is_out_of_image(image):
raise Exception("Cannot draw polygon %s on image with "
"shape %s." % (str(self), image.shape))
# TODO np.clip to image plane if is_fully_within_image(), similar to
# how it is done for bounding boxes
# TODO improve efficiency by only drawing in rectangle that covers
# poly instead of drawing in the whole image
# TODO for a rectangular polygon, the face coordinates include the
# top/left boundary but not the right/bottom boundary. This may
# be unintuitive when not drawing the boundary. Maybe somehow
# remove the boundary coordinates from the face coordinates after
# generating both?
input_dtype = image.dtype
result = image.astype(np.float32)
rr, cc = skimage.draw.polygon(
self.yy_int, self.xx_int, shape=image.shape)
if len(rr) > 0:
if alpha_face == 1:
result[rr, cc] = np.float32(color_face)
elif alpha_face == 0:
pass
else:
result[rr, cc] = (
(1 - alpha_face) * result[rr, cc, :]
+ alpha_face * np.float32(color_face)
)
ls_open = self.to_line_string(closed=False)
ls_closed = self.to_line_string(closed=True)
result = ls_closed.draw_lines_on_image(
result, color=color_lines, alpha=alpha_lines,
size=size_lines, raise_if_out_of_image=raise_if_out_of_image)
result = ls_open.draw_points_on_image(
result, color=color_points, alpha=alpha_points,
size=size_points, raise_if_out_of_image=raise_if_out_of_image)
if input_dtype.type == np.uint8:
# TODO make clipping more flexible
result = np.clip(np.round(result), 0, 255).astype(input_dtype)
else:
result = result.astype(input_dtype)
return result
# TODO add pad, similar to LineStrings
# TODO add pad_max, similar to LineStrings
# TODO add prevent_zero_size, similar to LineStrings
def extract_from_image(self, image):
"""Extract all image pixels within the polygon area.
This method returns a rectangular image array. All pixels within
that rectangle that do not belong to the polygon area will be filled
with zeros (i.e. they will be black).
The method will also zero-pad the image if the polygon is
partially/fully outside of the image.
Parameters
----------
image : (H,W) ndarray or (H,W,C) ndarray
The image from which to extract the pixels within the polygon.
Returns
-------
(H',W') ndarray or (H',W',C) ndarray
Pixels within the polygon. Zero-padded if the polygon is
partially/fully outside of the image.
"""
assert image.ndim in [2, 3], (
"Expected image of shape (H,W,[C]), got shape %s." % (
image.shape,))
if len(self.exterior) <= 2:
raise Exception("Polygon must be made up of at least 3 points to "
"extract its area from an image.")
bb = self.to_bounding_box()
bb_area = bb.extract_from_image(image)
if self.is_out_of_image(image, fully=True, partly=False):
return bb_area
xx = self.xx_int
yy = self.yy_int
xx_mask = xx - np.min(xx)
yy_mask = yy - np.min(yy)
height_mask = np.max(yy_mask)
width_mask = np.max(xx_mask)
rr_face, cc_face = skimage.draw.polygon(
yy_mask, xx_mask, shape=(height_mask, width_mask))
mask = np.zeros((height_mask, width_mask), dtype=np.bool)
mask[rr_face, cc_face] = True
if image.ndim == 3:
mask = np.tile(mask[:, :, np.newaxis], (1, 1, image.shape[2]))
return bb_area * mask
def change_first_point_by_coords(self, x, y, max_distance=1e-4,
raise_if_too_far_away=True):
"""
Reorder exterior points so that the point closest to given x/y is first.
This method takes a given ``(x,y)`` coordinate, finds the closest
corner point on the exterior and reorders all exterior corner points
so that the found point becomes the first one in the array.
If no matching points are found, an exception is raised.
Parameters
----------
x : number
X-coordinate of the point.
y : number
Y-coordinate of the point.
max_distance : None or number, optional
Maximum distance past which possible matches are ignored.
If ``None`` the distance limit is deactivated.
raise_if_too_far_away : bool, optional
Whether to raise an exception if the closest found point is too
far away (``True``) or simply return an unchanged copy if this
object (``False``).
Returns
-------
imgaug.augmentables.polys.Polygon
Copy of this polygon with the new point order.
"""
if len(self.exterior) == 0:
raise Exception("Cannot reorder polygon points, because it "
"contains no points.")
closest_idx, closest_dist = self.find_closest_point_index(
x=x, y=y, return_distance=True)
if max_distance is not None and closest_dist > max_distance:
if not raise_if_too_far_away:
return self.deepcopy()
closest_point = self.exterior[closest_idx, :]
raise Exception(
"Closest found point (%.9f, %.9f) exceeds max_distance of "
"%.9f exceeded" % (
closest_point[0], closest_point[1], closest_dist))
return self.change_first_point_by_index(closest_idx)
def change_first_point_by_index(self, point_idx):
"""
Reorder exterior points so that the point with given index is first.
This method takes a given index and reorders all exterior corner points
so that the point with that index becomes the first one in the array.
An ``AssertionError`` will be raised if the index does not match
any exterior point's index or the exterior does not contain any points.
Parameters
----------
point_idx : int
Index of the desired starting point.
Returns
-------
imgaug.augmentables.polys.Polygon
Copy of this polygon with the new point order.
"""
assert 0 <= point_idx < len(self.exterior), (
"Expected index of new first point to be in the discrete interval "
"[0..%d). Got index %d." % (len(self.exterior), point_idx))
if point_idx == 0:
return self.deepcopy()
exterior = np.concatenate(
(self.exterior[point_idx:, :], self.exterior[:point_idx, :]),
axis=0
)
return self.deepcopy(exterior=exterior)
def subdivide_(self, points_per_edge):
"""Derive a new poly with ``N`` interpolated points per edge in-place.
See :func:`~imgaug.augmentables.lines.LineString.subdivide` for details.
Added in 0.4.0.
Parameters
----------
points_per_edge : int
Number of points to interpolate on each edge.
Returns
-------
imgaug.augmentables.polys.Polygon
Polygon with subdivided edges.
The object may have been modified in-place.
"""
if len(self.exterior) == 1:
return self
ls = self.to_line_string(closed=True)
ls_sub = ls.subdivide(points_per_edge)
# [:-1] even works if the polygon contains zero points
exterior_subdivided = ls_sub.coords[:-1]
self.exterior = exterior_subdivided
return self
def subdivide(self, points_per_edge):
"""Derive a new polygon with ``N`` interpolated points per edge.
See :func:`~imgaug.augmentables.lines.LineString.subdivide` for details.
Added in 0.4.0.
Parameters
----------
points_per_edge : int
Number of points to interpolate on each edge.
Returns
-------
imgaug.augmentables.polys.Polygon
Polygon with subdivided edges.
"""
return self.deepcopy().subdivide_(points_per_edge)
def to_shapely_polygon(self):
"""Convert this polygon to a ``Shapely`` ``Polygon``.
Returns
-------
shapely.geometry.Polygon
The ``Shapely`` ``Polygon`` matching this polygon's exterior.
"""
# load shapely lazily, which makes the dependency more optional
import shapely.geometry
return shapely.geometry.Polygon(
[(point[0], point[1]) for point in self.exterior])
def to_shapely_line_string(self, closed=False, interpolate=0):
"""Convert this polygon to a ``Shapely`` ``LineString`` object.
Parameters
----------
closed : bool, optional
Whether to return the line string with the last point being
identical to the first point.
interpolate : int, optional
Number of points to interpolate between any pair of two
consecutive points. These points are added to the final line string.
Returns
-------
shapely.geometry.LineString
The ``Shapely`` ``LineString`` matching the polygon's exterior.
"""
return _convert_points_to_shapely_line_string(
self.exterior, closed=closed, interpolate=interpolate)
def to_bounding_box(self):
"""Convert this polygon to a bounding box containing the polygon.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Bounding box that tightly encapsulates the polygon.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.bbs import BoundingBox
xx = self.xx
yy = self.yy
return BoundingBox(x1=min(xx), x2=max(xx),
y1=min(yy), y2=max(yy),
label=self.label)
def to_keypoints(self):
"""Convert this polygon's exterior to ``Keypoint`` instances.
Returns
-------
list of imgaug.augmentables.kps.Keypoint
Exterior vertices as :class:`~imgaug.augmentables.kps.Keypoint`
instances.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.kps import Keypoint
return [Keypoint(x=point[0], y=point[1]) for point in self.exterior]
def to_line_string(self, closed=True):
"""Convert this polygon's exterior to a ``LineString`` instance.
Parameters
----------
closed : bool, optional
Whether to close the line string, i.e. to add the first point of
the `exterior` also as the last point at the end of the line string.
This has no effect if the polygon has a single point or zero
points.
Returns
-------
imgaug.augmentables.lines.LineString
Exterior of the polygon as a line string.
"""
from imgaug.augmentables.lines import LineString
if not closed or len(self.exterior) <= 1:
return LineString(self.exterior, label=self.label)
return LineString(
np.concatenate([self.exterior, self.exterior[0:1, :]], axis=0),
label=self.label)
@staticmethod
def from_shapely(polygon_shapely, label=None):
"""Create a polygon from a ``Shapely`` ``Polygon``.
.. note::
This will remove any holes in the shapely polygon.
Parameters
----------
polygon_shapely : shapely.geometry.Polygon
The shapely polygon.
label : None or str, optional
The label of the new polygon.
Returns
-------
imgaug.augmentables.polys.Polygon
A polygon with the same exterior as the ``Shapely`` ``Polygon``.
"""
# load shapely lazily, which makes the dependency more optional
import shapely.geometry
assert isinstance(polygon_shapely, shapely.geometry.Polygon), (
"Expected the input to be a shapely.geometry.Polgon instance. "
"Got %s." % (type(polygon_shapely),))
# polygon_shapely.exterior can be None if the polygon was
# instantiated without points
has_no_exterior = (
polygon_shapely.exterior is None
or len(polygon_shapely.exterior.coords) == 0)
if has_no_exterior:
return Polygon([], label=label)
exterior = np.float32([[x, y]
for (x, y)
in polygon_shapely.exterior.coords])
return Polygon(exterior, label=label)
def coords_almost_equals(self, other, max_distance=1e-4,
points_per_edge=8):
"""Alias for :func:`Polygon.exterior_almost_equals`.
Parameters
----------
other : imgaug.augmentables.polys.Polygon or (N,2) ndarray or list of tuple
See
:func:`~imgaug.augmentables.polys.Polygon.exterior_almost_equals`.
max_distance : number, optional
See
:func:`~imgaug.augmentables.polys.Polygon.exterior_almost_equals`.
points_per_edge : int, optional
See
:func:`~imgaug.augmentables.polys.Polygon.exterior_almost_equals`.
Returns
-------
bool
Whether the two polygon's exteriors can be viewed as equal
(approximate test).
"""
return self.exterior_almost_equals(
other, max_distance=max_distance, points_per_edge=points_per_edge)
def exterior_almost_equals(self, other, max_distance=1e-4,
points_per_edge=8):
"""Estimate if this and another polygon's exterior are almost identical.
The two exteriors can have different numbers of points, but any point
randomly sampled on the exterior of one polygon should be close to the
closest point on the exterior of the other polygon.
.. note::
This method works in an approximative way. One can come up with
polygons with fairly different shapes that will still be estimated
as equal by this method. In practice however this should be
unlikely to be the case. The probability for something like that
goes down as the interpolation parameter is increased.
Parameters
----------
other : imgaug.augmentables.polys.Polygon or (N,2) ndarray or list of tuple
The other polygon with which to compare the exterior.
If this is an ``ndarray``, it is assumed to represent an exterior.
It must then have dtype ``float32`` and shape ``(N,2)`` with the
second dimension denoting xy-coordinates.
If this is a ``list`` of ``tuple`` s, it is assumed to represent
an exterior. Each tuple then must contain exactly two ``number`` s,
denoting xy-coordinates.
max_distance : number, optional
The maximum euclidean distance between a point on one polygon and
the closest point on the other polygon. If the distance is exceeded
for any such pair, the two exteriors are not viewed as equal. The
points are either the points contained in the polygon's exterior
ndarray or interpolated points between these.
points_per_edge : int, optional
How many points to interpolate on each edge.
Returns
-------
bool
Whether the two polygon's exteriors can be viewed as equal
(approximate test).
"""
if isinstance(other, list):
other = Polygon(np.float32(other))
elif ia.is_np_array(other):
other = Polygon(other)
else:
assert isinstance(other, Polygon), (
"Expected 'other' to be a list of coordinates, a coordinate "
"array or a single Polygon. Got type %s." % (type(other),))
return self.to_line_string(closed=True).coords_almost_equals(
other.to_line_string(closed=True),
max_distance=max_distance,
points_per_edge=points_per_edge
)
def almost_equals(self, other, max_distance=1e-4, points_per_edge=8):
"""
Estimate if this polygon's and another's geometry/labels are similar.
This is the same as
:func:`~imgaug.augmentables.polys.Polygon.exterior_almost_equals` but
additionally compares the labels.
Parameters
----------
other : imgaug.augmentables.polys.Polygon
The other object to compare against. Expected to be a ``Polygon``.
max_distance : float, optional
See
:func:`~imgaug.augmentables.polys.Polygon.exterior_almost_equals`.
points_per_edge : int, optional
See
:func:`~imgaug.augmentables.polys.Polygon.exterior_almost_equals`.
Returns
-------
bool
``True`` if the coordinates are almost equal and additionally
the labels are equal. Otherwise ``False``.
"""
if self.label != other.label:
return False
return self.exterior_almost_equals(
other, max_distance=max_distance, points_per_edge=points_per_edge)
def copy(self, exterior=None, label=None):
"""Create a shallow copy of this object.
Parameters
----------
exterior : list of imgaug.augmentables.kps.Keypoint or list of tuple or (N,2) ndarray, optional
List of points defining the polygon. See
:func:`~imgaug.augmentables.polys.Polygon.__init__` for details.
label : None or str, optional
If not ``None``, the ``label`` of the copied object will be set
to this value.
Returns
-------
imgaug.augmentables.polys.Polygon
Shallow copy.
"""
return self.deepcopy(exterior=exterior, label=label)
def deepcopy(self, exterior=None, label=None):
"""Create a deep copy of this object.
Parameters
----------
exterior : list of Keypoint or list of tuple or (N,2) ndarray, optional
List of points defining the polygon. See
`imgaug.augmentables.polys.Polygon.__init__` for details.
label : None or str
If not ``None``, the ``label`` of the copied object will be set
to this value.
Returns
-------
imgaug.augmentables.polys.Polygon
Deep copy.
"""
return Polygon(
exterior=np.copy(self.exterior) if exterior is None else exterior,
label=self.label if label is None else label)
def __getitem__(self, indices):
"""Get the coordinate(s) with given indices.
Added in 0.4.0.
Returns
-------
ndarray
xy-coordinate(s) as ``ndarray``.
"""
return self.exterior[indices]
def __iter__(self):
"""Iterate over the coordinates of this instance.
Added in 0.4.0.
Yields
------
ndarray
An ``(2,)`` ``ndarray`` denoting an xy-coordinate pair.
"""
return iter(self.exterior)
def __repr__(self):
return self.__str__()
def __str__(self):
points_str = ", ".join([
"(x=%.3f, y=%.3f)" % (point[0], point[1])
for point
in self.exterior])
return "Polygon([%s] (%d points), label=%s)" % (
points_str, len(self.exterior), self.label)
# TODO add tests for this
class PolygonsOnImage(IAugmentable):
"""Container for all polygons on a single image.
Parameters
----------
polygons : list of imgaug.augmentables.polys.Polygon
List of polygons on the image.
shape : tuple of int
The shape of the image on which the objects are placed, i.e. the
result of ``image.shape``.
Should include the number of channels, not only height and width.
Examples
--------
>>> import numpy as np
>>> from imgaug.augmentables.polys import Polygon, PolygonsOnImage
>>> image = np.zeros((100, 100))
>>> polys = [
>>> Polygon([(0.5, 0.5), (100.5, 0.5), (100.5, 100.5), (0.5, 100.5)]),
>>> Polygon([(50.5, 0.5), (100.5, 50.5), (50.5, 100.5), (0.5, 50.5)])
>>> ]
>>> polys_oi = PolygonsOnImage(polys, shape=image.shape)
"""
def __init__(self, polygons, shape):
self.polygons = polygons
self.shape = _handle_on_image_shape(shape, self)
@property
def items(self):
"""Get the polygons in this container.
Added in 0.4.0.
Returns
-------
list of Polygon
Polygons within this container.
"""
return self.polygons
@items.setter
def items(self, value):
"""Set the polygons in this container.
Added in 0.4.0.
Parameters
----------
value : list of Polygon
Polygons within this container.
"""
self.polygons = value
@property
def empty(self):
"""Estimate whether this object contains zero polygons.
Returns
-------
bool
``True`` if this object contains zero polygons.
"""
return len(self.polygons) == 0
def on_(self, image):
"""Project all polygons from one image shape to a new one in-place.
Added in 0.4.0.
Parameters
----------
image : ndarray or tuple of int
New image onto which the polygons are to be projected.
May also simply be that new image's shape ``tuple``.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Object containing all projected polygons.
The object and its items may have been modified in-place.
"""
# pylint: disable=invalid-name
on_shape = normalize_imglike_shape(image)
if on_shape[0:2] == self.shape[0:2]:
self.shape = on_shape # channels may differ
return self
for i, item in enumerate(self.items):
self.polygons[i] = item.project_(self.shape, on_shape)
self.shape = on_shape
return self
def on(self, image):
"""Project all polygons from one image shape to a new one.
Parameters
----------
image : ndarray or tuple of int
New image onto which the polygons are to be projected.
May also simply be that new image's shape ``tuple``.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Object containing all projected polygons.
"""
# pylint: disable=invalid-name
return self.deepcopy().on_(image)
def draw_on_image(self,
image,
color=(0, 255, 0), color_face=None,
color_lines=None, color_points=None,
alpha=1.0, alpha_face=None,
alpha_lines=None, alpha_points=None,
size=1, size_lines=None, size_points=None,
raise_if_out_of_image=False):
"""Draw all polygons onto a given image.
Parameters
----------
image : (H,W,C) ndarray
The image onto which to draw the bounding boxes.
This image should usually have the same shape as set in
``PolygonsOnImage.shape``.
color : iterable of int, optional
The color to use for the whole polygons.
Must correspond to the channel layout of the image. Usually RGB.
The values for `color_face`, `color_lines` and `color_points`
will be derived from this color if they are set to ``None``.
This argument has no effect if `color_face`, `color_lines`
and `color_points` are all set anything other than ``None``.
color_face : None or iterable of int, optional
The color to use for the inner polygon areas (excluding perimeters).
Must correspond to the channel layout of the image. Usually RGB.
If this is ``None``, it will be derived from ``color * 1.0``.
color_lines : None or iterable of int, optional
The color to use for the lines (aka perimeters/borders) of the
polygons. Must correspond to the channel layout of the image.
Usually RGB. If this is ``None``, it will be derived
from ``color * 0.5``.
color_points : None or iterable of int, optional
The color to use for the corner points of the polygons.
Must correspond to the channel layout of the image. Usually RGB.
If this is ``None``, it will be derived from ``color * 0.5``.
alpha : float, optional
The opacity of the whole polygons, where ``1.0`` denotes
completely visible polygons and ``0.0`` invisible ones.
The values for `alpha_face`, `alpha_lines` and `alpha_points`
will be derived from this alpha value if they are set to ``None``.
This argument has no effect if `alpha_face`, `alpha_lines`
and `alpha_points` are all set anything other than ``None``.
alpha_face : None or number, optional
The opacity of the polygon's inner areas (excluding the perimeters),
where ``1.0`` denotes completely visible inner areas and ``0.0``
invisible ones.
If this is ``None``, it will be derived from ``alpha * 0.5``.
alpha_lines : None or number, optional
The opacity of the polygon's lines (aka perimeters/borders),
where ``1.0`` denotes completely visible perimeters and ``0.0``
invisible ones.
If this is ``None``, it will be derived from ``alpha * 1.0``.
alpha_points : None or number, optional
The opacity of the polygon's corner points, where ``1.0`` denotes
completely visible corners and ``0.0`` invisible ones.
Currently this is an on/off choice, i.e. only ``0.0`` or ``1.0``
are allowed.
If this is ``None``, it will be derived from ``alpha * 1.0``.
size : int, optional
Size of the polygons.
The sizes of the line and points are derived from this value,
unless they are set.
size_lines : None or int, optional
Thickness of the polygon lines (aka perimeter/border).
If ``None``, this value is derived from `size`.
size_points : int, optional
The size of all corner points. If set to ``C``, each corner point
will be drawn as a square of size ``C x C``.
raise_if_out_of_image : bool, optional
Whether to raise an error if any polygon is fully
outside of the image. If set to False, no error will be raised and
only the parts inside the image will be drawn.
Returns
-------
(H,W,C) ndarray
Image with drawn polygons.
"""
for poly in self.polygons:
image = poly.draw_on_image(
image,
color=color,
color_face=color_face,
color_lines=color_lines,
color_points=color_points,
alpha=alpha,
alpha_face=alpha_face,
alpha_lines=alpha_lines,
alpha_points=alpha_points,
size=size,
size_lines=size_lines,
size_points=size_points,
raise_if_out_of_image=raise_if_out_of_image
)
return image
def remove_out_of_image_(self, fully=True, partly=False):
"""Remove all polygons that are fully/partially OOI in-place.
'OOI' is the abbreviation for 'out of image'.
Added in 0.4.0.
Parameters
----------
fully : bool, optional
Whether to remove polygons that are fully outside of the image.
partly : bool, optional
Whether to remove polygons that are partially outside of the image.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Reduced set of polygons. Those that are fully/partially
outside of the given image plane are removed.
The object and its items may have been modified in-place.
"""
self.polygons = [
poly for poly in self.polygons
if not poly.is_out_of_image(self.shape, fully=fully, partly=partly)
]
return self
def remove_out_of_image(self, fully=True, partly=False):
"""Remove all polygons that are fully/partially outside of an image.
Parameters
----------
fully : bool, optional
Whether to remove polygons that are fully outside of the image.
partly : bool, optional
Whether to remove polygons that are partially outside of the image.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Reduced set of polygons. Those that are fully/partially
outside of the given image plane are removed.
"""
return self.deepcopy().remove_out_of_image_(fully, partly)
def remove_out_of_image_fraction_(self, fraction):
"""Remove all Polys with an OOI fraction of ``>=fraction`` in-place.
Added in 0.4.0.
Parameters
----------
fraction : number
Minimum out of image fraction that a polygon has to have in
order to be removed. A fraction of ``1.0`` removes only polygons
that are ``100%`` outside of the image. A fraction of ``0.0``
removes all polygons.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Reduced set of polygons, with those that had an out of image
fraction greater or equal the given one removed.
The object and its items may have been modified in-place.
"""
return _remove_out_of_image_fraction_(self, fraction)
def remove_out_of_image_fraction(self, fraction):
"""Remove all Polys with an out of image fraction of ``>=fraction``.
Added in 0.4.0.
Parameters
----------
fraction : number
Minimum out of image fraction that a polygon has to have in
order to be removed. A fraction of ``1.0`` removes only polygons
that are ``100%`` outside of the image. A fraction of ``0.0``
removes all polygons.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Reduced set of polygons, with those that had an out of image
fraction greater or equal the given one removed.
"""
return self.copy().remove_out_of_image_fraction_(fraction)
def clip_out_of_image_(self):
"""Clip off all parts from all polygons that are OOI in-place.
'OOI' is the abbreviation for 'out of image'.
.. note::
The result can contain fewer polygons than the input did. That
happens when a polygon is fully outside of the image plane.
.. note::
The result can also contain *more* polygons than the input
did. That happens when distinct parts of a polygon are only
connected by areas that are outside of the image plane and hence
will be clipped off, resulting in two or more unconnected polygon
parts that are left in the image plane.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Polygons, clipped to fall within the image dimensions.
The count of output polygons may differ from the input count.
The object and its items may have been modified in-place.
"""
self.polygons = [
poly_clipped
for poly in self.polygons
for poly_clipped in poly.clip_out_of_image(self.shape)]
return self
def clip_out_of_image(self):
"""Clip off all parts from all polygons that are outside of an image.
.. note::
The result can contain fewer polygons than the input did. That
happens when a polygon is fully outside of the image plane.
.. note::
The result can also contain *more* polygons than the input
did. That happens when distinct parts of a polygon are only
connected by areas that are outside of the image plane and hence
will be clipped off, resulting in two or more unconnected polygon
parts that are left in the image plane.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Polygons, clipped to fall within the image dimensions.
The count of output polygons may differ from the input count.
"""
return self.copy().clip_out_of_image_()
def shift_(self, x=0, y=0):
"""Move the polygons along the x/y-axis in-place.
The origin ``(0, 0)`` is at the top left of the image.
Added in 0.4.0.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Shifted polygons.
"""
for i, poly in enumerate(self.polygons):
self.polygons[i] = poly.shift_(x=x, y=y)
return self
def shift(self, x=0, y=0, top=None, right=None, bottom=None, left=None):
"""Move the polygons along the x/y-axis.
The origin ``(0, 0)`` is at the top left of the image.
Parameters
----------
x : number, optional
Value to be added to all x-coordinates. Positive values shift
towards the right images.
y : number, optional
Value to be added to all y-coordinates. Positive values shift
towards the bottom images.
top : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
top (towards the bottom).
right : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
right (towads the left).
bottom : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
bottom (towards the top).
left : None or int, optional
Deprecated since 0.4.0.
Amount of pixels by which to shift all objects *from* the
left (towards the right).
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Shifted polygons.
"""
x, y = _normalize_shift_args(
x, y, top=top, right=right, bottom=bottom, left=left)
return self.deepcopy().shift_(x=x, y=y)
def subdivide_(self, points_per_edge):
"""Interpolate ``N`` points on each polygon.
Added in 0.4.0.
Parameters
----------
points_per_edge : int
Number of points to interpolate on each edge.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Subdivided polygons.
"""
for i, poly in enumerate(self.polygons):
self.polygons[i] = poly.subdivide_(points_per_edge)
return self
def subdivide(self, points_per_edge):
"""Interpolate ``N`` points on each polygon.
Added in 0.4.0.
Parameters
----------
points_per_edge : int
Number of points to interpolate on each edge.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Subdivided polygons.
"""
return self.deepcopy().subdivide_(points_per_edge)
def to_xy_array(self):
"""Convert all polygon coordinates to one array of shape ``(N,2)``.
Added in 0.4.0.
Returns
-------
(N, 2) ndarray
Array containing all xy-coordinates of all polygons within this
instance.
"""
if self.empty:
return np.zeros((0, 2), dtype=np.float32)
return np.concatenate([poly.exterior for poly in self.polygons])
def fill_from_xy_array_(self, xy):
"""Modify the corner coordinates of all polygons in-place.
.. note::
This currently expects that `xy` contains exactly as many
coordinates as the polygons within this instance have corner
points. Otherwise, an ``AssertionError`` will be raised.
.. warning::
This does not validate the new coordinates or repair the resulting
polygons. If bad coordinates are provided, the result will be
invalid polygons (e.g. self-intersections).
Added in 0.4.0.
Parameters
----------
xy : (N, 2) ndarray or iterable of iterable of number
XY-Coordinates of ``N`` corner points. ``N`` must match the
number of corner points in all polygons within this instance.
Returns
-------
PolygonsOnImage
This instance itself, with updated coordinates.
Note that the instance was modified in-place.
"""
xy = np.array(xy, dtype=np.float32)
# note that np.array([]) is (0,), not (0, 2)
assert xy.shape[0] == 0 or (xy.ndim == 2 and xy.shape[-1] == 2), ( # pylint: disable=unsubscriptable-object
"Expected input array to have shape (N,2), "
"got shape %s." % (xy.shape,))
counter = 0
for poly in self.polygons:
nb_points = len(poly.exterior)
assert counter + nb_points <= len(xy), (
"Received fewer points than there are corner points in the "
"exteriors of all polygons. Got %d points, expected %d." % (
len(xy), sum([len(p.exterior) for p in self.polygons])))
poly.exterior[:, ...] = xy[counter:counter+nb_points]
counter += nb_points
assert counter == len(xy), (
"Expected to get exactly as many xy-coordinates as there are "
"points in the exteriors of all polygons within this instance. "
"Got %d points, could only assign %d points." % (
len(xy), counter,))
return self
def to_keypoints_on_image(self):
"""Convert the polygons to one ``KeypointsOnImage`` instance.
Added in 0.4.0.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
A keypoints instance containing ``N`` coordinates for a total
of ``N`` points in all exteriors of the polygons within this
container. Order matches the order in ``polygons``.
"""
from . import KeypointsOnImage
if self.empty:
return KeypointsOnImage([], shape=self.shape)
exteriors = np.concatenate(
[poly.exterior for poly in self.polygons],
axis=0)
return KeypointsOnImage.from_xy_array(exteriors, shape=self.shape)
def invert_to_keypoints_on_image_(self, kpsoi):
"""Invert the output of ``to_keypoints_on_image()`` in-place.
This function writes in-place into this ``PolygonsOnImage``
instance.
Added in 0.4.0.
Parameters
----------
kpsoi : imgaug.augmentables.kps.KeypointsOnImages
Keypoints to convert back to polygons, i.e. the outputs
of ``to_keypoints_on_image()``.
Returns
-------
PolygonsOnImage
Polygons container with updated coordinates.
Note that the instance is also updated in-place.
"""
polys = self.polygons
exteriors = [poly.exterior for poly in polys]
nb_points_exp = sum([len(exterior) for exterior in exteriors])
assert len(kpsoi.keypoints) == nb_points_exp, (
"Expected %d coordinates, got %d." % (
nb_points_exp, len(kpsoi.keypoints)))
xy_arr = kpsoi.to_xy_array()
counter = 0
for poly in polys:
exterior = poly.exterior
exterior[:, :] = xy_arr[counter:counter+len(exterior), :]
counter += len(exterior)
self.shape = kpsoi.shape
return self
def copy(self, polygons=None, shape=None):
"""Create a shallow copy of this object.
Parameters
----------
polygons : None or list of imgaug.augmentables.polys.Polygons, optional
List of polygons on the image.
If not ``None``, then the ``polygons`` attribute of the copied
object will be set to this value.
shape : None or tuple of int or ndarray, optional
The shape of the image on which the objects are placed.
Either an image with shape ``(H,W,[C])`` or a tuple denoting
such an image shape.
If not ``None``, then the ``shape`` attribute of the copied object
will be set to this value.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Shallow copy.
"""
if polygons is None:
polygons = self.polygons[:]
if shape is None:
# use tuple() here in case the shape was provided as a list
shape = tuple(self.shape)
return PolygonsOnImage(polygons, shape)
def deepcopy(self, polygons=None, shape=None):
"""Create a deep copy of this object.
Parameters
----------
polygons : None or list of imgaug.augmentables.polys.Polygons, optional
List of polygons on the image.
If not ``None``, then the ``polygons`` attribute of the copied
object will be set to this value.
shape : None or tuple of int or ndarray, optional
The shape of the image on which the objects are placed.
Either an image with shape ``(H,W,[C])`` or a tuple denoting
such an image shape.
If not ``None``, then the ``shape`` attribute of the copied object
will be set to this value.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Deep copy.
"""
# Manual copy is far faster than deepcopy, so use manual copy here.
if polygons is None:
polygons = [poly.deepcopy() for poly in self.polygons]
if shape is None:
# use tuple() here in case the shape was provided as a list
shape = tuple(self.shape)
return PolygonsOnImage(polygons, shape)
def __getitem__(self, indices):
"""Get the polygon(s) with given indices.
Added in 0.4.0.
Returns
-------
list of imgaug.augmentables.polys.Polygon
Polygon(s) with given indices.
"""
return self.polygons[indices]
def __iter__(self):
"""Iterate over the polygons in this container.
Added in 0.4.0.
Yields
------
Polygon
A polygon in this container.
The order is identical to the order in the polygon list
provided upon class initialization.
"""
return iter(self.polygons)
def __len__(self):
"""Get the number of items in this instance.
Added in 0.4.0.
Returns
-------
int
Number of items in this instance.
"""
return len(self.items)
def __repr__(self):
return self.__str__()
def __str__(self):
return "PolygonsOnImage(%s, shape=%s)" % (
str(self.polygons), self.shape)
def _convert_points_to_shapely_line_string(points, closed=False,
interpolate=0):
# load shapely lazily, which makes the dependency more optional
import shapely.geometry
if len(points) <= 1:
raise Exception(
"Conversion to shapely line string requires at least two points, "
"but points input contains only %d points." % (len(points),))
points_tuples = [(point[0], point[1]) for point in points]
# interpolate points between each consecutive pair of points
if interpolate > 0:
points_tuples = interpolate_points(points_tuples, interpolate)
# close if requested and not yet closed
# used here intentionally `points` instead of `points_tuples`
if closed and len(points) > 1:
points_tuples.append(points_tuples[0])
return shapely.geometry.LineString(points_tuples)
class _ConcavePolygonRecoverer(object):
def __init__(self, threshold_duplicate_points=1e-4, noise_strength=1e-4,
oversampling=0.01, max_segment_difference=1e-4):
self.threshold_duplicate_points = threshold_duplicate_points
self.noise_strength = noise_strength
self.oversampling = oversampling
self.max_segment_difference = max_segment_difference
# this limits the maximum amount of points after oversampling, i.e.
# if N points are input into oversampling, then M oversampled points
# are generated such that N+M <= this value
self.oversample_up_to_n_points_max = 75
# ----
# parameters for _fit_best_valid_polygon()
# ----
# how many changes may be done max to the initial (convex hull) polygon
# before simply returning the result
self.fit_n_changes_max = 100
# for how many iterations the optimization loop may run max
# before simply returning the result
self.fit_n_iters_max = 3
# how far (wrt. to their position in the input list) two points may be
# apart max to consider adding an edge between them (in the first loop
# iteration and the ones after that)
self.fit_max_dist_first_iter = 1
self.fit_max_dist_other_iters = 2
# The fit loop first generates candidate edges and then modifies the
# polygon based on these candidates. This limits the maximum amount
# of considered candidates. If the number is less than the possible
# number of candidates, they are randomly subsampled. Values beyond
# 100 significantly increase runtime (for polygons that reach that
# number).
self.fit_n_candidates_before_sort_max = 100
# If abs(x) or abs(y) of any coordinate of a polygon is beyond this
# value, no intersection points will be computed anymore. That is done,
# because the underlying library to find these points uses float
# values as keys and may therefore start to encounter inaccuracies
# leading to exceptions within that library.
self.limit_coords_values_for_inter_search = 50000
# Rounding of coordinates to use before feeding them into the
# library to search for intersection points. Note that the library
# was set to also use a corresponding eps of 1e-4.
self.decimals = 4
def recover_from(self, new_exterior, old_polygon, random_state=0):
assert isinstance(new_exterior, list) or (
ia.is_np_array(new_exterior)
and new_exterior.ndim == 2
and new_exterior.shape[1] == 2), (
"Expected exterior as list or (N,2) ndarray, got type %s." % (
type(new_exterior),))
assert len(new_exterior) >= 3, \
"Cannot recover a concave polygon from less than three points."
# create Polygon instance, if it is already valid then just return
# immediately
polygon = old_polygon.deepcopy(exterior=new_exterior)
if polygon.is_valid:
return polygon
random_state = iarandom.RNG.create_if_not_rng_(random_state)
rss = random_state.duplicate(3)
# remove consecutive duplicate points
new_exterior = self._remove_consecutive_duplicate_points(new_exterior)
# check that points are not all identical or on a line
new_exterior = self._fix_polygon_is_line(new_exterior, rss[0])
# jitter duplicate points
new_exterior = self._jitter_duplicate_points(new_exterior, rss[1])
# generate intersection points
segment_add_points = self._generate_intersection_points(
new_exterior, decimals=self.decimals)
# oversample points around intersections
if self.oversampling is not None and self.oversampling > 0:
segment_add_points = self._oversample_intersection_points(
new_exterior, segment_add_points)
# integrate new points into exterior
new_exterior_inter = self._insert_intersection_points(
new_exterior, segment_add_points)
# find best fit polygon, starting from convext polygon
new_exterior_concave_ids = self._fit_best_valid_polygon(
new_exterior_inter, rss[2])
new_exterior_concave = [
new_exterior_inter[idx] for idx in new_exterior_concave_ids]
# TODO return new_exterior_concave here instead of polygon, leave it to
# caller to decide what to do with it
return old_polygon.deepcopy(exterior=new_exterior_concave)
def _remove_consecutive_duplicate_points(self, points):
result = []
for point in points:
if result:
dist = np.linalg.norm(
np.float32(point) - np.float32(result[-1]))
is_same = (dist < self.threshold_duplicate_points)
if not is_same:
result.append(point)
else:
result.append(point)
if len(result) >= 2:
dist = np.linalg.norm(
np.float32(result[0]) - np.float32(result[-1]))
is_same = (dist < self.threshold_duplicate_points)
result = result[0:-1] if is_same else result
return result
# fix polygons for which all points are on a line
def _fix_polygon_is_line(self, exterior, random_state):
assert len(exterior) >= 3, (
"Can only fix line-like polygons with an exterior containing at "
"least 3 points. Got one with %d points." % (len(exterior),))
noise_strength = self.noise_strength
while self._is_polygon_line(exterior):
noise = random_state.uniform(
-noise_strength, noise_strength, size=(len(exterior), 2)
).astype(np.float32)
exterior = [(point[0] + noise_i[0], point[1] + noise_i[1])
for point, noise_i in zip(exterior, noise)]
noise_strength = noise_strength * 10
assert noise_strength > 0, (
"Expected noise strength to be >0, got %.4f." % (
noise_strength,))
return exterior
@classmethod
def _is_polygon_line(cls, exterior):
vec_down = np.float32([0, 1])
point1 = exterior[0]
angles = set()
for point2 in exterior[1:]:
vec = np.float32(point2) - np.float32(point1)
angle = ia.angle_between_vectors(vec_down, vec)
angles.add(int(angle * 1000))
return len(angles) <= 1
def _jitter_duplicate_points(self, exterior, random_state):
def _find_duplicates(exterior_with_duplicates):
points_map = collections.defaultdict(list)
for i, point in enumerate(exterior_with_duplicates):
# we use 10/x here to be a bit more lenient, the precise
# distance test is further below
x = int(np.round(point[0]
* ((1/10) / self.threshold_duplicate_points)))
y = int(np.round(point[1]
* ((1/10) / self.threshold_duplicate_points)))
for direction0 in [-1, 0, 1]:
for direction1 in [-1, 0, 1]:
points_map[(x+direction0, y+direction1)].append(i)
duplicates = [False] * len(exterior_with_duplicates)
for key in points_map:
candidates = points_map[key]
for i, p0_idx in enumerate(candidates):
p0_idx = candidates[i]
point0 = exterior_with_duplicates[p0_idx]
if duplicates[p0_idx]:
continue
for j in range(i+1, len(candidates)):
p1_idx = candidates[j]
point1 = exterior_with_duplicates[p1_idx]
if duplicates[p1_idx]:
continue
dist = np.sqrt(
(point0[0] - point1[0])**2
+ (point0[1] - point1[1])**2)
if dist < self.threshold_duplicate_points:
duplicates[p1_idx] = True
return duplicates
noise_strength = self.noise_strength
assert noise_strength > 0, (
"Expected noise strength to be >0, got %.4f." % (noise_strength,))
exterior = exterior[:]
converged = False
while not converged:
duplicates = _find_duplicates(exterior)
if any(duplicates):
noise = random_state.uniform(
-self.noise_strength,
self.noise_strength,
size=(len(exterior), 2)
).astype(np.float32)
for i, is_duplicate in enumerate(duplicates):
if is_duplicate:
exterior[i] = (
exterior[i][0] + noise[i][0],
exterior[i][1] + noise[i][1])
noise_strength *= 10
else:
converged = True
return exterior
# TODO remove?
@classmethod
def _calculate_circumference(cls, points):
assert len(points) >= 3, (
"Need at least 3 points on the exterior to compute the "
"circumference. Got %d." % (len(points),))
points = np.array(points, dtype=np.float32)
points_matrix = np.zeros((len(points), 4), dtype=np.float32)
points_matrix[:, 0:2] = points
points_matrix[0:-1, 2:4] = points_matrix[1:, 0:2]
points_matrix[-1, 2:4] = points_matrix[0, 0:2]
distances = np.linalg.norm(
points_matrix[:, 0:2] - points_matrix[:, 2:4], axis=1)
return np.sum(distances)
def _generate_intersection_points(self, exterior,
one_point_per_intersection=True,
decimals=4):
# pylint: disable=broad-except
largest_value = np.max(np.abs(np.array(exterior, dtype=np.float32)))
too_large_values = (
largest_value > self.limit_coords_values_for_inter_search)
if too_large_values:
ia.warn(
"Encountered during polygon repair a polygon with extremely "
"large coordinate values beyond %d. Will skip intersection "
"point computation for that polygon. This avoids exceptions "
"and is -- due to the extreme distortion -- likely pointless "
"anyways (i.e. the polygon is already broken beyond repair). "
"Try using weaker augmentation parameters to avoid such "
"large coordinate values." % (
self.limit_coords_values_for_inter_search,)
)
return [[] for _ in range(len(exterior))]
if ia.is_np_array(exterior):
exterior = list(exterior)
assert isinstance(exterior, list), (
"Expected 'exterior' to be a list or a ndarray. "
"Got type %s." % (type(exterior),))
assert all([len(point) == 2 for point in exterior]), (
"Expected 'exterior' to contain (x,y) coordinate pairs. "
"Got lengths %s." % (
", ".join([str(len(point)) for point in exterior])))
if len(exterior) <= 0:
return []
# use (*[i][0], *[i][1]) formulation here instead of just *[i],
# because this way we convert numpy arrays to tuples of floats, which
# is required by isect_segments_include_segments
segments = [
(
(
np.round(float(exterior[i][0]), decimals),
np.round(float(exterior[i][1]), decimals)
),
(
np.round(float(exterior[(i + 1) % len(exterior)][0]),
decimals),
np.round(float(exterior[(i + 1) % len(exterior)][1]),
decimals)
)
)
for i in range(len(exterior))
]
# returns [(point, [(segment_p0, segment_p1), ..]), ...]
from imgaug.external.poly_point_isect_py2py3 import (
isect_segments_include_segments)
try:
intersections = isect_segments_include_segments(segments)
except Exception as exc:
# Exceptions in the segment intersection search can at least
# happen due to large float coords (the library uses
# floats as indices, which is bound to cause inaccuracies).
# Usually such exceptions should not appear, as too large
# coordinate values are already caught at the start of this
# function. For the case that there are more errors, this block
# will prevent a full crash.
ia.warn(
"Encountered exception %s during polygon repair in segment "
"intersection computation. Will skip that step." % (
str(exc),))
traceback.print_exc()
return [[] for _ in range(len(exterior))]
# estimate to which segment the found intersection points belong
segments_add_points = [[] for _ in range(len(segments))]
for point, associated_segments in intersections:
# the intersection point may be associated with multiple segments,
# but we only want to add it once, so pick the first segment
if one_point_per_intersection:
associated_segments = [associated_segments[0]]
for seg_inter_p0, seg_inter_p1 in associated_segments:
diffs = []
dists = []
for seg_p0, seg_p1 in segments:
dist_p0p0 = np.linalg.norm(seg_p0 - np.array(seg_inter_p0))
dist_p1p1 = np.linalg.norm(seg_p1 - np.array(seg_inter_p1))
dist_p0p1 = np.linalg.norm(seg_p0 - np.array(seg_inter_p1))
dist_p1p0 = np.linalg.norm(seg_p1 - np.array(seg_inter_p0))
diff = min(dist_p0p0 + dist_p1p1, dist_p0p1 + dist_p1p0)
diffs.append(diff)
dists.append(np.linalg.norm(
(seg_p0[0] - point[0], seg_p0[1] - point[1])
))
min_diff = np.min(diffs)
if min_diff < self.max_segment_difference:
idx = int(np.argmin(diffs))
segments_add_points[idx].append((point, dists[idx]))
else:
ia.warn(
"Couldn't find fitting segment in "
"_generate_intersection_points(). Ignoring "
"intersection point.")
# sort intersection points by their distance to point 0 in each segment
# (clockwise ordering, this does something only for segments with
# >=2 intersection points)
segment_add_points_sorted = []
for idx in range(len(segments_add_points)):
points = [t[0] for t in segments_add_points[idx]]
dists = [t[1] for t in segments_add_points[idx]]
if len(points) < 2:
segment_add_points_sorted.append(points)
else:
both = sorted(zip(points, dists), key=lambda t: t[1])
# keep points, drop distances
segment_add_points_sorted.append([a for a, _b in both])
return segment_add_points_sorted
def _oversample_intersection_points(self, exterior, segment_add_points):
# segment_add_points must be sorted
if self.oversampling is None or self.oversampling <= 0:
return segment_add_points
segment_add_points_sorted_overs = [
[] for _ in range(len(segment_add_points))]
n_points = len(exterior)
for i, last in enumerate(exterior):
for j, p_inter in enumerate(segment_add_points[i]):
direction = (p_inter[0] - last[0], p_inter[1] - last[1])
if j == 0:
# previous point was non-intersection, place 1 new point
oversample = [1.0 - self.oversampling]
else:
# previous point was intersection, place 2 new points
oversample = [self.oversampling, 1.0 - self.oversampling]
for dist in oversample:
point_over = (last[0] + dist * direction[0],
last[1] + dist * direction[1])
segment_add_points_sorted_overs[i].append(point_over)
segment_add_points_sorted_overs[i].append(p_inter)
last = p_inter
is_last_in_group = (j == len(segment_add_points[i]) - 1)
if is_last_in_group:
# previous point was oversampled, next point is
# non-intersection, place 1 new point between the two
exterior_point = exterior[(i + 1) % len(exterior)]
direction = (exterior_point[0] - last[0],
exterior_point[1] - last[1])
segment_add_points_sorted_overs[i].append(
(last[0] + self.oversampling * direction[0],
last[1] + self.oversampling * direction[1])
)
last = segment_add_points_sorted_overs[i][-1]
n_points += len(segment_add_points_sorted_overs[i])
if n_points > self.oversample_up_to_n_points_max:
return segment_add_points_sorted_overs
return segment_add_points_sorted_overs
@classmethod
def _insert_intersection_points(cls, exterior, segment_add_points):
# segment_add_points must be sorted
assert len(exterior) == len(segment_add_points), (
"Expected one entry in 'segment_add_points' for every point in "
"the exterior. Got %d (segment_add_points) and %d (exterior) "
"entries instead." % (len(segment_add_points), len(exterior)))
exterior_interp = []
for i, point0 in enumerate(exterior):
point0 = exterior[i]
exterior_interp.append(point0)
for p_inter in segment_add_points[i]:
exterior_interp.append(p_inter)
return exterior_interp
def _fit_best_valid_polygon(self, points, random_state):
if len(points) < 2:
return None
def _compute_distance_point_to_line(point, line_start, line_end):
x_diff = line_end[0] - line_start[0]
y_diff = line_end[1] - line_start[1]
num = abs(
y_diff*point[0] - x_diff*point[1]
+ line_end[0]*line_start[1] - line_end[1]*line_start[0]
)
den = np.sqrt(y_diff**2 + x_diff**2)
if den == 0:
return np.sqrt(
(point[0] - line_start[0])**2
+ (point[1] - line_start[1])**2)
return num / den
poly = Polygon(points)
if poly.is_valid:
return sm.xrange(len(points))
hull = scipy.spatial.ConvexHull(points)
points_kept = list(hull.vertices)
points_left = [i for i in range(len(points)) if i not in points_kept]
iteration = 0
n_changes = 0
converged = False
while not converged:
candidates = []
# estimate distance metrics for points-segment pairs:
# (1) distance (in vertices) between point and segment-start-point
# in original input point chain
# (2) euclidean distance between point and segment/line
# TODO this can be done more efficiently by caching the values and
# only computing distances to segments that have changed in
# the last iteration
# TODO these distances are not really the best metrics here.
# Something like IoU between new and old (invalid) polygon
# would be better, but can probably only be computed for
# pairs of valid polygons. Maybe something based on pointwise
# distances, where the points are sampled on the edges (not
# edge vertices themselves). Maybe something based on drawing
# the perimeter on images or based on distance maps.
point_kept_idx_to_pos = {
point_idx: i for i, point_idx in enumerate(points_kept)}
# generate all possible combinations from and
#
combos = np.transpose([
np.tile(
np.int32(points_left), len(np.int32(points_kept))
),
np.repeat(
np.int32(points_kept), len(np.int32(points_left))
)
])
combos = np.concatenate(
(combos, np.zeros((combos.shape[0], 3), dtype=np.int32)),
axis=1)
# copy columns 0, 1 into 2, 3 so that 2 is always the lower value
mask = combos[:, 0] < combos[:, 1]
combos[:, 2:4] = combos[:, 0:2]
combos[mask, 2] = combos[mask, 1]
combos[mask, 3] = combos[mask, 0]
# distance (in indices) between each pair of and
#
combos[:, 4] = np.minimum(
combos[:, 3] - combos[:, 2],
len(points) - combos[:, 3] + combos[:, 2]
)
# limit candidates
max_dist = self.fit_max_dist_other_iters
if iteration > 0:
max_dist = self.fit_max_dist_first_iter
candidate_rows = combos[combos[:, 4] <= max_dist]
do_limit = (
self.fit_n_candidates_before_sort_max is not None
and len(candidate_rows) > self.fit_n_candidates_before_sort_max)
if do_limit:
random_state.shuffle(candidate_rows)
candidate_rows = candidate_rows[
0:self.fit_n_candidates_before_sort_max]
for row in candidate_rows:
point_left_idx = row[0]
point_kept_idx = row[1]
in_points_kept_pos = point_kept_idx_to_pos[point_kept_idx]
segment_start_idx = point_kept_idx
segment_end_idx = points_kept[
(in_points_kept_pos+1) % len(points_kept)]
segment_start = points[segment_start_idx]
segment_end = points[segment_end_idx]
if iteration == 0:
dist_eucl = 0
else:
dist_eucl = _compute_distance_point_to_line(
points[point_left_idx], segment_start, segment_end)
candidates.append(
(point_left_idx, point_kept_idx, row[4], dist_eucl))
# Sort computed distances first by minimal vertex-distance (see
# above, metric 1) (ASC), then by euclidean distance
# (metric 2) (ASC).
candidate_ids = np.arange(len(candidates))
candidate_ids = sorted(
candidate_ids,
key=lambda idx: (candidates[idx][2], candidates[idx][3]))
if self.fit_n_changes_max is not None:
candidate_ids = candidate_ids[:self.fit_n_changes_max]
# Iterate over point-segment pairs in sorted order. For each such
# candidate: Add the point to the already collected points,
# create a polygon from that and check if the polygon is valid.
# If it is, add the point to the output list and recalculate
# distance metrics. If it isn't valid, proceed with the next
# candidate until no more candidates are left.
#
# small change: this now no longer breaks upon the first found
# point that leads to a valid polygon, but checks all candidates
# instead
is_valid = False
done = set()
for candidate_idx in candidate_ids:
point_left_idx = candidates[candidate_idx][0]
point_kept_idx = candidates[candidate_idx][1]
if (point_left_idx, point_kept_idx) not in done:
in_points_kept_idx = [
i
for i, point_idx
in enumerate(points_kept)
if point_idx == point_kept_idx
][0]
points_kept_hypothesis = points_kept[:]
points_kept_hypothesis.insert(
in_points_kept_idx+1,
point_left_idx)
poly_hypothesis = Polygon([
points[idx] for idx in points_kept_hypothesis])
if poly_hypothesis.is_valid:
is_valid = True
points_kept = points_kept_hypothesis
points_left = [point_idx
for point_idx
in points_left
if point_idx != point_left_idx]
n_changes += 1
if n_changes >= self.fit_n_changes_max:
return points_kept
done.add((point_left_idx, point_kept_idx))
done.add((point_kept_idx, point_left_idx))
# none of the left points could be used to create a valid polygon?
# (this automatically covers the case of no points being left)
if not is_valid and iteration > 0:
converged = True
iteration += 1
has_reached_iters_max = (
self.fit_n_iters_max is not None
and iteration > self.fit_n_iters_max)
if has_reached_iters_max:
break
return points_kept
# TODO remove this? was previously only used by Polygon.clip_*(), but that
# doesn't use it anymore
class MultiPolygon(object):
"""
Class that represents several polygons.
Parameters
----------
geoms : list of imgaug.augmentables.polys.Polygon
List of the polygons.
"""
def __init__(self, geoms):
"""Create a new MultiPolygon instance."""
assert (
len(geoms) == 0
or all([isinstance(el, Polygon) for el in geoms])), (
"Expected 'geoms' to a list of Polygon instances. "
"Got types %s." % (", ".join([str(el) for el in geoms])))
self.geoms = geoms
@staticmethod
def from_shapely(geometry, label=None):
"""Create a MultiPolygon from a shapely object.
This also creates all necessary ``Polygon`` s contained in this
``MultiPolygon``.
Parameters
----------
geometry : shapely.geometry.MultiPolygon or shapely.geometry.Polygon or shapely.geometry.collection.GeometryCollection
The object to convert to a MultiPolygon.
label : None or str, optional
A label assigned to all Polygons within the MultiPolygon.
Returns
-------
imgaug.augmentables.polys.MultiPolygon
The derived MultiPolygon.
"""
# load shapely lazily, which makes the dependency more optional
import shapely.geometry
if isinstance(geometry, shapely.geometry.MultiPolygon):
return MultiPolygon([
Polygon.from_shapely(poly, label=label)
for poly
in geometry.geoms])
if isinstance(geometry, shapely.geometry.Polygon):
return MultiPolygon([Polygon.from_shapely(geometry, label=label)])
if isinstance(geometry,
shapely.geometry.collection.GeometryCollection):
assert all([
isinstance(poly, shapely.geometry.Polygon)
for poly
in geometry.geoms]), (
"Expected the geometry collection to only contain shapely "
"polygons. Got types %s." % (
", ".join([str(type(v)) for v in geometry.geoms])))
return MultiPolygon([
Polygon.from_shapely(poly, label=label)
for poly
in geometry.geoms])
raise Exception(
"Unknown datatype '%s'. Expected shapely.geometry.Polygon or "
"shapely.geometry.MultiPolygon or "
"shapely.geometry.collections.GeometryCollection." % (
type(geometry),))
================================================
FILE: imgaug/augmentables/segmaps.py
================================================
"""Classes dealing with segmentation maps.
E.g. masks, semantic or instance segmentation maps.
"""
from __future__ import print_function, division, absolute_import
import numpy as np
import six.moves as sm
from .. import imgaug as ia
from ..augmenters import blend as blendlib
from .base import IAugmentable
@ia.deprecated(alt_func="SegmentationMapsOnImage",
comment="(Note the plural 'Maps' instead of old 'Map'.)")
def SegmentationMapOnImage(*args, **kwargs):
"""Object representing a segmentation map associated with an image."""
# pylint: disable=invalid-name
return SegmentationMapsOnImage(*args, **kwargs)
class SegmentationMapsOnImage(IAugmentable):
"""
Object representing a segmentation map associated with an image.
Attributes
----------
DEFAULT_SEGMENT_COLORS : list of tuple of int
Standard RGB colors to use during drawing, ordered by class index.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Array representing the segmentation map(s). May have dtypes bool,
int or uint.
shape : tuple of int
Shape of the image on which the segmentation map(s) is/are placed.
**Not** the shape of the segmentation map(s) array, unless it is
identical to the image shape (note the likely difference between the
arrays in the number of channels).
This is expected to be ``(H, W)`` or ``(H, W, C)`` with ``C`` usually
being ``3``.
If there is no corresponding image, use ``(H_arr, W_arr)`` instead,
where ``H_arr`` is the height of the segmentation map(s) array
(analogous ``W_arr``).
nb_classes : None or int, optional
Deprecated.
"""
# TODO replace this by matplotlib colormap
DEFAULT_SEGMENT_COLORS = [
(0, 0, 0), # black
(230, 25, 75), # red
(60, 180, 75), # green
(255, 225, 25), # yellow
(0, 130, 200), # blue
(245, 130, 48), # orange
(145, 30, 180), # purple
(70, 240, 240), # cyan
(240, 50, 230), # magenta
(210, 245, 60), # lime
(250, 190, 190), # pink
(0, 128, 128), # teal
(230, 190, 255), # lavender
(170, 110, 40), # brown
(255, 250, 200), # beige
(128, 0, 0), # maroon
(170, 255, 195), # mint
(128, 128, 0), # olive
(255, 215, 180), # coral
(0, 0, 128), # navy
(128, 128, 128), # grey
(255, 255, 255), # white
# --
(115, 12, 37), # dark red
(30, 90, 37), # dark green
(127, 112, 12), # dark yellow
(0, 65, 100), # dark blue
(122, 65, 24), # dark orange
(72, 15, 90), # dark purple
(35, 120, 120), # dark cyan
(120, 25, 115), # dark magenta
(105, 122, 30), # dark lime
(125, 95, 95), # dark pink
(0, 64, 64), # dark teal
(115, 95, 127), # dark lavender
(85, 55, 20), # dark brown
(127, 125, 100), # dark beige
(64, 0, 0), # dark maroon
(85, 127, 97), # dark mint
(64, 64, 0), # dark olive
(127, 107, 90), # dark coral
(0, 0, 64), # dark navy
(64, 64, 64), # dark grey
]
def __init__(self, arr, shape, nb_classes=None):
assert ia.is_np_array(arr), (
"Expected to get numpy array, got %s." % (type(arr),))
assert arr.ndim in [2, 3], (
"Expected segmentation map array to be 2- or "
"3-dimensional, got %d dimensions and shape %s." % (
arr.ndim, arr.shape))
assert isinstance(shape, tuple), (
"Expected 'shape' to be a tuple denoting the shape of the image "
"on which the segmentation map is placed. Got type %s instead." % (
type(shape)))
if arr.dtype.kind == "f":
ia.warn_deprecated(
"Got a float array as the segmentation map in "
"SegmentationMapsOnImage. That is deprecated. Please provide "
"instead a (H,W,[C]) array of dtype bool_, int or uint, where "
"C denotes the segmentation map index."
)
if arr.ndim == 2:
arr = (arr > 0.5)
else: # arr.ndim == 3
arr = np.argmax(arr, axis=2).astype(np.int32)
if arr.dtype.name == "bool":
self._input_was = (arr.dtype, arr.ndim)
if arr.ndim == 2:
arr = arr[..., np.newaxis]
elif arr.dtype.kind in ["i", "u"]:
assert np.min(arr.flat[0:100]) >= 0, (
"Expected segmentation map array to only contain values >=0, "
"got a minimum of %d." % (np.min(arr),))
if arr.dtype.kind == "u":
# allow only <=uint16 due to conversion to int32
assert arr.dtype.itemsize <= 2, (
"When using uint arrays as segmentation maps, only uint8 "
"and uint16 are allowed. Got dtype %s." % (arr.dtype.name,)
)
elif arr.dtype.kind == "i":
# allow only <=uint16 due to conversion to int32
assert arr.dtype.itemsize <= 4, (
"When using int arrays as segmentation maps, only int8, "
"int16 and int32 are allowed. Got dtype %s." % (
arr.dtype.name,)
)
self._input_was = (arr.dtype, arr.ndim)
if arr.ndim == 2:
arr = arr[..., np.newaxis]
else:
raise Exception((
"Input was expected to be an array of dtype 'bool', 'int' "
"or 'uint'. Got dtype '%s'.") % (arr.dtype.name,))
if arr.dtype.name != "int32":
arr = arr.astype(np.int32)
self.arr = arr
self.shape = shape
if nb_classes is not None:
ia.warn_deprecated(
"Providing nb_classes to SegmentationMapsOnImage is no longer "
"necessary and hence deprecated. The argument is ignored "
"and can be safely removed.")
def get_arr(self):
"""Return the seg.map array, with original dtype and shape ndim.
Here, "original" denotes the dtype and number of shape dimensions that
was used when the :class:`SegmentationMapsOnImage` instance was
created, i.e. upon the call of
:func:`SegmentationMapsOnImage.__init__`.
Internally, this class may use a different dtype and shape to simplify
computations.
.. note::
The height and width may have changed compared to the original
input due to e.g. pooling operations.
Returns
-------
ndarray
Segmentation map array.
Same dtype and number of dimensions as was originally used when
the :class:`SegmentationMapsOnImage` instance was created.
"""
input_dtype, input_ndim = self._input_was
# The internally used int32 has a wider value range than any other
# input dtype, hence we can simply convert via astype() here.
arr_input = self.arr.astype(input_dtype)
if input_ndim == 2:
assert arr_input.shape[2] == 1, (
"Originally got a (H,W) segmentation map. Internal array "
"should now have shape (H,W,1), but got %s. This might be "
"an internal error." % (arr_input.shape,))
return arr_input[:, :, 0]
return arr_input
@ia.deprecated(alt_func="SegmentationMapsOnImage.get_arr()")
def get_arr_int(self, *args, **kwargs):
"""Return the seg.map array, with original dtype and shape ndim."""
# pylint: disable=unused-argument
return self.get_arr()
def draw(self, size=None, colors=None):
"""
Render the segmentation map as an RGB image.
Parameters
----------
size : None or float or iterable of int or iterable of float, optional
Size of the rendered RGB image as ``(height, width)``.
See :func:`~imgaug.imgaug.imresize_single_image` for details.
If set to ``None``, no resizing is performed and the size of the
segmentation map array is used.
colors : None or list of tuple of int, optional
Colors to use. One for each class to draw.
If ``None``, then default colors will be used.
Returns
-------
list of (H,W,3) ndarray
Rendered segmentation map (dtype is ``uint8``).
One per ``C`` in the original input array ``(H,W,C)``.
"""
def _handle_sizeval(sizeval, arr_axis_size):
if sizeval is None:
return arr_axis_size
if ia.is_single_float(sizeval):
return max(int(arr_axis_size * sizeval), 1)
if ia.is_single_integer(sizeval):
return sizeval
raise ValueError("Expected float or int, got %s." % (
type(sizeval),))
if size is None:
size = [size, size]
elif not ia.is_iterable(size):
size = [size, size]
height = _handle_sizeval(size[0], self.arr.shape[0])
width = _handle_sizeval(size[1], self.arr.shape[1])
image = np.zeros((height, width, 3), dtype=np.uint8)
return self.draw_on_image(
image,
alpha=1.0,
resize="segmentation_map",
colors=colors,
draw_background=True
)
def draw_on_image(self, image, alpha=0.75, resize="segmentation_map",
colors=None, draw_background=False,
background_class_id=0, background_threshold=None):
"""Draw the segmentation map as an overlay over an image.
Parameters
----------
image : (H,W,3) ndarray
Image onto which to draw the segmentation map. Expected dtype
is ``uint8``.
alpha : float, optional
Alpha/opacity value to use for the mixing of image and
segmentation map. Larger values mean that the segmentation map
will be more visible and the image less visible.
resize : {'segmentation_map', 'image'}, optional
In case of size differences between the image and segmentation
map, either the image or the segmentation map can be resized.
This parameter controls which of the two will be resized to the
other's size.
colors : None or list of tuple of int, optional
Colors to use. One for each class to draw.
If ``None``, then default colors will be used.
draw_background : bool, optional
If ``True``, the background will be drawn like any other class.
If ``False``, the background will not be drawn, i.e. the respective
background pixels will be identical with the image's RGB color at
the corresponding spatial location and no color overlay will be
applied.
background_class_id : int, optional
Class id to interpret as the background class.
See `draw_background`.
background_threshold : None, optional
Deprecated.
This parameter is ignored.
Returns
-------
list of (H,W,3) ndarray
Rendered overlays as ``uint8`` arrays.
Always a **list** containing one RGB image per segmentation map
array channel.
"""
if background_threshold is not None:
ia.warn_deprecated(
"The argument `background_threshold` is deprecated and "
"ignored. Please don't use it anymore.")
assert image.ndim == 3, (
"Expected to draw on 3-dimensional image, got image with %d "
"dimensions." % (image.ndim,))
assert image.shape[2] == 3, (
"Expected to draw on RGB image, got image with %d channels "
"instead." % (image.shape[2],))
assert image.dtype.name == "uint8", (
"Expected to get image with dtype uint8, got dtype %s." % (
image.dtype.name,))
assert 0 - 1e-8 <= alpha <= 1.0 + 1e-8, (
"Expected 'alpha' to be in interval [0.0, 1.0], got %.4f." % (
alpha,))
assert resize in ["segmentation_map", "image"], (
"Expected 'resize' to be \"segmentation_map\" or \"image\", got "
"%s." % (resize,))
colors = (
colors
if colors is not None
else SegmentationMapsOnImage.DEFAULT_SEGMENT_COLORS
)
if resize == "image":
image = ia.imresize_single_image(
image, self.arr.shape[0:2], interpolation="cubic")
segmaps_drawn = []
arr_channelwise = np.dsplit(self.arr, self.arr.shape[2])
for arr in arr_channelwise:
arr = arr[:, :, 0]
nb_classes = 1 + np.max(arr)
segmap_drawn = np.zeros((arr.shape[0], arr.shape[1], 3),
dtype=np.uint8)
assert nb_classes <= len(colors), (
"Can't draw all %d classes as it would exceed the maximum "
"number of %d available colors." % (nb_classes, len(colors),))
ids_in_map = np.unique(arr)
for c, color in zip(sm.xrange(nb_classes), colors):
if c in ids_in_map:
class_mask = (arr == c)
segmap_drawn[class_mask] = color
segmap_drawn = ia.imresize_single_image(
segmap_drawn, image.shape[0:2], interpolation="nearest")
segmap_on_image = blendlib.blend_alpha(segmap_drawn, image, alpha)
if draw_background:
mix = segmap_on_image
else:
foreground_mask = ia.imresize_single_image(
(arr != background_class_id),
image.shape[0:2],
interpolation="nearest")
# without this, the merge below does nothing
foreground_mask = np.atleast_3d(foreground_mask)
mix = (
(~foreground_mask) * image
+ foreground_mask * segmap_on_image
)
segmaps_drawn.append(mix)
return segmaps_drawn
def pad(self, top=0, right=0, bottom=0, left=0, mode="constant", cval=0):
"""Pad the segmentation maps at their top/right/bottom/left side.
Parameters
----------
top : int, optional
Amount of pixels to add at the top side of the segmentation map.
Must be ``0`` or greater.
right : int, optional
Amount of pixels to add at the right side of the segmentation map.
Must be ``0`` or greater.
bottom : int, optional
Amount of pixels to add at the bottom side of the segmentation map.
Must be ``0`` or greater.
left : int, optional
Amount of pixels to add at the left side of the segmentation map.
Must be ``0`` or greater.
mode : str, optional
Padding mode to use. See :func:`~imgaug.imgaug.pad` for details.
cval : number, optional
Value to use for padding if `mode` is ``constant``.
See :func:`~imgaug.imgaug.pad` for details.
Returns
-------
imgaug.augmentables.segmaps.SegmentationMapsOnImage
Padded segmentation map with height ``H'=H+top+bottom`` and
width ``W'=W+left+right``.
"""
from ..augmenters import size as iasize
arr_padded = iasize.pad(self.arr, top=top, right=right, bottom=bottom,
left=left, mode=mode, cval=cval)
return self.deepcopy(arr=arr_padded)
def pad_to_aspect_ratio(self, aspect_ratio, mode="constant", cval=0,
return_pad_amounts=False):
"""Pad the segmentation maps until they match a target aspect ratio.
Depending on which dimension is smaller (height or width), only the
corresponding sides (left/right or top/bottom) will be padded. In
each case, both of the sides will be padded equally.
Parameters
----------
aspect_ratio : float
Target aspect ratio, given as width/height. E.g. ``2.0`` denotes
the image having twice as much width as height.
mode : str, optional
Padding mode to use.
See :func:`~imgaug.imgaug.pad` for details.
cval : number, optional
Value to use for padding if `mode` is ``constant``.
See :func:`~imgaug.imgaug.pad` for details.
return_pad_amounts : bool, optional
If ``False``, then only the padded instance will be returned.
If ``True``, a tuple with two entries will be returned, where
the first entry is the padded instance and the second entry are
the amounts by which each array side was padded. These amounts are
again a tuple of the form ``(top, right, bottom, left)``, with
each value being an integer.
Returns
-------
imgaug.augmentables.segmaps.SegmentationMapsOnImage
Padded segmentation map as :class:`SegmentationMapsOnImage`
instance.
tuple of int
Amounts by which the instance's array was padded on each side,
given as a tuple ``(top, right, bottom, left)``.
This tuple is only returned if `return_pad_amounts` was set to
``True``.
"""
from ..augmenters import size as iasize
arr_padded, pad_amounts = iasize.pad_to_aspect_ratio(
self.arr,
aspect_ratio=aspect_ratio,
mode=mode,
cval=cval,
return_pad_amounts=True)
segmap = self.deepcopy(arr=arr_padded)
if return_pad_amounts:
return segmap, pad_amounts
return segmap
@ia.deprecated(alt_func="SegmentationMapsOnImage.resize()",
comment="resize() has the exactly same interface.")
def scale(self, *args, **kwargs):
"""Resize the seg.map(s) array given a target size and interpolation."""
return self.resize(*args, **kwargs)
def resize(self, sizes, interpolation="nearest"):
"""Resize the seg.map(s) array given a target size and interpolation.
Parameters
----------
sizes : float or iterable of int or iterable of float
New size of the array in ``(height, width)``.
See :func:`~imgaug.imgaug.imresize_single_image` for details.
interpolation : None or str or int, optional
The interpolation to use during resize.
Nearest neighbour interpolation (``"nearest"``) is almost always
the best choice.
See :func:`~imgaug.imgaug.imresize_single_image` for details.
Returns
-------
imgaug.augmentables.segmaps.SegmentationMapsOnImage
Resized segmentation map object.
"""
arr_resized = ia.imresize_single_image(self.arr, sizes,
interpolation=interpolation)
return self.deepcopy(arr_resized)
# TODO how best to handle changes to _input_was due to changed 'arr'?
def copy(self, arr=None, shape=None):
"""Create a shallow copy of the segmentation map object.
Parameters
----------
arr : None or (H,W) ndarray or (H,W,C) ndarray, optional
Optionally the `arr` attribute to use for the new segmentation map
instance. Will be copied from the old instance if not provided.
See
:func:`~imgaug.augmentables.segmaps.SegmentationMapsOnImage.__init__`
for details.
shape : None or tuple of int, optional
Optionally the shape attribute to use for the the new segmentation
map instance. Will be copied from the old instance if not provided.
See
:func:`~imgaug.augmentables.segmaps.SegmentationMapsOnImage.__init__`
for details.
Returns
-------
imgaug.augmentables.segmaps.SegmentationMapsOnImage
Shallow copy.
"""
# pylint: disable=protected-access
segmap = SegmentationMapsOnImage(
self.arr if arr is None else arr,
shape=self.shape if shape is None else shape)
segmap._input_was = self._input_was
return segmap
def deepcopy(self, arr=None, shape=None):
"""Create a deep copy of the segmentation map object.
Parameters
----------
arr : None or (H,W) ndarray or (H,W,C) ndarray, optional
Optionally the `arr` attribute to use for the new segmentation map
instance. Will be copied from the old instance if not provided.
See
:func:`~imgaug.augmentables.segmaps.SegmentationMapsOnImage.__init__`
for details.
shape : None or tuple of int, optional
Optionally the shape attribute to use for the the new segmentation
map instance. Will be copied from the old instance if not provided.
See
:func:`~imgaug.augmentables.segmaps.SegmentationMapsOnImage.__init__`
for details.
Returns
-------
imgaug.augmentables.segmaps.SegmentationMapsOnImage
Deep copy.
"""
# pylint: disable=protected-access
segmap = SegmentationMapsOnImage(
np.copy(self.arr if arr is None else arr),
shape=self.shape if shape is None else shape)
segmap._input_was = self._input_was
return segmap
================================================
FILE: imgaug/augmentables/utils.py
================================================
"""Utility functions used in augmentable modules."""
from __future__ import print_function, division, absolute_import
import copy as copylib
import numpy as np
import six.moves as sm
import imgaug as ia
# TODO add tests
def copy_augmentables(augmentables):
if ia.is_np_array(augmentables):
return np.copy(augmentables)
result = []
for augmentable in augmentables:
if ia.is_np_array(augmentable):
result.append(np.copy(augmentable))
else:
result.append(augmentable.deepcopy())
return result
# Added in 0.4.0.
def deepcopy_fast(obj):
if obj is None:
return None
if ia.is_single_number(obj) or ia.is_string(obj):
return obj
if isinstance(obj, list):
return [deepcopy_fast(el) for el in obj]
if isinstance(obj, tuple):
return tuple([deepcopy_fast(el) for el in obj])
if ia.is_np_array(obj):
return np.copy(obj)
if hasattr(obj, "deepcopy"):
return obj.deepcopy()
return copylib.deepcopy(obj)
# Added in 0.5.0.
def _handle_on_image_shape(shape, obj):
if hasattr(shape, "shape"):
ia.warn_deprecated(
"Providing a numpy array for parameter `shape` in "
"`%s` is deprecated. Please provide a shape tuple, "
"i.e. a tuple of integers denoting (height, width, [channels]). "
"Use something similar to `image.shape` to convert an array "
"to a shape tuple." % (
obj.__class__.__name__,
)
)
shape = normalize_shape(shape)
else:
assert isinstance(shape, tuple), (
"Expected to get a tuple of integers or a numpy array "
"(deprecated) for parameter `shape` in `%s`. Got type %s." % (
obj.__class__.__name__, type(shape).__name__
)
)
return shape
def normalize_shape(shape):
"""Normalize a shape ``tuple`` or ``array`` to a shape ``tuple``.
Parameters
----------
shape : tuple of int or ndarray
The input to normalize. May optionally be an array.
Returns
-------
tuple of int
Shape ``tuple``.
"""
if isinstance(shape, tuple):
return shape
assert ia.is_np_array(shape), (
"Expected tuple of ints or array, got %s." % (type(shape),))
return shape.shape
def normalize_imglike_shape(shape):
"""Normalize a shape tuple or image-like ``array`` to a shape tuple.
Added in 0.5.0.
Parameters
----------
shape : tuple of int or ndarray
The input to normalize. May optionally be an array. If it is an
array, it must be 2-dimensional (height, width) or 3-dimensional
(height, width, channels). Otherwise an error will be raised.
Returns
-------
tuple of int
Shape ``tuple``.
"""
if isinstance(shape, tuple):
return shape
assert ia.is_np_array(shape), (
"Expected tuple of ints or array, got %s." % (type(shape),))
shape = shape.shape
assert len(shape) in [2, 3], (
"Expected image array to be 2-dimensional or 3-dimensional, got "
"%d-dimensional input of shape %s." % (len(shape), shape)
)
return shape
def project_coords_(coords, from_shape, to_shape):
"""Project coordinates from one image shape to another in-place.
This performs a relative projection, e.g. a point at ``60%`` of the old
image width will be at ``60%`` of the new image width after projection.
Added in 0.4.0.
Parameters
----------
coords : ndarray or list of tuple of number
Coordinates to project.
Either an ``(N,2)`` numpy array or a ``list`` containing ``(x,y)``
coordinate ``tuple`` s.
from_shape : tuple of int or ndarray
Old image shape.
to_shape : tuple of int or ndarray
New image shape.
Returns
-------
ndarray
Projected coordinates as ``(N,2)`` ``float32`` numpy array.
This function may change the input data in-place.
"""
from_shape = normalize_shape(from_shape)
to_shape = normalize_shape(to_shape)
if from_shape[0:2] == to_shape[0:2]:
return coords
from_height, from_width = from_shape[0:2]
to_height, to_width = to_shape[0:2]
no_zeros_in_shapes = (
all([v > 0 for v in [from_height, from_width, to_height, to_width]]))
assert no_zeros_in_shapes, (
"Expected from_shape and to_shape to not contain zeros. Got shapes "
"%s (from_shape) and %s (to_shape)." % (from_shape, to_shape))
coords_proj = coords
if not ia.is_np_array(coords) or coords.dtype.kind != "f":
coords_proj = np.array(coords).astype(np.float32)
coords_proj[:, 0] = (coords_proj[:, 0] / from_width) * to_width
coords_proj[:, 1] = (coords_proj[:, 1] / from_height) * to_height
return coords_proj
def project_coords(coords, from_shape, to_shape):
"""Project coordinates from one image shape to another.
This performs a relative projection, e.g. a point at ``60%`` of the old
image width will be at ``60%`` of the new image width after projection.
Parameters
----------
coords : ndarray or list of tuple of number
Coordinates to project.
Either an ``(N,2)`` numpy array or a ``list`` containing ``(x,y)``
coordinate ``tuple`` s.
from_shape : tuple of int or ndarray
Old image shape.
to_shape : tuple of int or ndarray
New image shape.
Returns
-------
ndarray
Projected coordinates as ``(N,2)`` ``float32`` numpy array.
"""
if ia.is_np_array(coords):
coords = np.copy(coords)
return project_coords_(coords, from_shape, to_shape)
# TODO does that include point_b in the result?
def interpolate_point_pair(point_a, point_b, nb_steps):
"""Interpolate ``N`` points on a line segment.
Parameters
----------
point_a : iterable of number
Start point of the line segment, given as ``(x,y)`` coordinates.
point_b : iterable of number
End point of the line segment, given as ``(x,y)`` coordinates.
nb_steps : int
Number of points to interpolate between `point_a` and `point_b`.
Returns
-------
list of tuple of number
The interpolated points.
Does not include `point_a`.
"""
if nb_steps < 1:
return []
x1, y1 = point_a
x2, y2 = point_b
vec = np.float32([x2 - x1, y2 - y1])
step_size = vec / (1 + nb_steps)
return [
(x1 + (i + 1) * step_size[0], y1 + (i + 1) * step_size[1])
for i
in sm.xrange(nb_steps)]
def interpolate_points(points, nb_steps, closed=True):
"""Interpolate ``N`` on each line segment in a line string.
Parameters
----------
points : iterable of iterable of number
Points on the line segments, each one given as ``(x,y)`` coordinates.
They are assumed to form one connected line string.
nb_steps : int
Number of points to interpolate on each individual line string.
closed : bool, optional
If ``True`` the output contains the last point in `points`.
Otherwise it does not (but it will contain the interpolated points
leading to the last point).
Returns
-------
list of tuple of number
Coordinates of `points`, with additional `nb_steps` new points
interpolated between each point pair. If `closed` is ``False``,
the last point in `points` is not returned.
"""
if len(points) <= 1:
return points
if closed:
points = list(points) + [points[0]]
points_interp = []
for point_a, point_b in zip(points[:-1], points[1:]):
points_interp.extend(
[point_a]
+ interpolate_point_pair(point_a, point_b, nb_steps)
)
if not closed:
points_interp.append(points[-1])
# close does not have to be reverted here, as last point is not included
# in the extend()
return points_interp
def interpolate_points_by_max_distance(points, max_distance, closed=True):
"""Interpolate points with distance ``d`` on a line string.
For a list of points ``A, B, C``, if the distance between ``A`` and ``B``
is greater than `max_distance`, it will place at least one point between
``A`` and ``B`` at ``A + max_distance * (B - A)``. Multiple points can
be placed between the two points if they are far enough away from each
other. The process is repeated for ``B`` and ``C``.
Parameters
----------
points : iterable of iterable of number
Points on the line segments, each one given as ``(x,y)`` coordinates.
They are assumed to form one connected line string.
max_distance : number
Maximum distance between any two points in the result.
closed : bool, optional
If ``True`` the output contains the last point in `points`.
Otherwise it does not (but it will contain the interpolated points
leading to the last point).
Returns
-------
list of tuple of number
Coordinates of `points`, with interpolated points added to the
iterable. If `closed` is ``False``, the last point in `points` is not
returned.
"""
assert max_distance > 0, (
"Expected max_distance to have a value >0, got %.8f." % (
max_distance,))
if len(points) <= 1:
return points
if closed:
points = list(points) + [points[0]]
points_interp = []
for point_a, point_b in zip(points[:-1], points[1:]):
dist = np.sqrt(
(point_a[0] - point_b[0]) ** 2
+ (point_a[1] - point_b[1]) ** 2)
nb_steps = int((dist / max_distance) - 1)
points_interp.extend(
[point_a]
+ interpolate_point_pair(point_a, point_b, nb_steps))
if not closed:
points_interp.append(points[-1])
return points_interp
def convert_cbaois_to_kpsois(cbaois):
"""Convert coordinate-based augmentables to KeypointsOnImage instances.
Added in 0.4.0.
Parameters
----------
cbaois : list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.bbs.PolygonsOnImage or list of imgaug.augmentables.bbs.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.bbs.PolygonsOnImage or imgaug.augmentables.bbs.LineStringsOnImage
Coordinate-based augmentables to convert, e.g. bounding boxes.
Returns
-------
list of imgaug.augmentables.kps.KeypointsOnImage or imgaug.augmentables.kps.KeypointsOnImage
``KeypointsOnImage`` instances containing the coordinates of input
`cbaois`.
"""
if not isinstance(cbaois, list):
return cbaois.to_keypoints_on_image()
kpsois = []
for cbaoi in cbaois:
kpsois.append(cbaoi.to_keypoints_on_image())
return kpsois
def invert_convert_cbaois_to_kpsois_(cbaois, kpsois):
"""Invert the output of :func:`convert_to_cbaois_to_kpsois` in-place.
This function writes in-place into `cbaois`.
Added in 0.4.0.
Parameters
----------
cbaois : list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.bbs.PolygonsOnImage or list of imgaug.augmentables.bbs.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.bbs.PolygonsOnImage or imgaug.augmentables.bbs.LineStringsOnImage
Original coordinate-based augmentables before they were converted,
i.e. the same inputs as provided to :func:`convert_to_kpsois`.
kpsois : list of imgaug.augmentables.kps.KeypointsOnImages or imgaug.augmentables.kps.KeypointsOnImages
Keypoints to convert back to the types of `cbaois`, i.e. the outputs
of :func:`convert_cbaois_to_kpsois`.
Returns
-------
list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.bbs.PolygonsOnImage or list of imgaug.augmentables.bbs.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.bbs.PolygonsOnImage or imgaug.augmentables.bbs.LineStringsOnImage
Parameter `cbaois`, with updated coordinates and shapes derived from
`kpsois`. `cbaois` is modified in-place.
"""
if not isinstance(cbaois, list):
assert not isinstance(kpsois, list), (
"Expected non-list for `kpsois` when `cbaois` is non-list. "
"Got type %s." % (type(kpsois.__name__)),)
return cbaois.invert_to_keypoints_on_image_(kpsois)
result = []
for cbaoi, kpsoi in zip(cbaois, kpsois):
cbaoi_recovered = cbaoi.invert_to_keypoints_on_image_(kpsoi)
result.append(cbaoi_recovered)
return result
# Added in 0.4.0.
def _remove_out_of_image_fraction_(cbaoi, fraction):
cbaoi.items = [
item for item in cbaoi.items
if item.compute_out_of_image_fraction(cbaoi.shape) < fraction]
return cbaoi
# Added in 0.4.0.
def _normalize_shift_args(x, y, top=None, right=None, bottom=None, left=None):
"""Normalize ``shift()`` arguments to x, y and handle deprecated args."""
if any([v is not None for v in [top, right, bottom, left]]):
ia.warn_deprecated(
"Got one of the arguments `top` (%s), `right` (%s), "
"`bottom` (%s), `left` (%s) in a shift() call. "
"These are deprecated. Use `x` and `y` instead." % (
top, right, bottom, left),
stacklevel=3)
top = top if top is not None else 0
right = right if right is not None else 0
bottom = bottom if bottom is not None else 0
left = left if left is not None else 0
x = x + left - right
y = y + top - bottom
return x, y
================================================
FILE: imgaug/augmenters/__init__.py
================================================
"""Combination of all augmenters, related classes and related functions."""
# pylint: disable=unused-import
from __future__ import absolute_import
from imgaug.augmenters.base import *
from imgaug.augmenters.arithmetic import *
from imgaug.augmenters.artistic import *
from imgaug.augmenters.blend import *
from imgaug.augmenters.blur import *
from imgaug.augmenters.collections import *
from imgaug.augmenters.color import *
from imgaug.augmenters.contrast import *
from imgaug.augmenters.convolutional import *
from imgaug.augmenters.debug import *
from imgaug.augmenters.edges import *
from imgaug.augmenters.flip import *
from imgaug.augmenters.geometric import *
import imgaug.augmenters.imgcorruptlike # use as iaa.imgcorrupt.
from imgaug.augmenters.meta import *
import imgaug.augmenters.pillike # use via: iaa.pillike.*
from imgaug.augmenters.pooling import *
from imgaug.augmenters.segmentation import *
from imgaug.augmenters.size import *
from imgaug.augmenters.weather import *
================================================
FILE: imgaug/augmenters/arithmetic.py
================================================
"""
Augmenters that perform simple arithmetic changes.
List of augmenters:
* :class:`Add`
* :class:`AddElementwise`
* :class:`AdditiveGaussianNoise`
* :class:`AdditiveLaplaceNoise`
* :class:`AdditivePoissonNoise`
* :class:`Multiply`
* :class:`MultiplyElementwise`
* :class:`Cutout`
* :class:`Dropout`
* :class:`CoarseDropout`
* :class:`Dropout2d`
* :class:`TotalDropout`
* :class:`ReplaceElementwise`
* :class:`ImpulseNoise`
* :class:`SaltAndPepper`
* :class:`CoarseSaltAndPepper`
* :class:`Salt`
* :class:`CoarseSalt`
* :class:`Pepper`
* :class:`CoarsePepper`
* :class:`Invert`
* :class:`Solarize`
* :class:`ContrastNormalization`
* :class:`JpegCompression`
"""
from __future__ import print_function, division, absolute_import
import tempfile
import imageio
import numpy as np
import cv2
import imgaug as ia
from . import meta
from .. import parameters as iap
from .. import dtypes as iadt
from .. import random as iarandom
from ..imgaug import _normalize_cv2_input_arr_
# fill modes for apply_cutout_() and Cutout augmenter
# contains roughly:
# 'str fill_mode_name => (str module_name, str function_name)'
# We could also assign the function to each fill mode name instead of its
# name, but that has the disadvantage that these aren't defined yet (they
# are defined further below) and that during unittesting they would be harder
# to mock. (mock.patch() seems to not automatically replace functions
# assigned in that way.)
_CUTOUT_FILL_MODES = {
"constant": ("imgaug.augmenters.arithmetic", "_fill_rectangle_constant_"),
"gaussian": ("imgaug.augmenters.arithmetic", "_fill_rectangle_gaussian_")
}
def add_scalar(image, value):
"""Add a scalar value (or one scalar per channel) to an image.
This method ensures that ``uint8`` does not overflow during the addition.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: limited; tested (1)
* ``uint32``: no
* ``uint64``: no
* ``int8``: limited; tested (1)
* ``int16``: limited; tested (1)
* ``int32``: no
* ``int64``: no
* ``float16``: limited; tested (1)
* ``float32``: limited; tested (1)
* ``float64``: no
* ``float128``: no
* ``bool``: limited; tested (1)
- (1) Non-uint8 dtypes can overflow. For floats, this can result
in +/-inf.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
If `value` contains more than one value, the shape of the image is
expected to be ``(H,W,C)``.
value : number or ndarray
The value to add to the image. Either a single value or an array
containing exactly one component per channel, i.e. ``C`` components.
Returns
-------
ndarray
Image with value added to it.
"""
return add_scalar_(np.copy(image), value)
def add_scalar_(image, value):
"""Add in-place a scalar value (or one scalar per channel) to an image.
This method ensures that ``uint8`` does not overflow during the addition.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: limited; tested (1)
* ``uint32``: no
* ``uint64``: no
* ``int8``: limited; tested (1)
* ``int16``: limited; tested (1)
* ``int32``: no
* ``int64``: no
* ``float16``: limited; tested (1)
* ``float32``: limited; tested (1)
* ``float64``: no
* ``float128``: no
* ``bool``: limited; tested (1)
- (1) Non-uint8 dtypes can overflow. For floats, this can result
in +/-inf.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
If `value` contains more than one value, the shape of the image is
expected to be ``(H,W,C)``.
The image might be changed in-place.
value : number or ndarray
The value to add to the image. Either a single value or an array
containing exactly one component per channel, i.e. ``C`` components.
Returns
-------
ndarray
Image with value added to it.
This might be the input `image`, changed in-place.
"""
if image.size == 0:
return np.copy(image)
iadt.gate_dtypes_strs(
{image.dtype},
allowed="bool uint8 uint16 int8 int16 float16 float32",
disallowed="uint32 uint64 int32 int64 float64 float128",
augmenter=None)
if image.dtype == iadt._UINT8_DTYPE:
return _add_scalar_to_uint8_(image, value)
return _add_scalar_to_non_uint8(image, value)
def _add_scalar_to_uint8_(image, value):
if ia.is_single_number(value):
is_single_value = True
value = round(value)
elif ia.is_np_scalar(value) or ia.is_np_array(value):
is_single_value = (value.size == 1)
value = np.round(value) if value.dtype.kind == "f" else value
else:
is_single_value = False
is_channelwise = not is_single_value
if image.ndim == 2 and is_single_value:
return cv2.add(image, value, dst=image, dtype=cv2.CV_8U)
input_shape = image.shape
image = image.ravel()
values = np.array(value)
if not is_channelwise:
values = np.broadcast_to(values, image.shape)
else:
values = np.tile(values, image.size // len(values))
image_add = cv2.add(image, values, dst=image, dtype=cv2.CV_8U)
return image_add.reshape(input_shape)
def _add_scalar_to_non_uint8(image, value):
input_dtype = image.dtype
is_single_value = (
ia.is_single_number(value)
or ia.is_np_scalar(value)
or (ia.is_np_array(value) and value.size == 1))
is_channelwise = not is_single_value
nb_channels = 1 if image.ndim == 2 else image.shape[-1]
shape = (1, 1, nb_channels if is_channelwise else 1)
value = np.array(value).reshape(shape)
# We limit here the value range of the value parameter to the
# bytes in the image's dtype. This prevents overflow problems
# and makes it less likely that the image has to be up-casted,
# which again improves performance and saves memory. Note that
# this also enables more dtypes for image inputs.
# The downside is that the mul parameter is limited in its
# value range.
#
# We need 2* the itemsize of the image here to allow to shift
# the image's max value to the lowest possible value, e.g. for
# uint8 it must allow for -255 to 255.
itemsize = image.dtype.itemsize * 2
dtype_target = np.dtype("%s%d" % (value.dtype.kind, itemsize))
value = iadt.clip_to_dtype_value_range_(
value, dtype_target, validate=True)
# Itemsize is currently reduced from 2 to 1 due to clip no
# longer supporting int64, which can cause issues with int32
# samples (32*2 = 64bit).
# TODO limit value ranges of samples to int16/uint16 for
# security
image, value = iadt.promote_array_dtypes_(
[image, value],
dtypes=[image.dtype, dtype_target],
increase_itemsize_factor=1)
image = np.add(image, value, out=image, casting="no")
return iadt.restore_dtypes_(image, input_dtype)
def add_elementwise(image, values):
"""Add an array of values to an image.
This method ensures that ``uint8`` does not overflow during the addition.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: limited; tested (1)
* ``uint32``: no
* ``uint64``: no
* ``int8``: limited; tested (1)
* ``int16``: limited; tested (1)
* ``int32``: no
* ``int64``: no
* ``float16``: limited; tested (1)
* ``float32``: limited; tested (1)
* ``float64``: no
* ``float128``: no
* ``bool``: limited; tested (1)
- (1) Non-uint8 dtypes can overflow. For floats, this can result
in +/-inf.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
values : ndarray
The values to add to the image. Expected to have the same height
and width as `image` and either no channels or one channel or
the same number of channels as `image`.
This array is expected to have dtype ``int8``, ``int16``, ``int32``,
``uint8``, ``uint16``, ``float32``, ``float64``. Other dtypes may
or may not work.
For ``uint8`` inputs, only `value` arrays with values in the interval
``[-1000, 1000]`` are supported. Values beyond that interval may
result in an output array of zeros (no error is raised due to
performance reasons).
Returns
-------
ndarray
Image with values added to it.
"""
iadt.gate_dtypes_strs(
{image.dtype},
allowed="bool uint8 uint16 int8 int16 float16 float32",
disallowed="uint32 uint64 int32 int64 float64 float128",
augmenter=None)
if image.dtype == iadt._UINT8_DTYPE:
vdt = values.dtype
valid_value_dtypes_cv2 = iadt._convert_dtype_strs_to_types(
"int8 int16 int32 uint8 uint16 float32 float64"
)
ishape = image.shape
is_image_valid_shape_cv2 = (
(
len(ishape) == 2
or (len(ishape) == 3 and ishape[-1] <= 512)
)
and 0 not in ishape
)
use_cv2 = (
is_image_valid_shape_cv2
and vdt in valid_value_dtypes_cv2
)
if use_cv2:
return _add_elementwise_cv2_to_uint8(image, values)
return _add_elementwise_np_to_uint8(image, values)
return _add_elementwise_np_to_non_uint8(image, values)
def _add_elementwise_cv2_to_uint8(image, values):
ind, vnd = image.ndim, values.ndim
valid_vnd = [ind] if ind == 2 else [ind-1, ind]
assert vnd in valid_vnd, (
"Expected values with any of %s dimensions, "
"got %d dimensions (shape %s vs. image shape %s)." % (
valid_vnd, vnd, values.shape, image.shape
)
)
if vnd == ind - 1:
values = values[:, :, np.newaxis]
if values.shape[-1] == 1:
values = np.broadcast_to(values, image.shape)
# add does not seem to require normalization
result = cv2.add(image, values, dtype=cv2.CV_8U)
if result.ndim == 2 and ind == 3:
return result[:, :, np.newaxis]
return result
def _add_elementwise_np_to_uint8(image, values):
# This special uint8 block is around 60-100% faster than the
# corresponding non-uint8 function further below (more speedup
# for smaller images).
#
# Also tested to instead compute min/max of image and value
# and then only convert image/value dtype if actually
# necessary, but that was like 20-30% slower, even for 224x224
# images.
#
if values.dtype.kind == "f":
values = np.round(values)
image = image.astype(np.int16)
values = np.clip(values, -255, 255).astype(np.int16)
image_aug = image + values
image_aug = np.clip(image_aug, 0, 255).astype(np.uint8)
return image_aug
def _add_elementwise_np_to_non_uint8(image, values):
# We limit here the value range of the value parameter to the
# bytes in the image's dtype. This prevents overflow problems
# and makes it less likely that the image has to be up-casted,
# which again improves performance and saves memory. Note that
# this also enables more dtypes for image inputs.
# The downside is that the mul parameter is limited in its
# value range.
#
# We need 2* the itemsize of the image here to allow to shift
# the image's max value to the lowest possible value, e.g. for
# uint8 it must allow for -255 to 255.
if image.dtype.kind != "f" and values.dtype.kind == "f":
values = np.round(values)
input_shape = image.shape
input_dtype = image.dtype
if image.ndim == 2:
image = image[..., np.newaxis]
if values.ndim == 2:
values = values[..., np.newaxis]
nb_channels = image.shape[-1]
itemsize = image.dtype.itemsize * 2
dtype_target = np.dtype("%s%d" % (values.dtype.kind, itemsize))
values = iadt.clip_to_dtype_value_range_(values, dtype_target,
validate=100)
if values.shape[2] == 1:
values = np.tile(values, (1, 1, nb_channels))
# Decreased itemsize from 2 to 1 here, see explanation in Add.
image, values = iadt.promote_array_dtypes_(
[image, values],
dtypes=[image.dtype, dtype_target],
increase_itemsize_factor=1)
image = np.add(image, values, out=image, casting="no")
image = iadt.restore_dtypes_(image, input_dtype)
if len(input_shape) == 2:
return image[..., 0]
return image
def multiply_scalar(image, multiplier):
"""Multiply an image by a single scalar or one scalar per channel.
This method ensures that ``uint8`` does not overflow during the
multiplication.
note::
Tests were only conducted for rather small multipliers, around
``-10.0`` to ``+10.0``.
In general, the multipliers sampled from `multiplier` must be in a
value range that corresponds to the input image's dtype. E.g. if the
input image has dtype ``uint16`` and the samples generated from
`multiplier` are ``float64``, this function will still force all
samples to be within the value range of ``float16``, as it has the
same number of bytes (two) as ``uint16``. This is done to make
overflows less likely to occur.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: limited; tested (1)
* ``uint32``: no
* ``uint64``: no
* ``int8``: limited; tested (1)
* ``int16``: limited; tested (1)
* ``int32``: no
* ``int64``: no
* ``float16``: limited; tested (1)
* ``float32``: limited; tested (1)
* ``float64``: no
* ``float128``: no
* ``bool``: limited; tested (1)
- (1) Non-uint8 dtypes can overflow. For floats, this can result in
+/-inf.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
If `value` contains more than one value, the shape of the image is
expected to be ``(H,W,C)``.
multiplier : number or ndarray
The multiplier to use. Either a single value or an array
containing exactly one component per channel, i.e. ``C`` components.
Returns
-------
ndarray
Image, multiplied by `multiplier`.
"""
return multiply_scalar_(np.copy(image), multiplier)
def multiply_scalar_(image, multiplier):
"""Multiply in-place an image by a single scalar or one scalar per channel.
This method ensures that ``uint8`` does not overflow during the
multiplication.
note::
Tests were only conducted for rather small multipliers, around
``-10.0`` to ``+10.0``.
In general, the multipliers sampled from `multiplier` must be in a
value range that corresponds to the input image's dtype. E.g. if the
input image has dtype ``uint16`` and the samples generated from
`multiplier` are ``float64``, this function will still force all
samples to be within the value range of ``float16``, as it has the
same number of bytes (two) as ``uint16``. This is done to make
overflows less likely to occur.
Added in 0.5.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: limited; tested (1)
* ``uint32``: no
* ``uint64``: no
* ``int8``: limited; tested (1)
* ``int16``: limited; tested (1)
* ``int32``: no
* ``int64``: no
* ``float16``: limited; tested (1)
* ``float32``: limited; tested (1)
* ``float64``: no
* ``float128``: no
* ``bool``: limited; tested (1)
- (1) Non-uint8 dtypes can overflow. For floats, this can result in
+/-inf.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
If `value` contains more than one value, the shape of the image is
expected to be ``(H,W,C)``.
May be changed in-place.
multiplier : number or ndarray
The multiplier to use. Either a single value or an array
containing exactly one component per channel, i.e. ``C`` components.
Returns
-------
ndarray
Image, multiplied by `multiplier`.
Might be the same image instance as was provided in `image`.
"""
size = image.size
if size == 0:
return image
iadt.gate_dtypes_strs(
{image.dtype},
allowed="bool uint8 uint16 int8 int16 float16 float32",
disallowed="uint32 uint64 int32 int64 float64 float128",
augmenter=None)
if image.dtype == iadt._UINT8_DTYPE:
if size >= 224*224*3:
return _multiply_scalar_to_uint8_lut_(image, multiplier)
return _multiply_scalar_to_uint8_cv2_mul_(image, multiplier)
return _multiply_scalar_to_non_uint8(image, multiplier)
# TODO add a c++/cython method here to compute the LUT tables
# Added in 0.5.0.
def _multiply_scalar_to_uint8_lut_(image, multiplier):
is_single_value = (
ia.is_single_number(multiplier)
or ia.is_np_scalar(multiplier)
or (ia.is_np_array(multiplier) and multiplier.size == 1))
is_channelwise = not is_single_value
nb_channels = 1 if image.ndim == 2 else image.shape[-1]
multiplier = np.float32(multiplier)
value_range = np.arange(0, 256, dtype=np.float32)
if is_channelwise:
assert multiplier.ndim == 1, (
"Expected `multiplier` to be 1-dimensional, got %d-dimensional "
"data with shape %s." % (multiplier.ndim, multiplier.shape))
assert image.ndim == 3, (
"Expected `image` to be 3-dimensional when multiplying by one "
"value per channel, got %d-dimensional data with shape %s." % (
image.ndim, image.shape))
assert image.shape[-1] == multiplier.size, (
"Expected number of channels in `image` and number of components "
"in `multiplier` to be identical. Got %d vs. %d." % (
image.shape[-1], multiplier.size))
value_range = np.broadcast_to(value_range[:, np.newaxis],
(256, nb_channels))
value_range = value_range * multiplier[np.newaxis, :]
else:
value_range = value_range * multiplier
value_range = np.clip(value_range, 0, 255).astype(image.dtype)
return ia.apply_lut_(image, value_range)
# Added in 0.5.0.
def _multiply_scalar_to_uint8_cv2_mul_(image, multiplier):
# multiplier must already be an array_like
if multiplier.size > 1:
multiplier = multiplier[np.newaxis, np.newaxis, :]
multiplier = np.broadcast_to(multiplier, image.shape)
else:
multiplier = np.full(image.shape, multiplier, dtype=np.float32)
image = _normalize_cv2_input_arr_(image)
result = cv2.multiply(
image,
multiplier,
dtype=cv2.CV_8U,
dst=image
)
return result
def _multiply_scalar_to_non_uint8(image, multiplier):
# TODO estimate via image min/max values whether a resolution
# increase is necessary
input_dtype = image.dtype
is_single_value = (
ia.is_single_number(multiplier)
or ia.is_np_scalar(multiplier)
or (ia.is_np_array(multiplier) and multiplier.size == 1))
is_channelwise = not is_single_value
nb_channels = 1 if image.ndim == 2 else image.shape[-1]
shape = (1, 1, nb_channels if is_channelwise else 1)
multiplier = np.array(multiplier).reshape(shape)
# deactivated itemsize increase due to clip causing problems
# with int64, see Add
# mul_min = np.min(mul)
# mul_max = np.max(mul)
# is_not_increasing_value_range = (
# (-1 <= mul_min <= 1)
# and (-1 <= mul_max <= 1))
# We limit here the value range of the mul parameter to the
# bytes in the image's dtype. This prevents overflow problems
# and makes it less likely that the image has to be up-casted,
# which again improves performance and saves memory. Note that
# this also enables more dtypes for image inputs.
# The downside is that the mul parameter is limited in its
# value range.
itemsize = max(
image.dtype.itemsize,
2 if multiplier.dtype.kind == "f" else 1
) # float min itemsize is 2 not 1
dtype_target = np.dtype("%s%d" % (multiplier.dtype.kind, itemsize))
multiplier = iadt.clip_to_dtype_value_range_(
multiplier, dtype_target, validate=True)
image, multiplier = iadt.promote_array_dtypes_(
[image, multiplier],
dtypes=[image.dtype, dtype_target],
# increase_itemsize_factor=(
# 1 if is_not_increasing_value_range else 2)
increase_itemsize_factor=1
)
image = np.multiply(image, multiplier, out=image, casting="no")
return iadt.restore_dtypes_(image, input_dtype)
def multiply_elementwise(image, multipliers):
"""Multiply an image with an array of values.
This method ensures that ``uint8`` does not overflow during the addition.
note::
Tests were only conducted for rather small multipliers, around
``-10.0`` to ``+10.0``.
In general, the multipliers sampled from `multipliers` must be in a
value range that corresponds to the input image's dtype. E.g. if the
input image has dtype ``uint16`` and the samples generated from
`multipliers` are ``float64``, this function will still force all
samples to be within the value range of ``float16``, as it has the
same number of bytes (two) as ``uint16``. This is done to make
overflows less likely to occur.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: limited; tested (1)
* ``uint32``: no
* ``uint64``: no
* ``int8``: limited; tested (1)
* ``int16``: limited; tested (1)
* ``int32``: no
* ``int64``: no
* ``float16``: limited; tested (1)
* ``float32``: limited; tested (1)
* ``float64``: no
* ``float128``: no
* ``bool``: limited; tested (1)
- (1) Non-uint8 dtypes can overflow. For floats, this can result
in +/-inf.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
multipliers : ndarray
The multipliers with which to multiply the image. Expected to have
the same height and width as `image` and either no channels or one
channel or the same number of channels as `image`.
Returns
-------
ndarray
Image, multiplied by `multipliers`.
"""
return multiply_elementwise_(np.copy(image), multipliers)
def multiply_elementwise_(image, multipliers):
"""Multiply in-place an image with an array of values.
This method ensures that ``uint8`` does not overflow during the addition.
note::
Tests were only conducted for rather small multipliers, around
``-10.0`` to ``+10.0``.
In general, the multipliers sampled from `multipliers` must be in a
value range that corresponds to the input image's dtype. E.g. if the
input image has dtype ``uint16`` and the samples generated from
`multipliers` are ``float64``, this function will still force all
samples to be within the value range of ``float16``, as it has the
same number of bytes (two) as ``uint16``. This is done to make
overflows less likely to occur.
Added in 0.5.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: limited; tested (1)
* ``uint32``: no
* ``uint64``: no
* ``int8``: limited; tested (1)
* ``int16``: limited; tested (1)
* ``int32``: no
* ``int64``: no
* ``float16``: limited; tested (1)
* ``float32``: limited; tested (1)
* ``float64``: no
* ``float128``: no
* ``bool``: limited; tested (1)
- (1) Non-uint8 dtypes can overflow. For floats, this can result
in +/-inf.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
multipliers : ndarray
The multipliers with which to multiply the image. Expected to have
the same height and width as `image` and either no channels or one
channel or the same number of channels as `image`.
Returns
-------
ndarray
Image, multiplied by `multipliers`.
"""
iadt.gate_dtypes_strs(
{image.dtype},
allowed="bool uint8 uint16 int8 int16 float16 float32",
disallowed="uint32 uint64 int32 int64 float64 float128",
augmenter=None)
if 0 in image.shape:
return image
if multipliers.dtype.kind == "b":
# TODO extend this with some shape checks
image *= multipliers
return image
if image.dtype == iadt._UINT8_DTYPE:
return _multiply_elementwise_to_uint8_(image, multipliers)
return _multiply_elementwise_to_non_uint8(image, multipliers)
# Added in 0.5.0.
def _multiply_elementwise_to_uint8_(image, multipliers):
dt = multipliers.dtype
kind = dt.kind
if kind == "f" and dt != iadt._FLOAT32_DTYPE:
multipliers = multipliers.astype(np.float32)
elif kind == "i" and dt != iadt._INT32_DTYPE:
multipliers = multipliers.astype(np.int32)
elif kind == "u" and dt != iadt._UINT8_DTYPE:
multipliers = multipliers.astype(np.uint8)
if multipliers.ndim < image.ndim:
multipliers = multipliers[:, :, np.newaxis]
if multipliers.shape != image.shape:
multipliers = np.broadcast_to(multipliers, image.shape)
assert image.shape == multipliers.shape, (
"Expected multipliers to have shape (H,W) or (H,W,1) or (H,W,C) "
"(H = image height, W = image width, C = image channels). Reached "
"shape %s after broadcasting, compared to image shape %s." % (
multipliers.shape, image.shape
)
)
# views seem to be fine here
if image.flags["C_CONTIGUOUS"] is False:
image = np.ascontiguousarray(image)
result = cv2.multiply(image, multipliers, dst=image, dtype=cv2.CV_8U)
return result
def _multiply_elementwise_to_non_uint8(image, multipliers):
input_dtype = image.dtype
# TODO maybe introduce to stochastic parameters some way to
# get the possible min/max values, could make things
# faster for dropout to get 0/1 min/max from the binomial
# itemsize decrease is currently deactivated due to issues
# with clip and int64, see Add
mul_min = np.min(multipliers)
mul_max = np.max(multipliers)
# is_not_increasing_value_range = (
# (-1 <= mul_min <= 1) and (-1 <= mul_max <= 1))
# We limit here the value range of the mul parameter to the
# bytes in the image's dtype. This prevents overflow problems
# and makes it less likely that the image has to be up-casted,
# which again improves performance and saves memory. Note that
# this also enables more dtypes for image inputs.
# The downside is that the mul parameter is limited in its
# value range.
itemsize = max(
image.dtype.itemsize,
2 if multipliers.dtype.kind == "f" else 1
) # float min itemsize is 2
dtype_target = np.dtype("%s%d" % (multipliers.dtype.kind, itemsize))
multipliers = iadt.clip_to_dtype_value_range_(
multipliers, dtype_target,
validate=True, validate_values=(mul_min, mul_max))
if multipliers.shape[2] == 1:
# TODO check if tile() is here actually needed
nb_channels = image.shape[-1]
multipliers = np.tile(multipliers, (1, 1, nb_channels))
image, multipliers = iadt.promote_array_dtypes_(
[image, multipliers],
dtypes=[image, dtype_target],
increase_itemsize_factor=1
# increase_itemsize_factor=(
# 1 if is_not_increasing_value_range else 2)
)
image = np.multiply(image, multipliers, out=image, casting="no")
return iadt.restore_dtypes_(image, input_dtype)
def cutout(image, x1, y1, x2, y2,
fill_mode="constant", cval=0, fill_per_channel=False,
seed=None):
"""Fill a single area within an image using a fill mode.
This cutout method uses the top-left and bottom-right corner coordinates
of the cutout region given as absolute pixel values.
.. note::
Gaussian fill mode will assume that float input images contain values
in the interval ``[0.0, 1.0]`` and hence sample values from a
gaussian within that interval, i.e. from ``N(0.5, std=0.5/3)``.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
Added in 0.4.0.
Parameters
----------
image : ndarray
Image to modify.
x1 : number
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
y1 : number
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
x2 : number
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
y2 : number
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
fill_mode : {'constant', 'gaussian'}, optional
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
cval : number or tuple of number, optional
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
fill_per_channel : number or bool, optional
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
Returns
-------
ndarray
Image with area filled in.
"""
return cutout_(np.copy(image),
x1, y1, x2, y2,
fill_mode, cval, fill_per_channel, seed)
def cutout_(image, x1, y1, x2, y2,
fill_mode="constant", cval=0, fill_per_channel=False,
seed=None):
"""Fill a single area within an image using a fill mode (in-place).
This cutout method uses the top-left and bottom-right corner coordinates
of the cutout region given as absolute pixel values.
.. note::
Gaussian fill mode will assume that float input images contain values
in the interval ``[0.0, 1.0]`` and hence sample values from a
gaussian within that interval, i.e. from ``N(0.5, std=0.5/3)``.
Added in 0.4.0.
**Supported dtypes**:
minimum of (
:func:`~imgaug.augmenters.arithmetic._fill_rectangle_gaussian_`,
:func:`~imgaug.augmenters.arithmetic._fill_rectangle_constant_`
)
Parameters
----------
image : ndarray
Image to modify. Might be modified in-place.
x1 : number
X-coordinate of the top-left corner of the cutout region.
y1 : number
Y-coordinate of the top-left corner of the cutout region.
x2 : number
X-coordinate of the bottom-right corner of the cutout region.
y2 : number
Y-coordinate of the bottom-right corner of the cutout region.
fill_mode : {'constant', 'gaussian'}, optional
Fill mode to use.
cval : number or tuple of number, optional
The constant value to use when filling with mode ``constant``.
May be an intensity value or color tuple.
fill_per_channel : number or bool, optional
Whether to fill in a channelwise fashion.
If number then a value ``>=0.5`` will be interpreted as ``True``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
A random number generator to sample random values from.
Usually an integer seed value or an ``RNG`` instance.
See :class:`imgaug.random.RNG` for details.
Returns
-------
ndarray
Image with area filled in.
The input image might have been modified in-place.
"""
import importlib
height, width = image.shape[0:2]
x1 = min(max(int(x1), 0), width)
y1 = min(max(int(y1), 0), height)
x2 = min(max(int(x2), 0), width)
y2 = min(max(int(y2), 0), height)
if x2 > x1 and y2 > y1:
assert fill_mode in _CUTOUT_FILL_MODES, (
"Expected one of the following fill modes: %s. "
"Got: %s." % (
str(list(_CUTOUT_FILL_MODES.keys())), fill_mode))
module_name, fname = _CUTOUT_FILL_MODES[fill_mode]
module = importlib.import_module(module_name)
func = getattr(module, fname)
image = func(
image,
x1=x1, y1=y1, x2=x2, y2=y2,
cval=cval,
per_channel=(fill_per_channel >= 0.5),
random_state=(
iarandom.RNG(seed)
if not isinstance(seed, iarandom.RNG)
else seed) # only RNG(.) without "if" is ~8x slower
)
return image
def _fill_rectangle_gaussian_(image, x1, y1, x2, y2, cval, per_channel,
random_state):
"""Fill a rectangular image area with samples from a gaussian.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: limited; tested (1)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: limited; tested (1)
* ``float16``: yes; tested (2)
* ``float32``: yes; tested (2)
* ``float64``: yes; tested (2)
* ``float128``: limited; tested (1) (2)
* ``bool``: yes; tested
- (1) Possible loss of resolution due to gaussian values being sampled
as ``float64`` s.
- (2) Float input arrays are assumed to be in interval ``[0.0, 1.0]``
and all gaussian samples are within that interval too.
"""
# for float we assume value range [0.0, 1.0]
# that matches the common use case and also makes the tests way easier
# we also set bool here manually as the center value returned by
# get_value_range_for_dtype() is None
kind = image.dtype.kind
if kind in ["f", "b"]:
min_value = 0.0
center_value = 0.5
max_value = 1.0
else:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(
image.dtype)
# set standard deviation to 1/3 of value range to get 99.7% of values
# within [min v.r., max v.r.]
# we also divide by 2 because we want to spread towards the
# "left"/"right" of the center value by half of the value range
stddev = (float(max_value) - float(min_value)) / 2.0 / 3.0
height = y2 - y1
width = x2 - x1
shape = (height, width)
if per_channel and image.ndim == 3:
shape = shape + (image.shape[2],)
rect = random_state.normal(center_value, stddev, size=shape)
if image.dtype.kind == "b":
rect_vr = (rect > 0.5)
else:
rect_vr = np.clip(rect, min_value, max_value).astype(image.dtype)
if image.ndim == 3:
image[y1:y2, x1:x2, :] = np.atleast_3d(rect_vr)
else:
image[y1:y2, x1:x2] = rect_vr
return image
def _fill_rectangle_constant_(image, x1, y1, x2, y2, cval, per_channel,
random_state):
"""Fill a rectangular area within an image with constant value(s).
`cval` may be a single value or one per channel. If the number of items
in `cval` does not match the number of channels in `image`, it may
be tiled up to the number of channels.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
"""
if ia.is_iterable(cval):
if per_channel:
nb_channels = None if image.ndim == 2 else image.shape[-1]
if nb_channels is None:
cval = cval[0]
elif len(cval) < nb_channels:
mul = int(np.ceil(nb_channels / len(cval)))
cval = np.tile(cval, (mul,))[0:nb_channels]
elif len(cval) > nb_channels:
cval = cval[0:nb_channels]
else:
cval = cval[0]
# without the array(), uint64 max value is assigned as 0
image[y1:y2, x1:x2, ...] = np.array(cval, dtype=image.dtype)
return image
def replace_elementwise_(image, mask, replacements):
"""Replace components in an image array with new values.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: no (1)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: no (2)
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no
* ``bool``: yes; tested
- (1) ``uint64`` is currently not supported, because
:func:`~imgaug.dtypes.clip_to_dtype_value_range_()` does not
support it, which again is because numpy.clip() seems to not
support it.
- (2) `int64` is disallowed due to being converted to `float64`
by :func:`numpy.clip` since 1.17 (possibly also before?).
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
mask : ndarray
Mask of shape ``(H,W,[C])`` denoting which components to replace.
If ``C`` is provided, it must be ``1`` or match the ``C`` of `image`.
May contain floats in the interval ``[0.0, 1.0]``.
replacements : iterable
Replacements to place in `image` at the locations defined by `mask`.
This 1-dimensional iterable must contain exactly as many values
as there are replaced components in `image`.
Returns
-------
ndarray
Image with replaced components.
"""
iadt.gate_dtypes_strs(
{image.dtype},
allowed="bool uint8 uint16 uint32 int8 int16 int32 float16 float32 "
"float64",
disallowed="uint64 int64 float128",
augmenter=None
)
# This is slightly faster (~20%) for masks that are True at many
# locations, but slower (~50%) for masks with few Trues, which is
# probably the more common use-case:
#
# replacement_samples = self.replacement.draw_samples(
# sampling_shape, random_state=rs_replacement)
#
# # round, this makes 0.2 e.g. become 0 in case of boolean
# # image (otherwise replacing values with 0.2 would
# # lead to True instead of False).
# if (image.dtype.kind in ["i", "u", "b"]
# and replacement_samples.dtype.kind == "f"):
# replacement_samples = np.round(replacement_samples)
#
# replacement_samples = iadt.clip_to_dtype_value_range_(
# replacement_samples, image.dtype, validate=False)
# replacement_samples = replacement_samples.astype(
# image.dtype, copy=False)
#
# if sampling_shape[2] == 1:
# mask_samples = np.tile(mask_samples, (1, 1, nb_channels))
# replacement_samples = np.tile(
# replacement_samples, (1, 1, nb_channels))
# mask_thresh = mask_samples > 0.5
# image[mask_thresh] = replacement_samples[mask_thresh]
input_shape = image.shape
if image.ndim == 2:
image = image[..., np.newaxis]
if mask.ndim == 2:
mask = mask[..., np.newaxis]
mask_thresh = mask > 0.5
if mask.shape[2] == 1:
nb_channels = image.shape[-1]
# TODO verify if tile() is here really necessary
mask_thresh = np.tile(mask_thresh, (1, 1, nb_channels))
# round, this makes 0.2 e.g. become 0 in case of boolean
# image (otherwise replacing values with 0.2 would lead to True
# instead of False).
if image.dtype.kind in ["i", "u", "b"] and replacements.dtype.kind == "f":
replacements = np.round(replacements)
replacement_samples = iadt.clip_to_dtype_value_range_(
replacements, image.dtype, validate=False)
replacement_samples = replacement_samples.astype(image.dtype, copy=False)
image[mask_thresh] = replacement_samples
if len(input_shape) == 2:
return image[..., 0]
return image
def invert(image, min_value=None, max_value=None, threshold=None,
invert_above_threshold=True):
"""Invert an array.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.invert_`.
Parameters
----------
image : ndarray
See :func:`invert_`.
min_value : None or number, optional
See :func:`invert_`.
max_value : None or number, optional
See :func:`invert_`.
threshold : None or number, optional
See :func:`invert_`.
invert_above_threshold : bool, optional
See :func:`invert_`.
Returns
-------
ndarray
Inverted image.
"""
return invert_(np.copy(image), min_value=min_value, max_value=max_value,
threshold=threshold,
invert_above_threshold=invert_above_threshold)
def invert_(image, min_value=None, max_value=None, threshold=None,
invert_above_threshold=True):
"""Invert an array in-place.
Added in 0.4.0.
**Supported dtypes**:
if (min_value=None and max_value=None):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
if (min_value!=None or max_value!=None):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: no (1)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: no (2)
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: no (2)
* ``float128``: no (3)
* ``bool``: no (4)
- (1) Not allowed due to numpy's clip converting from ``uint64`` to
``float64``.
- (2) Not allowed as int/float have to be increased in resolution
when using min/max values.
- (3) Not tested.
- (4) Makes no sense when using min/max values.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
The array *might* be modified in-place.
min_value : None or number, optional
Minimum of the value range of input images, e.g. ``0`` for ``uint8``
images. If set to ``None``, the value will be automatically derived
from the image's dtype.
max_value : None or number, optional
Maximum of the value range of input images, e.g. ``255`` for ``uint8``
images. If set to ``None``, the value will be automatically derived
from the image's dtype.
threshold : None or number, optional
A threshold to use in order to invert only numbers above or below
the threshold. If ``None`` no thresholding will be used.
invert_above_threshold : bool, optional
If ``True``, only values ``>=threshold`` will be inverted.
Otherwise, only values ``= min_value_dt, (
"Expected min_value to be above or equal to dtype's min "
"value, got %s (vs. min possible %s for %s)" % (
str(min_value), str(min_value_dt), image.dtype.name)
)
assert max_value <= max_value_dt, (
"Expected max_value to be below or equal to dtype's max "
"value, got %s (vs. max possible %s for %s)" % (
str(max_value), str(max_value_dt), image.dtype.name)
)
assert min_value < max_value, (
"Expected min_value to be below max_value, got %s "
"and %s" % (
str(min_value), str(max_value))
)
if min_value != min_value_dt or max_value != max_value_dt:
assert image.dtype in allow_dtypes_custom_minmax, (
"Can use custom min/max values only with the following "
"dtypes: %s. Got: %s." % (
", ".join(allow_dtypes_custom_minmax), image.dtype.name))
if image.dtype == iadt._UINT8_DTYPE:
return _invert_uint8_(image, min_value, max_value, threshold,
invert_above_threshold)
dtype_kind_to_invert_func = {
"b": _invert_bool,
"u": _invert_uint16_or_larger_, # uint8 handled above
"i": _invert_int_,
"f": _invert_float
}
func = dtype_kind_to_invert_func[image.dtype.kind]
if threshold is None:
return func(image, min_value, max_value)
arr_inv = func(np.copy(image), min_value, max_value)
if invert_above_threshold:
mask = (image >= threshold)
else:
mask = (image < threshold)
image[mask] = arr_inv[mask]
return image
def _invert_bool(arr, min_value, max_value):
assert min_value == 0 and max_value == 1, (
"min_value and max_value must be 0 and 1 for bool arrays. "
"Got %.4f and %.4f." % (min_value, max_value))
return ~arr
# Added in 0.4.0.
def _invert_uint8_(arr, min_value, max_value, threshold,
invert_above_threshold):
shape = arr.shape
nb_channels = shape[-1] if len(shape) == 3 else 1
valid_for_cv2 = (
threshold is None
and min_value == 0
and len(shape) >= 2
and shape[0]*shape[1]*nb_channels != 4
)
if valid_for_cv2:
return _invert_uint8_subtract_(arr, max_value)
return _invert_uint8_lut_pregenerated_(
arr, min_value, max_value, threshold, invert_above_threshold
)
# Added in 0.5.0.
def _invert_uint8_lut_pregenerated_(arr, min_value, max_value, threshold,
invert_above_threshold):
table = _InvertTablesSingleton.get_instance().get_table(
min_value=min_value,
max_value=max_value,
threshold=threshold,
invert_above_threshold=invert_above_threshold
)
arr = ia.apply_lut_(arr, table)
return arr
# Added in 0.5.0.
def _invert_uint8_subtract_(arr, max_value):
# seems to work with arr.base.shape[0] > 1
if arr.base is not None and arr.base.shape[0] == 1:
arr = np.copy(arr)
if not arr.flags["C_CONTIGUOUS"]:
arr = np.ascontiguousarray(arr)
input_shape = arr.shape
if len(input_shape) > 2 and input_shape[-1] > 1:
arr = arr.ravel()
# This also supports a mask, which would help for thresholded invert, but
# it seems that all non-masked components are set to zero in the output
# array. Tackling this issue seems to rather require more time than just
# using a LUT.
arr = cv2.subtract(int(max_value), arr, dst=arr)
if arr.shape != input_shape:
return arr.reshape(input_shape)
return arr
# Added in 0.4.0.
def _invert_uint16_or_larger_(arr, min_value, max_value):
min_max_is_vr = (min_value == 0
and max_value == np.iinfo(arr.dtype).max)
if min_max_is_vr:
return max_value - arr
return _invert_by_distance(
np.clip(arr, min_value, max_value),
min_value, max_value
)
# Added in 0.4.0.
def _invert_int_(arr, min_value, max_value):
# note that for int dtypes the max value is
# (-1) * min_value - 1
# e.g. -128 and 127 (min/max) for int8
# mapping example:
# [-4, -3, -2, -1, 0, 1, 2, 3]
# will be mapped to
# [ 3, 2, 1, 0, -1, -2, -3, -4]
# hence we can not simply compute the inverse as:
# after = (-1) * before
# but instead need
# after = (-1) * before - 1
# however, this exceeds the value range for the minimum value, e.g.
# for int8: -128 -> 128 -> 127, where 128 exceeds it. Hence, we must
# compute the inverse via a mask (extra step for the minimum)
# or we have to increase the resolution of the array. Here, a
# two-step approach is used.
if min_value == (-1) * max_value - 1:
arr_inv = np.copy(arr)
mask = (arr_inv == min_value)
# there is probably a one-liner here to do this, but
# ((-1) * (arr_inv * ~mask) - 1) + mask * max_value
# has the disadvantage of inverting min_value to max_value - 1
# while
# ((-1) * (arr_inv * ~mask) - 1) + mask * (max_value+1)
# ((-1) * (arr_inv * ~mask) - 1) + mask * max_value + mask
# both sometimes increase the dtype resolution (e.g. int32 to int64)
arr_inv[mask] = max_value
arr_inv[~mask] = (-1) * arr_inv[~mask] - 1
return arr_inv
return _invert_by_distance(
np.clip(arr, min_value, max_value),
min_value, max_value
)
def _invert_float(arr, min_value, max_value):
if np.isclose(max_value, (-1)*min_value, rtol=0):
return (-1) * arr
return _invert_by_distance(
np.clip(arr, min_value, max_value),
min_value, max_value
)
def _invert_by_distance(arr, min_value, max_value):
arr_inv = arr
if arr.dtype.kind in ["i", "f"]:
arr_inv = iadt.increase_array_resolutions_([np.copy(arr)], 2)[0]
distance_from_min = np.abs(arr_inv - min_value) # d=abs(v-min)
arr_inv = max_value - distance_from_min # v'=MAX-d
# due to floating point inaccuracies, we might exceed the min/max
# values for floats here, hence clip this happens especially for
# values close to the float dtype's maxima
if arr.dtype.kind == "f":
arr_inv = np.clip(arr_inv, min_value, max_value)
if arr.dtype.kind in ["i", "f"]:
arr_inv = iadt.restore_dtypes_(
arr_inv, arr.dtype, clip=False)
return arr_inv
# Added in 0.4.0.
def _generate_table_for_invert_uint8(min_value, max_value, threshold,
invert_above_threshold):
table = np.arange(256).astype(np.int32)
full_value_range = (min_value == 0 and max_value == 255)
if full_value_range:
table_inv = table[::-1]
else:
distance_from_min = np.abs(table - min_value)
table_inv = max_value - distance_from_min
table_inv = np.clip(table_inv, min_value, max_value).astype(np.uint8)
if threshold is not None:
table = table.astype(np.uint8)
if invert_above_threshold:
table_inv = np.concatenate([
table[0:int(threshold)],
table_inv[int(threshold):]
], axis=0)
else:
table_inv = np.concatenate([
table_inv[0:int(threshold)],
table[int(threshold):]
], axis=0)
return table_inv
# Added in 0.5.0.
class _InvertTables(object):
# Added in 0.5.0.
def __init__(self):
self.tables = {}
# Added in 0.5.0.
def get_table(self, min_value, max_value, threshold,
invert_above_threshold):
if min_value == 0 and max_value == 255:
key = (threshold, invert_above_threshold)
table = self.tables.get(key, None)
if table is None:
table = _generate_table_for_invert_uint8(
min_value, max_value, threshold, invert_above_threshold
)
self.tables[key] = table
return table
return _generate_table_for_invert_uint8(
min_value, max_value, threshold, invert_above_threshold
)
# Added in 0.5.0.
class _InvertTablesSingleton(object):
_INSTANCE = None
# Added in 0.5.0.
@classmethod
def get_instance(cls):
if cls._INSTANCE is None:
cls._INSTANCE = _InvertTables()
return cls._INSTANCE
def solarize(image, threshold=128):
"""Invert pixel values above a threshold.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.solarize_`.
Parameters
----------
image : ndarray
See :func:`solarize_`.
threshold : None or number, optional
See :func:`solarize_`.
Returns
-------
ndarray
Inverted image.
"""
return solarize_(np.copy(image), threshold=threshold)
def solarize_(image, threshold=128):
"""Invert pixel values above a threshold in-place.
This function is a wrapper around :func:`invert`.
This function performs the same transformation as
:func:`PIL.ImageOps.solarize`.
Added in 0.4.0.
**Supported dtypes**:
See ``~imgaug.augmenters.arithmetic.invert_(min_value=None and max_value=None)``.
Parameters
----------
image : ndarray
See :func:`invert_`.
threshold : None or number, optional
See :func:`invert_`.
Note: The default threshold is optimized for ``uint8`` images.
Returns
-------
ndarray
Inverted image. This *can* be the same array as input in `image`,
modified in-place.
"""
return invert_(image, threshold=threshold)
def compress_jpeg(image, compression):
"""Compress an image using jpeg compression.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: ?
* ``uint32``: ?
* ``uint64``: ?
* ``int8``: ?
* ``int16``: ?
* ``int32``: ?
* ``int64``: ?
* ``float16``: ?
* ``float32``: ?
* ``float64``: ?
* ``float128``: ?
* ``bool``: ?
Parameters
----------
image : ndarray
Image of dtype ``uint8`` and shape ``(H,W,[C])``. If ``C`` is provided,
it must be ``1`` or ``3``.
compression : int
Strength of the compression in the interval ``[0, 100]``.
Returns
-------
ndarray
Input image after applying jpeg compression to it and reloading
the result into a new array. Same shape and dtype as the input.
"""
import PIL.Image
if image.size == 0:
return np.copy(image)
# The value range 1 to 95 is suggested by PIL's save() documentation
# Values above 95 seem to not make sense (no improvement in visual
# quality, but large file size).
# A value of 100 would mostly deactivate jpeg compression.
# A value of 0 would lead to no compression (instead of maximum
# compression).
# We use range 1 to 100 here, because this augmenter is about
# generating images for training and not for saving, hence we do not
# care about large file sizes.
maximum_quality = 100
minimum_quality = 1
iadt.allow_only_uint8({image.dtype})
assert 0 <= compression <= 100, (
"Expected compression to be in the interval [0, 100], "
"got %.4f." % (compression,))
has_no_channels = (image.ndim == 2)
is_single_channel = (image.ndim == 3 and image.shape[-1] == 1)
if is_single_channel:
image = image[..., 0]
assert has_no_channels or is_single_channel or image.shape[-1] == 3, (
"Expected either a grayscale image of shape (H,W) or (H,W,1) or an "
"RGB image of shape (H,W,3). Got shape %s." % (image.shape,))
# Map from compression to quality used by PIL
# We have valid compressions from 0 to 100, i.e. 101 possible
# values
quality = int(
np.clip(
np.round(
minimum_quality
+ (maximum_quality - minimum_quality)
* (1.0 - (compression / 101))
),
minimum_quality,
maximum_quality
)
)
image_pil = PIL.Image.fromarray(image)
with tempfile.NamedTemporaryFile(mode="wb+", suffix=".jpg") as f:
image_pil.save(f, quality=quality)
# Read back from file.
# We dont read from f.name, because that leads to PermissionDenied
# errors on Windows. We add f.seek(0) here, because otherwise we get
# `SyntaxError: index out of range` in PIL.
f.seek(0)
pilmode = "RGB"
if has_no_channels or is_single_channel:
pilmode = "L"
image = imageio.imread(f, pilmode=pilmode, format="jpeg")
if is_single_channel:
image = image[..., np.newaxis]
return image
class Add(meta.Augmenter):
"""
Add a value to all pixels in an image.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.add_scalar`.
Parameters
----------
value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Value to add to all pixels.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Add(10)
Always adds a value of 10 to all channels of all pixels of all input
images.
>>> aug = iaa.Add((-10, 10))
Adds a value from the discrete interval ``[-10..10]`` to all pixels of
input images. The exact value is sampled per image.
>>> aug = iaa.Add((-10, 10), per_channel=True)
Adds a value from the discrete interval ``[-10..10]`` to all pixels of
input images. The exact value is sampled per image *and* channel,
i.e. to a red-channel it might add 5 while subtracting 7 from the
blue channel of the same image.
>>> aug = iaa.Add((-10, 10), per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for 50 percent of all images.
"""
def __init__(self, value=(-20, 20), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Add, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.value = iap.handle_continuous_param(
value, "value", value_range=None, tuple_to_uniform=True,
list_to_choice=True, prefetch=True)
self.per_channel = iap.handle_probability_param(
per_channel, "per_channel")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
nb_channels_max = meta.estimate_max_number_of_channels(images)
rss = random_state.duplicate(2)
per_channel_samples = self.per_channel.draw_samples(
(nb_images,), random_state=rss[0])
value_samples = self.value.draw_samples(
(nb_images, nb_channels_max), random_state=rss[1])
gen = enumerate(zip(images, value_samples, per_channel_samples))
for i, (image, value_samples_i, per_channel_samples_i) in gen:
nb_channels = image.shape[2]
# Example code to directly add images via image+sample (uint8 only)
# if per_channel_samples_i > 0.5:
# result = []
# image = image.astype(np.int16)
# value_samples_i = value_samples_i.astype(np.int16)
# for c, value in enumerate(value_samples_i[0:nb_channels]):
# result.append(
# np.clip(
# image[..., c:c+1] + value, 0, 255
# ).astype(np.uint8))
# images[i] = np.concatenate(result, axis=2)
# else:
# images[i] = np.clip(
# image.astype(np.int16)
# + value_samples_i[0].astype(np.int16),
# 0, 255
# ).astype(np.uint8)
if per_channel_samples_i > 0.5:
value = value_samples_i[0:nb_channels]
else:
# the if/else here catches the case of the channel axis being 0
value = value_samples_i[0] if value_samples_i.size > 0 else []
batch.images[i] = add_scalar_(image, value)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.value, self.per_channel]
# TODO merge this with Add
class AddElementwise(meta.Augmenter):
"""
Add to the pixels of images values that are pixelwise randomly sampled.
While the ``Add`` Augmenter samples one value to add *per image* (and
optionally per channel), this augmenter samples different values per image
and *per pixel* (and optionally per channel), i.e. intensities of
neighbouring pixels may be increased/decreased by different amounts.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.add_elementwise`.
Parameters
----------
value : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Value to add to the pixels.
* If an int, exactly that value will always be used.
* If a tuple ``(a, b)``, then values from the discrete interval
``[a..b]`` will be sampled per image and pixel.
* If a list of integers, a random value will be sampled from the
list per image and pixel.
* If a ``StochasticParameter``, then values will be sampled per
image and pixel from that parameter.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AddElementwise(10)
Always adds a value of 10 to all channels of all pixels of all input
images.
>>> aug = iaa.AddElementwise((-10, 10))
Samples per image and pixel a value from the discrete interval
``[-10..10]`` and adds that value to the respective pixel.
>>> aug = iaa.AddElementwise((-10, 10), per_channel=True)
Samples per image, pixel *and also channel* a value from the discrete
interval ``[-10..10]`` and adds it to the respective pixel's channel value.
Therefore, added values may differ between channels of the same pixel.
>>> aug = iaa.AddElementwise((-10, 10), per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for 50 percent of all images.
"""
def __init__(self, value=(-20, 20), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AddElementwise, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.value = iap.handle_continuous_param(
value, "value", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
self.per_channel = iap.handle_probability_param(
per_channel, "per_channel")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
rss = random_state.duplicate(1+nb_images)
per_channel_samples = self.per_channel.draw_samples(
(nb_images,), random_state=rss[0])
gen = enumerate(zip(images, per_channel_samples, rss[1:]))
for i, (image, per_channel_samples_i, rs) in gen:
height, width, nb_channels = image.shape
sample_shape = (height,
width,
nb_channels if per_channel_samples_i > 0.5 else 1)
values = self.value.draw_samples(sample_shape, random_state=rs)
batch.images[i] = add_elementwise(image, values)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.value, self.per_channel]
# TODO rename to AddGaussianNoise?
# TODO examples say that iaa.AdditiveGaussianNoise(scale=(0, 0.1*255)) samples
# the scale from the uniform dist. per image, but is that still the case?
# AddElementwise seems to now sample once for all images, which should
# lead to a single scale value.
class AdditiveGaussianNoise(AddElementwise):
"""
Add noise sampled from gaussian distributions elementwise to images.
This augmenter samples and adds noise elementwise, i.e. it can add
different noise values to neighbouring pixels and is comparable
to ``AddElementwise``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.AddElementwise`.
Parameters
----------
loc : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Mean of the normal distribution from which the noise is sampled.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value from the interval
``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list per
image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Standard deviation of the normal distribution that generates the noise.
Must be ``>=0``. If ``0`` then `loc` will simply be added to all
pixels.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value from the interval
``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list per
image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AdditiveGaussianNoise(scale=0.1*255)
Adds gaussian noise from the distribution ``N(0, 0.1*255)`` to images.
The samples are drawn per image and pixel.
>>> aug = iaa.AdditiveGaussianNoise(scale=(0, 0.1*255))
Adds gaussian noise from the distribution ``N(0, s)`` to images,
where ``s`` is sampled per image from the interval ``[0, 0.1*255]``.
>>> aug = iaa.AdditiveGaussianNoise(scale=0.1*255, per_channel=True)
Adds gaussian noise from the distribution ``N(0, 0.1*255)`` to images,
where the noise value is different per image and pixel *and* channel (e.g.
a different one for red, green and blue channels of the same pixel).
This leads to "colorful" noise.
>>> aug = iaa.AdditiveGaussianNoise(scale=0.1*255, per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for 50 percent of all images.
"""
def __init__(self, loc=0, scale=(0, 15), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
loc2 = iap.handle_continuous_param(
loc, "loc", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
scale2 = iap.handle_continuous_param(
scale, "scale", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True)
value = iap.Normal(loc=loc2, scale=scale2)
super(AdditiveGaussianNoise, self).__init__(
value, per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO add tests
# TODO rename to AddLaplaceNoise?
class AdditiveLaplaceNoise(AddElementwise):
"""
Add noise sampled from laplace distributions elementwise to images.
The laplace distribution is similar to the gaussian distribution, but
puts more weight on the long tail. Hence, this noise will add more
outliers (very high/low values). It is somewhere between gaussian noise and
salt and pepper noise.
Values of around ``255 * 0.05`` for `scale` lead to visible noise (for
``uint8``).
Values of around ``255 * 0.10`` for `scale` lead to very visible
noise (for ``uint8``).
It is recommended to usually set `per_channel` to ``True``.
This augmenter samples and adds noise elementwise, i.e. it can add
different noise values to neighbouring pixels and is comparable
to ``AddElementwise``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.AddElementwise`.
Parameters
----------
loc : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Mean of the laplace distribution that generates the noise.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value from the interval
``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list per
image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Standard deviation of the laplace distribution that generates the noise.
Must be ``>=0``. If ``0`` then only `loc` will be used.
Recommended to be around ``255*0.05``.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value from the interval
``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list per
image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AdditiveLaplaceNoise(scale=0.1*255)
Adds laplace noise from the distribution ``Laplace(0, 0.1*255)`` to images.
The samples are drawn per image and pixel.
>>> aug = iaa.AdditiveLaplaceNoise(scale=(0, 0.1*255))
Adds laplace noise from the distribution ``Laplace(0, s)`` to images,
where ``s`` is sampled per image from the interval ``[0, 0.1*255]``.
>>> aug = iaa.AdditiveLaplaceNoise(scale=0.1*255, per_channel=True)
Adds laplace noise from the distribution ``Laplace(0, 0.1*255)`` to images,
where the noise value is different per image and pixel *and* channel (e.g.
a different one for the red, green and blue channels of the same pixel).
This leads to "colorful" noise.
>>> aug = iaa.AdditiveLaplaceNoise(scale=0.1*255, per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for 50 percent of all images.
"""
def __init__(self, loc=0, scale=(0, 15), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
loc2 = iap.handle_continuous_param(
loc, "loc", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
scale2 = iap.handle_continuous_param(
scale, "scale", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True)
value = iap.Laplace(loc=loc2, scale=scale2)
super(AdditiveLaplaceNoise, self).__init__(
value,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO add tests
# TODO rename to AddPoissonNoise?
class AdditivePoissonNoise(AddElementwise):
"""
Add noise sampled from poisson distributions elementwise to images.
Poisson noise is comparable to gaussian noise, as e.g. generated via
``AdditiveGaussianNoise``. As poisson distributions produce only positive
numbers, the sign of the sampled values are here randomly flipped.
Values of around ``10.0`` for `lam` lead to visible noise (for ``uint8``).
Values of around ``20.0`` for `lam` lead to very visible noise (for
``uint8``).
It is recommended to usually set `per_channel` to ``True``.
This augmenter samples and adds noise elementwise, i.e. it can add
different noise values to neighbouring pixels and is comparable
to ``AddElementwise``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.AddElementwise`.
Parameters
----------
lam : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Lambda parameter of the poisson distribution. Must be ``>=0``.
Recommended values are around ``0.0`` to ``10.0``.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value from the interval
``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AdditivePoissonNoise(lam=5.0)
Adds poisson noise sampled from a poisson distribution with a ``lambda``
parameter of ``5.0`` to images.
The samples are drawn per image and pixel.
>>> aug = iaa.AdditivePoissonNoise(lam=(0.0, 15.0))
Adds poisson noise sampled from ``Poisson(x)`` to images, where ``x`` is
randomly sampled per image from the interval ``[0.0, 15.0]``.
>>> aug = iaa.AdditivePoissonNoise(lam=5.0, per_channel=True)
Adds poisson noise sampled from ``Poisson(5.0)`` to images,
where the values are different per image and pixel *and* channel (e.g. a
different one for red, green and blue channels for the same pixel).
>>> aug = iaa.AdditivePoissonNoise(lam=(0.0, 15.0), per_channel=True)
Adds poisson noise sampled from ``Poisson(x)`` to images,
with ``x`` being sampled from ``uniform(0.0, 15.0)`` per image and
channel. This is the *recommended* configuration.
>>> aug = iaa.AdditivePoissonNoise(lam=(0.0, 15.0), per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for 50 percent of all images.
"""
def __init__(self, lam=(0.0, 15.0), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
lam2 = iap.handle_continuous_param(
lam, "lam",
value_range=(0, None), tuple_to_uniform=True, list_to_choice=True)
value = iap.RandomSign(iap.Poisson(lam=lam2))
super(AdditivePoissonNoise, self).__init__(
value,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Multiply(meta.Augmenter):
"""
Multiply all pixels in an image with a random value sampled once per image.
This augmenter can be used to make images lighter or darker.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.multiply_scalar`.
Parameters
----------
mul : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
The value with which to multiply the pixel values in each image.
* If a number, then that value will always be used.
* If a tuple ``(a, b)``, then a value from the interval ``[a, b]``
will be sampled per image and used for all pixels.
* If a list, then a random value will be sampled from that list per
image.
* If a ``StochasticParameter``, then that parameter will be used to
sample a new value per image.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Multiply(2.0)
Multiplies all images by a factor of ``2``, making the images significantly
brighter.
>>> aug = iaa.Multiply((0.5, 1.5))
Multiplies images by a random value sampled uniformly from the interval
``[0.5, 1.5]``, making some images darker and others brighter.
>>> aug = iaa.Multiply((0.5, 1.5), per_channel=True)
Identical to the previous example, but the sampled multipliers differ by
image *and* channel, instead of only by image.
>>> aug = iaa.Multiply((0.5, 1.5), per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for 50 percent of all images.
"""
def __init__(self, mul=(0.8, 1.2), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Multiply, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.mul = iap.handle_continuous_param(
mul, "mul", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
self.per_channel = iap.handle_probability_param(
per_channel, "per_channel")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
nb_channels_max = meta.estimate_max_number_of_channels(images)
rss = random_state.duplicate(2)
per_channel_samples = self.per_channel.draw_samples(
(nb_images,), random_state=rss[0])
mul_samples = self.mul.draw_samples(
(nb_images, nb_channels_max), random_state=rss[1])
gen = enumerate(zip(images, mul_samples, per_channel_samples))
for i, (image, mul_samples_i, per_channel_samples_i) in gen:
nb_channels = image.shape[2]
# Example code to directly multiply images via image*sample
# (uint8 only) -- apparently slower than LUT
# if per_channel_samples_i > 0.5:
# result = []
# image = image.astype(np.float32)
# mul_samples_i = mul_samples_i.astype(np.float32)
# for c, mul in enumerate(mul_samples_i[0:nb_channels]):
# result.append(
# np.clip(
# image[..., c:c+1] * mul, 0, 255
# ).astype(np.uint8))
# images[i] = np.concatenate(result, axis=2)
# else:
# images[i] = np.clip(
# image.astype(np.float32)
# * mul_samples_i[0].astype(np.float32),
# 0, 255
# ).astype(np.uint8)
if per_channel_samples_i > 0.5:
mul = mul_samples_i[0:nb_channels]
else:
# the if/else here catches the case of the channel axis being 0
mul = mul_samples_i[0] if mul_samples_i.size > 0 else []
batch.images[i] = multiply_scalar_(image, mul)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.mul, self.per_channel]
# TODO merge with Multiply
class MultiplyElementwise(meta.Augmenter):
"""
Multiply image pixels with values that are pixelwise randomly sampled.
While the ``Multiply`` Augmenter uses a constant multiplier *per
image* (and optionally channel), this augmenter samples the multipliers
to use per image and *per pixel* (and optionally per channel).
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.multiply_elementwise`.
Parameters
----------
mul : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
The value with which to multiply pixel values in the image.
* If a number, then that value will always be used.
* If a tuple ``(a, b)``, then a value from the interval ``[a, b]``
will be sampled per image and pixel.
* If a list, then a random value will be sampled from that list
per image and pixel.
* If a ``StochasticParameter``, then that parameter will be used to
sample a new value per image and pixel.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MultiplyElementwise(2.0)
Multiply all images by a factor of ``2.0``, making them significantly
bighter.
>>> aug = iaa.MultiplyElementwise((0.5, 1.5))
Samples per image and pixel uniformly a value from the interval
``[0.5, 1.5]`` and multiplies the pixel with that value.
>>> aug = iaa.MultiplyElementwise((0.5, 1.5), per_channel=True)
Samples per image and pixel *and channel* uniformly a value from the
interval ``[0.5, 1.5]`` and multiplies the pixel with that value. Therefore,
used multipliers may differ between channels of the same pixel.
>>> aug = iaa.MultiplyElementwise((0.5, 1.5), per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for 50 percent of all images.
"""
def __init__(self, mul=(0.8, 1.2), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MultiplyElementwise, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.mul = iap.handle_continuous_param(
mul, "mul",
value_range=None, tuple_to_uniform=True, list_to_choice=True)
self.per_channel = iap.handle_probability_param(per_channel,
"per_channel")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
rss = random_state.duplicate(1+nb_images)
per_channel_samples = self.per_channel.draw_samples(
(nb_images,), random_state=rss[0])
is_mul_binomial = isinstance(self.mul, iap.Binomial) or (
isinstance(self.mul, iap.FromLowerResolution)
and isinstance(self.mul.other_param, iap.Binomial)
)
gen = enumerate(zip(images, per_channel_samples, rss[1:]))
for i, (image, per_channel_samples_i, rs) in gen:
height, width, nb_channels = image.shape
sample_shape = (height,
width,
nb_channels if per_channel_samples_i > 0.5 else 1)
mul = self.mul.draw_samples(sample_shape, random_state=rs)
# TODO let Binomial return boolean mask directly instead of [0, 1]
# integers?
# hack to improve performance for Dropout and CoarseDropout
# converts mul samples to mask if mul is binomial
if mul.dtype.kind != "b" and is_mul_binomial:
mul = mul.astype(bool, copy=False)
batch.images[i] = multiply_elementwise_(image, mul)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.mul, self.per_channel]
# Added in 0.4.0.
class _CutoutSamples(object):
# Added in 0.4.0.
def __init__(self, nb_iterations, pos_x, pos_y, size_h, size_w, squared,
fill_mode, cval, fill_per_channel):
self.nb_iterations = nb_iterations
self.pos_x = pos_x
self.pos_y = pos_y
self.size_h = size_h
self.size_w = size_w
self.squared = squared
self.fill_mode = fill_mode
self.cval = cval
self.fill_per_channel = fill_per_channel
class Cutout(meta.Augmenter):
"""Fill one or more rectangular areas in an image using a fill mode.
See paper "Improved Regularization of Convolutional Neural Networks with
Cutout" by DeVries and Taylor.
In contrast to the paper, this implementation also supports replacing
image sub-areas with gaussian noise, random intensities or random RGB
colors. It also supports non-squared areas. While the paper uses
absolute pixel values for the size and position, this implementation
uses relative values, which seems more appropriate for mixed-size
datasets. The position parameter furthermore allows more flexibility, e.g.
gaussian distributions around the center.
.. note::
This augmenter affects only image data. Other datatypes (e.g.
segmentation map pixels or keypoints within the filled areas)
are not affected.
.. note::
Gaussian fill mode will assume that float input images contain values
in the interval ``[0.0, 1.0]`` and hence sample values from a
gaussian within that interval, i.e. from ``N(0.5, std=0.5/3)``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.cutout_`.
Parameters
----------
nb_iterations : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
How many rectangular areas to fill.
* If ``int``: Exactly that many areas will be filled on all images.
* If ``tuple`` ``(a, b)``: A value from the interval ``[a, b]``
will be sampled per image.
* If ``list``: A random value will be sampled from that ``list``
per image.
* If ``StochasticParameter``: That parameter will be used to
sample ``(B,)`` values per batch of ``B`` images.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
Defines the position of each area to fill.
Analogous to the definition in e.g.
:class:`~imgaug.augmenters.size.CropToFixedSize`.
Usually, ``uniform`` (anywhere in the image) or ``normal`` (anywhere
in the image with preference around the center) are sane values.
size : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
The size of the rectangle to fill as a fraction of the corresponding
image size, i.e. with value range ``[0.0, 1.0]``. The size is sampled
independently per image axis.
* If ``number``: Exactly that size is always used.
* If ``tuple`` ``(a, b)``: A value from the interval ``[a, b]``
will be sampled per area and axis.
* If ``list``: A random value will be sampled from that ``list``
per area and axis.
* If ``StochasticParameter``: That parameter will be used to
sample ``(N, 2)`` values per batch, where ``N`` is the total
number of areas to fill within the whole batch.
squared : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to generate only squared areas cutout areas or allow
rectangular ones. If this evaluates to a true-like value, the
first value from `size` will be converted to absolute pixels and used
for both axes.
If this value is a float ``p``, then for ``p`` percent of all areas
to be filled `per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
fill_mode : str or list of str or imgaug.parameters.StochasticParameter, optional
Mode to use in order to fill areas. Corresponds to ``mode`` parameter
in some other augmenters. Valid strings for the mode are:
* ``contant``: Fill each area with a single value.
* ``gaussian``: Fill each area with gaussian noise.
Valid datatypes are:
* If ``str``: Exactly that mode will alaways be used.
* If ``list``: A random value will be sampled from that ``list``
per area.
* If ``StochasticParameter``: That parameter will be used to
sample ``(N,)`` values per batch, where ``N`` is the total number
of areas to fill within the whole batch.
cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
The value to use (i.e. the color) to fill areas if `fill_mode` is
```constant``.
* If ``number``: Exactly that value is used for all areas
and channels.
* If ``tuple`` ``(a, b)``: A value from the interval ``[a, b]``
will be sampled per area (and channel if ``per_channel=True``).
* If ``list``: A random value will be sampled from that ``list``
per area (and channel if ``per_channel=True``).
* If ``StochasticParameter``: That parameter will be used to
sample ``(N, Cmax)`` values per batch, where ``N`` is the total
number of areas to fill within the whole batch and ``Cmax``
is the maximum number of channels in any image (usually ``3``).
If ``per_channel=False``, only the first value of the second
axis is used.
fill_per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to fill each area in a channelwise fashion (``True``) or
not (``False``).
The behaviour per fill mode is:
* ``constant``: Whether to fill all channels with the same value
(i.e, grayscale) or different values (i.e. usually RGB color).
* ``gaussian``: Whether to sample once from a gaussian and use the
values for all channels (i.e. grayscale) or to sample
channelwise (i.e. RGB colors)
If this value is a float ``p``, then for ``p`` percent of all areas
to be filled `per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
deterministic : bool, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.bit_generator.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Cutout(nb_iterations=2)
Fill per image two random areas, by default with grayish pixels.
>>> aug = iaa.Cutout(nb_iterations=(1, 5), size=0.2, squared=False)
Fill per image between one and five areas, each having ``20%``
of the corresponding size of the height and width (for non-square
images this results in non-square areas to be filled).
>>> aug = iaa.Cutout(fill_mode="constant", cval=255)
Fill all areas with white pixels.
>>> aug = iaa.Cutout(fill_mode="constant", cval=(0, 255),
>>> fill_per_channel=0.5)
Fill ``50%`` of all areas with a random intensity value between
``0`` and ``256``. Fill the other ``50%`` of all areas with
random colors.
>>> aug = iaa.Cutout(fill_mode="gaussian", fill_per_channel=True)
Fill areas with gaussian channelwise noise (i.e. usually RGB).
"""
# Added in 0.4.0.
def __init__(self,
nb_iterations=1,
position="uniform",
size=0.2,
squared=True,
fill_mode="constant",
cval=128,
fill_per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
from .size import _handle_position_parameter # TODO move to iap
from .geometric import _handle_cval_arg # TODO move to iap
super(Cutout, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.nb_iterations = iap.handle_discrete_param(
nb_iterations, "nb_iterations", value_range=(0, None),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
self.position = _handle_position_parameter(position)
self.size = iap.handle_continuous_param(
size, "size", value_range=(0.0, 1.0+1e-4),
tuple_to_uniform=True, list_to_choice=True)
self.squared = iap.handle_probability_param(squared, "squared")
self.fill_mode = self._handle_fill_mode_param(fill_mode)
self.cval = _handle_cval_arg(cval)
self.fill_per_channel = iap.handle_probability_param(
fill_per_channel, "fill_per_channel")
# Added in 0.4.0.
@classmethod
def _handle_fill_mode_param(cls, fill_mode):
if ia.is_string(fill_mode):
assert fill_mode in _CUTOUT_FILL_MODES, (
"Expected 'fill_mode' to be one of: %s. Got %s." % (
str(list(_CUTOUT_FILL_MODES.keys())), fill_mode))
return iap.Deterministic(fill_mode)
if isinstance(fill_mode, iap.StochasticParameter):
return fill_mode
assert ia.is_iterable(fill_mode), (
"Expected 'fill_mode' to be a string, "
"StochasticParameter or list of strings. Got type %s." % (
type(fill_mode).__name__))
return iap.Choice(fill_mode)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
samples = self._draw_samples(batch.images, random_state)
# map from xyhw to xyxy (both relative coords)
cutout_height_half = samples.size_h / 2
cutout_width_half = samples.size_w / 2
x1_rel = samples.pos_x - cutout_width_half
y1_rel = samples.pos_y - cutout_height_half
x2_rel = samples.pos_x + cutout_width_half
y2_rel = samples.pos_y + cutout_height_half
nb_iterations_sum = 0
gen = enumerate(zip(batch.images, samples.nb_iterations))
for i, (image, nb_iterations) in gen:
start = nb_iterations_sum
end = start + nb_iterations
height, width = image.shape[0:2]
# map from relative xyxy to absolute xyxy coords
batch.images[i] = self._augment_image_by_samples(
image,
x1_rel[start:end] * width,
y1_rel[start:end] * height,
x2_rel[start:end] * width,
y2_rel[start:end] * height,
samples.squared[start:end],
samples.fill_mode[start:end],
samples.cval[start:end],
samples.fill_per_channel[start:end],
random_state)
nb_iterations_sum += nb_iterations
return batch
# Added in 0.4.0.
def _draw_samples(self, images, random_state):
rngs = random_state.duplicate(8)
nb_rows = len(images)
nb_channels_max = meta.estimate_max_number_of_channels(images)
nb_iterations = self.nb_iterations.draw_samples(
(nb_rows,), random_state=rngs[0])
nb_dropped_areas = int(np.sum(nb_iterations))
if isinstance(self.position, tuple):
pos_x = self.position[0].draw_samples((nb_dropped_areas,),
random_state=rngs[1])
pos_y = self.position[1].draw_samples((nb_dropped_areas,),
random_state=rngs[2])
else:
pos = self.position.draw_samples((nb_dropped_areas, 2),
random_state=rngs[1])
pos_x = pos[:, 0]
pos_y = pos[:, 1]
size = self.size.draw_samples((nb_dropped_areas, 2),
random_state=rngs[3])
squared = self.squared.draw_samples((nb_dropped_areas,),
random_state=rngs[4])
fill_mode = self.fill_mode.draw_samples(
(nb_dropped_areas,), random_state=rngs[5])
cval = self.cval.draw_samples((nb_dropped_areas, nb_channels_max),
random_state=rngs[6])
fill_per_channel = self.fill_per_channel.draw_samples(
(nb_dropped_areas,), random_state=rngs[7])
return _CutoutSamples(
nb_iterations=nb_iterations,
pos_x=pos_x,
pos_y=pos_y,
size_h=size[:, 0],
size_w=size[:, 1],
squared=squared,
fill_mode=fill_mode,
cval=cval,
fill_per_channel=fill_per_channel
)
# Added in 0.4.0.
@classmethod
def _augment_image_by_samples(cls, image, x1, y1, x2, y2, squared,
fill_mode, cval, fill_per_channel,
random_state):
for i, x1_i in enumerate(x1):
x2_i = x2[i]
if squared[i] >= 0.5:
height_h = (y2[i] - y1[i]) / 2
x_center = x1_i + (x2_i - x1_i) / 2
x1_i = x_center - height_h
x2_i = x_center + height_h
image = cutout_(
image,
x1=x1_i,
y1=y1[i],
x2=x2_i,
y2=y2[i],
fill_mode=fill_mode[i],
cval=cval[i],
fill_per_channel=fill_per_channel[i],
seed=random_state)
return image
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.nb_iterations, self.position, self.size, self.squared,
self.fill_mode, self.cval, self.fill_per_channel]
# TODO verify that (a, b) still leads to a p being sampled per image and not
# per batch
class Dropout(MultiplyElementwise):
"""
Set a fraction of pixels in images to zero.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.MultiplyElementwise`.
Parameters
----------
p : float or tuple of float or imgaug.parameters.StochasticParameter, optional
The probability of any pixel being dropped (i.e. to set it to zero).
* If a float, then that value will be used for all images. A value
of ``1.0`` would mean that all pixels will be dropped
and ``0.0`` that no pixels will be dropped. A value of ``0.05``
corresponds to ``5`` percent of all pixels being dropped.
* If a tuple ``(a, b)``, then a value ``p`` will be sampled from
the interval ``[a, b]`` per image and be used as the pixel's
dropout probability.
* If a list, then a value will be sampled from that list per
batch and used as the probability.
* If a ``StochasticParameter``, then this parameter will be used to
determine per pixel whether it should be *kept* (sampled value
of ``>0.5``) or shouldn't be kept (sampled value of ``<=0.5``).
If you instead want to provide the probability as a stochastic
parameter, you can usually do ``imgaug.parameters.Binomial(1-p)``
to convert parameter `p` to a 0/1 representation.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Dropout(0.02)
Drops ``2`` percent of all pixels.
>>> aug = iaa.Dropout((0.0, 0.05))
Drops in each image a random fraction of all pixels, where the fraction
is uniformly sampled from the interval ``[0.0, 0.05]``.
>>> aug = iaa.Dropout(0.02, per_channel=True)
Drops ``2`` percent of all pixels in a channelwise fashion, i.e. it is
unlikely for any pixel to have all channels set to zero (black pixels).
>>> aug = iaa.Dropout(0.02, per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for ``50`` percent of all images.
"""
def __init__(self, p=(0.0, 0.05), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
p_param = _handle_dropout_probability_param(p, "p")
super(Dropout, self).__init__(
p_param,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def _handle_dropout_probability_param(p, name):
if ia.is_single_number(p):
p_param = iap.Binomial(1 - p)
elif isinstance(p, tuple):
assert len(p) == 2, (
"Expected `%s` to be given as a tuple containing exactly 2 values, "
"got %d values." % (name, len(p),))
assert p[0] < p[1], (
"Expected `%s` to be given as a tuple containing exactly 2 values "
"(a, b) with a < b. Got %.4f and %.4f." % (name, p[0], p[1]))
assert 0 <= p[0] <= 1.0 and 0 <= p[1] <= 1.0, (
"Expected `%s` given as tuple to only contain values in the "
"interval [0.0, 1.0], got %.4f and %.4f." % (name, p[0], p[1]))
p_param = iap.Binomial(iap.Uniform(1 - p[1], 1 - p[0]))
elif ia.is_iterable(p):
assert all([ia.is_single_number(v) for v in p]), (
"Expected iterable parameter '%s' to only contain numbers, "
"got %s." % (name, [type(v) for v in p],))
assert all([0 <= p_i <= 1.0 for p_i in p]), (
"Expected iterable parameter '%s' to only contain probabilities "
"in the interval [0.0, 1.0], got values %s." % (
name, ", ".join(["%.4f" % (p_i,) for p_i in p])))
p_param = iap.Binomial(1 - iap.Choice(p))
elif isinstance(p, iap.StochasticParameter):
p_param = p
else:
raise Exception(
"Expected `%s` to be float or int or tuple (, ) "
"or StochasticParameter, got type '%s'." % (
name, type(p).__name__,))
return p_param
# TODO invert size_px and size_percent so that larger values denote larger
# areas being dropped instead of the opposite way around
class CoarseDropout(MultiplyElementwise):
"""
Set rectangular areas within images to zero.
In contrast to ``Dropout``, these areas can have larger sizes.
(E.g. you might end up with three large black rectangles in an image.)
Note that the current implementation leads to correlated sizes,
so if e.g. there is any thin and high rectangle that is dropped, there is
a high likelihood that all other dropped areas are also thin and high.
This method is implemented by generating the dropout mask at a
lower resolution (than the image has) and then upsampling the mask
before dropping the pixels.
This augmenter is similar to Cutout. Usually, cutout is defined as an
operation that drops exactly one rectangle from an image, while here
``CoarseDropout`` can drop multiple rectangles (with some correlation
between the sizes of these rectangles).
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.MultiplyElementwise`.
Parameters
----------
p : float or tuple of float or imgaug.parameters.StochasticParameter, optional
The probability of any pixel being dropped (i.e. set to zero) in
the lower-resolution dropout mask.
* If a float, then that value will be used for all pixels. A value
of ``1.0`` would mean, that all pixels will be dropped. A value
of ``0.0`` would lead to no pixels being dropped.
* If a tuple ``(a, b)``, then a value ``p`` will be sampled from
the interval ``[a, b]`` per image and be used as the dropout
probability.
* If a list, then a value will be sampled from that list per
batch and used as the probability.
* If a ``StochasticParameter``, then this parameter will be used to
determine per pixel whether it should be *kept* (sampled value
of ``>0.5``) or shouldn't be kept (sampled value of ``<=0.5``).
If you instead want to provide the probability as a stochastic
parameter, you can usually do ``imgaug.parameters.Binomial(1-p)``
to convert parameter `p` to a 0/1 representation.
size_px : None or int or tuple of int or imgaug.parameters.StochasticParameter, optional
The size of the lower resolution image from which to sample the dropout
mask in absolute pixel dimensions.
Note that this means that *lower* values of this parameter lead to
*larger* areas being dropped (as any pixel in the lower resolution
image will correspond to a larger area at the original resolution).
* If ``None`` then `size_percent` must be set.
* If an integer, then that size will always be used for both height
and width. E.g. a value of ``3`` would lead to a ``3x3`` mask,
which is then upsampled to ``HxW``, where ``H`` is the image size
and ``W`` the image width.
* If a tuple ``(a, b)``, then two values ``M``, ``N`` will be
sampled from the discrete interval ``[a..b]``. The dropout mask
will then be generated at size ``MxN`` and upsampled to ``HxW``.
* If a ``StochasticParameter``, then this parameter will be used to
determine the sizes. It is expected to be discrete.
size_percent : None or float or tuple of float or imgaug.parameters.StochasticParameter, optional
The size of the lower resolution image from which to sample the dropout
mask *in percent* of the input image.
Note that this means that *lower* values of this parameter lead to
*larger* areas being dropped (as any pixel in the lower resolution
image will correspond to a larger area at the original resolution).
* If ``None`` then `size_px` must be set.
* If a float, then that value will always be used as the percentage
of the height and width (relative to the original size). E.g. for
value ``p``, the mask will be sampled from ``(p*H)x(p*W)`` and
later upsampled to ``HxW``.
* If a tuple ``(a, b)``, then two values ``m``, ``n`` will be
sampled from the interval ``(a, b)`` and used as the size
fractions, i.e the mask size will be ``(m*H)x(n*W)``.
* If a ``StochasticParameter``, then this parameter will be used to
sample the percentage values. It is expected to be continuous.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
min_size : int, optional
Minimum height and width of the low resolution mask. If
`size_percent` or `size_px` leads to a lower value than this,
`min_size` will be used instead. This should never have a value of
less than ``2``, otherwise one may end up with a ``1x1`` low resolution
mask, leading easily to the whole image being dropped.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CoarseDropout(0.02, size_percent=0.5)
Drops ``2`` percent of all pixels on a lower-resolution image that has
``50`` percent of the original image's size, leading to dropped areas that
have roughly ``2x2`` pixels size.
>>> aug = iaa.CoarseDropout((0.0, 0.05), size_percent=(0.05, 0.5))
Generates a dropout mask at ``5`` to ``50`` percent of each input image's
size. In that mask, ``0`` to ``5`` percent of all pixels are marked as
being dropped. The mask is afterwards projected to the input image's
size to apply the actual dropout operation.
>>> aug = iaa.CoarseDropout((0.0, 0.05), size_px=(2, 16))
Same as the previous example, but the lower resolution image has ``2`` to
``16`` pixels size. On images of e.g. ``224x224` pixels in size this would
lead to fairly large areas being dropped (height/width of ``224/2`` to
``224/16``).
>>> aug = iaa.CoarseDropout(0.02, size_percent=0.5, per_channel=True)
Drops ``2`` percent of all pixels at ``50`` percent resolution (``2x2``
sizes) in a channel-wise fashion, i.e. it is unlikely for any pixel to
have all channels set to zero (black pixels).
>>> aug = iaa.CoarseDropout(0.02, size_percent=0.5, per_channel=0.5)
Same as the previous example, but the `per_channel` feature is only active
for ``50`` percent of all images.
"""
def __init__(self, p=(0.02, 0.1), size_px=None, size_percent=None,
per_channel=False, min_size=3,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
p_param = _handle_dropout_probability_param(p, "p")
if size_px is not None:
p_param = iap.FromLowerResolution(other_param=p_param,
size_px=size_px,
min_size=min_size)
elif size_percent is not None:
p_param = iap.FromLowerResolution(other_param=p_param,
size_percent=size_percent,
min_size=min_size)
else:
# default if neither size_px nor size_percent is provided
# is size_px=(3, 8)
p_param = iap.FromLowerResolution(other_param=p_param,
size_px=(3, 8),
min_size=min_size)
super(CoarseDropout, self).__init__(
p_param,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Dropout2d(meta.Augmenter):
"""Drop random channels from images.
For image data, dropped channels will be filled with zeros.
.. note::
This augmenter may also set the arrays of heatmaps and segmentation
maps to zero and remove all coordinate-based data (e.g. it removes
all bounding boxes on images that were filled with zeros).
It does so if and only if *all* channels of an image are dropped.
If ``nb_keep_channels >= 1`` then that never happens.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
p : float or tuple of float or imgaug.parameters.StochasticParameter, optional
The probability of any channel to be dropped (i.e. set to zero).
* If a ``float``, then that value will be used for all channels.
A value of ``1.0`` would mean, that all channels will be dropped.
A value of ``0.0`` would lead to no channels being dropped.
* If a tuple ``(a, b)``, then a value ``p`` will be sampled from
the interval ``[a, b)`` per batch and be used as the dropout
probability.
* If a list, then a value will be sampled from that list per
batch and used as the probability.
* If a ``StochasticParameter``, then this parameter will be used to
determine per channel whether it should be *kept* (sampled value
of ``>=0.5``) or shouldn't be kept (sampled value of ``<0.5``).
If you instead want to provide the probability as a stochastic
parameter, you can usually do ``imgaug.parameters.Binomial(1-p)``
to convert parameter `p` to a 0/1 representation.
nb_keep_channels : int
Minimum number of channels to keep unaltered in all images.
E.g. a value of ``1`` means that at least one channel in every image
will not be dropped, even if ``p=1.0``. Set to ``0`` to allow dropping
all channels.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Dropout2d(p=0.5)
Create a dropout augmenter that drops on average half of all image
channels. Dropped channels will be filled with zeros. At least one
channel is kept unaltered in each image (default setting).
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Dropout2d(p=0.5, nb_keep_channels=0)
Create a dropout augmenter that drops on average half of all image
channels *and* may drop *all* channels in an image (i.e. images may
contain nothing but zeros).
"""
# Added in 0.4.0.
def __init__(self, p=0.1, nb_keep_channels=1,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Dropout2d, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.p = _handle_dropout_probability_param(p, "p")
self.nb_keep_channels = max(nb_keep_channels, 0)
self._drop_images = True
self._drop_heatmaps = True
self._drop_segmentation_maps = True
self._drop_keypoints = True
self._drop_bounding_boxes = True
self._drop_polygons = True
self._drop_line_strings = True
self._heatmaps_cval = 0.0
self._segmentation_maps_cval = 0
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
imagewise_drop_channel_ids, all_dropped_ids = self._draw_samples(
batch, random_state)
if batch.images is not None:
for image, drop_ids in zip(batch.images,
imagewise_drop_channel_ids):
image[:, :, drop_ids] = 0
# Skip the non-image data steps below if we won't modify non-image
# anyways. Minor performance improvement.
if len(all_dropped_ids) == 0:
return batch
if batch.heatmaps is not None and self._drop_heatmaps:
cval = self._heatmaps_cval
for drop_idx in all_dropped_ids:
batch.heatmaps[drop_idx].arr_0to1[...] = cval
if batch.segmentation_maps is not None and self._drop_segmentation_maps:
cval = self._segmentation_maps_cval
for drop_idx in all_dropped_ids:
batch.segmentation_maps[drop_idx].arr[...] = cval
for attr_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
do_drop = getattr(self, "_drop_%s" % (attr_name,))
attr_value = getattr(batch, attr_name)
if attr_value is not None and do_drop:
for drop_idx in all_dropped_ids:
# same as e.g.:
# batch.bounding_boxes[drop_idx].bounding_boxes = []
setattr(attr_value[drop_idx], attr_name, [])
return batch
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
# maybe noteworthy here that the channel axis can have size 0,
# e.g. (5, 5, 0)
shapes = batch.get_rowwise_shapes()
shapes = [shape
if len(shape) >= 2
else tuple(list(shape) + [1])
for shape in shapes]
imagewise_channels = np.array([
shape[2] for shape in shapes
], dtype=np.int32)
# channelwise drop value over all images (float <0.5 = drop channel)
p_samples = self.p.draw_samples((int(np.sum(imagewise_channels)),),
random_state=random_state)
# We map the flat p_samples array to an imagewise one,
# convert the mask to channel-ids to drop and remove channel ids if
# there are more to be dropped than are allowed to be dropped (see
# nb_keep_channels).
# We also track all_dropped_ids, which contains the ids of examples
# (not channel ids!) where all channels were dropped.
imagewise_channels_to_drop = []
all_dropped_ids = []
channel_idx = 0
for i, nb_channels in enumerate(imagewise_channels):
p_samples_i = p_samples[channel_idx:channel_idx+nb_channels]
drop_ids = np.nonzero(p_samples_i < 0.5)[0]
nb_dropable = max(nb_channels - self.nb_keep_channels, 0)
if len(drop_ids) > nb_dropable:
random_state.shuffle(drop_ids)
drop_ids = drop_ids[:nb_dropable]
imagewise_channels_to_drop.append(drop_ids)
all_dropped = (len(drop_ids) == nb_channels)
if all_dropped:
all_dropped_ids.append(i)
channel_idx += nb_channels
return imagewise_channels_to_drop, all_dropped_ids
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.p, self.nb_keep_channels]
class TotalDropout(meta.Augmenter):
"""Drop all channels of a defined fraction of all images.
For image data, all components of dropped images will be filled with zeros.
.. note::
This augmenter also sets the arrays of heatmaps and segmentation
maps to zero and removes all coordinate-based data (e.g. it removes
all bounding boxes on images that were filled with zeros).
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
p : float or tuple of float or imgaug.parameters.StochasticParameter, optional
The probability of an image to be filled with zeros.
* If ``float``: The value will be used for all images.
A value of ``1.0`` would mean that all images will be set to zero.
A value of ``0.0`` would lead to no images being set to zero.
* If ``tuple`` ``(a, b)``: A value ``p`` will be sampled from
the interval ``[a, b)`` per batch and be used as the dropout
probability.
* If a list, then a value will be sampled from that list per
batch and used as the probability.
* If ``StochasticParameter``: The parameter will be used to
determine per image whether it should be *kept* (sampled value
of ``>=0.5``) or shouldn't be kept (sampled value of ``<0.5``).
If you instead want to provide the probability as a stochastic
parameter, you can usually do ``imgaug.parameters.Binomial(1-p)``
to convert parameter `p` to a 0/1 representation.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.TotalDropout(1.0)
Create an augmenter that sets *all* components of all images to zero.
>>> aug = iaa.TotalDropout(0.5)
Create an augmenter that sets *all* components of ``50%`` of all images to
zero.
"""
# Added in 0.4.0.
def __init__(self, p=1,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(TotalDropout, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.p = _handle_dropout_probability_param(p, "p")
self._drop_images = True
self._drop_heatmaps = True
self._drop_segmentation_maps = True
self._drop_keypoints = True
self._drop_bounding_boxes = True
self._drop_polygons = True
self._drop_line_strings = True
self._heatmaps_cval = 0.0
self._segmentation_maps_cval = 0
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
drop_mask = self._draw_samples(batch, random_state)
drop_ids = None
if batch.images is not None and self._drop_images:
if ia.is_np_array(batch.images):
batch.images[drop_mask, ...] = 0
else:
drop_ids = self._generate_drop_ids_once(drop_mask, drop_ids)
for drop_idx in drop_ids:
batch.images[drop_idx][...] = 0
if batch.heatmaps is not None and self._drop_heatmaps:
drop_ids = self._generate_drop_ids_once(drop_mask, drop_ids)
cval = self._heatmaps_cval
for drop_idx in drop_ids:
batch.heatmaps[drop_idx].arr_0to1[...] = cval
if batch.segmentation_maps is not None and self._drop_segmentation_maps:
drop_ids = self._generate_drop_ids_once(drop_mask, drop_ids)
cval = self._segmentation_maps_cval
for drop_idx in drop_ids:
batch.segmentation_maps[drop_idx].arr[...] = cval
for attr_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
do_drop = getattr(self, "_drop_%s" % (attr_name,))
attr_value = getattr(batch, attr_name)
if attr_value is not None and do_drop:
drop_ids = self._generate_drop_ids_once(drop_mask, drop_ids)
for drop_idx in drop_ids:
# same as e.g.:
# batch.bounding_boxes[drop_idx].bounding_boxes = []
setattr(attr_value[drop_idx], attr_name, [])
return batch
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
p = self.p.draw_samples((batch.nb_rows,), random_state=random_state)
drop_mask = (p < 0.5)
return drop_mask
# Added in 0.4.0.
@classmethod
def _generate_drop_ids_once(cls, drop_mask, drop_ids):
if drop_ids is None:
drop_ids = np.nonzero(drop_mask)[0]
return drop_ids
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.p]
class ReplaceElementwise(meta.Augmenter):
"""
Replace pixels in an image with new values.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.replace_elementwise_`.
Parameters
----------
mask : float or tuple of float or list of float or imgaug.parameters.StochasticParameter
Mask that indicates the pixels that are supposed to be replaced.
The mask will be binarized using a threshold of ``0.5``. A value
of ``1`` then indicates a pixel that is supposed to be replaced.
* If this is a float, then that value will be used as the
probability of being a ``1`` in the mask (sampled per image and
pixel) and hence being replaced.
* If a tuple ``(a, b)``, then the probability will be uniformly
sampled per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image and pixel.
* If a ``StochasticParameter``, then this parameter will be used to
sample a mask per image.
replacement : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The replacement to use at all locations that are marked as ``1`` in
the mask.
* If this is a number, then that value will always be used as the
replacement.
* If a tuple ``(a, b)``, then the replacement will be sampled
uniformly per image and pixel from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image and pixel.
* If a ``StochasticParameter``, then this parameter will be used
sample replacement values per image and pixel.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = ReplaceElementwise(0.05, [0, 255])
Replaces ``5`` percent of all pixels in each image by either ``0``
or ``255``.
>>> import imgaug.augmenters as iaa
>>> aug = ReplaceElementwise(0.1, [0, 255], per_channel=0.5)
For ``50%`` of all images, replace ``10%`` of all pixels with either the
value ``0`` or the value ``255`` (same as in the previous example). For
the other ``50%`` of all images, replace *channelwise* ``10%`` of all
pixels with either the value ``0`` or the value ``255``. So, it will be
very rare for each pixel to have all channels replaced by ``255`` or
``0``.
>>> import imgaug.augmenters as iaa
>>> import imgaug.parameters as iap
>>> aug = ReplaceElementwise(0.1, iap.Normal(128, 0.4*128), per_channel=0.5)
Replace ``10%`` of all pixels by gaussian noise centered around ``128``.
Both the replacement mask and the gaussian noise are sampled channelwise
for ``50%`` of all images.
>>> import imgaug.augmenters as iaa
>>> import imgaug.parameters as iap
>>> aug = ReplaceElementwise(
>>> iap.FromLowerResolution(iap.Binomial(0.1), size_px=8),
>>> iap.Normal(128, 0.4*128),
>>> per_channel=0.5)
Replace ``10%`` of all pixels by gaussian noise centered around ``128``.
Sample the replacement mask at a lower resolution (``8x8`` pixels) and
upscale it to the image size, resulting in coarse areas being replaced by
gaussian noise.
"""
def __init__(self, mask, replacement, per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ReplaceElementwise, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.mask = iap.handle_probability_param(
mask, "mask", tuple_to_uniform=True, list_to_choice=True)
self.replacement = iap.handle_continuous_param(replacement,
"replacement")
self.per_channel = iap.handle_probability_param(per_channel,
"per_channel")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
rss = random_state.duplicate(1+2*nb_images)
per_channel_samples = self.per_channel.draw_samples(
(nb_images,), random_state=rss[0])
gen = enumerate(zip(images, per_channel_samples, rss[1::2], rss[2::2]))
for i, (image, per_channel_i, rs_mask, rs_replacement) in gen:
height, width, nb_channels = image.shape
sampling_shape = (height,
width,
nb_channels if per_channel_i > 0.5 else 1)
mask_samples = self.mask.draw_samples(sampling_shape,
random_state=rs_mask)
# TODO add separate per_channels for mask and replacement
# TODO add test that replacement with per_channel=False is not
# sampled per channel
if per_channel_i <= 0.5:
nb_channels = image.shape[-1]
replacement_samples = self.replacement.draw_samples(
(int(np.sum(mask_samples[:, :, 0])),),
random_state=rs_replacement)
# important here to use repeat instead of tile. repeat
# converts e.g. [0, 1, 2] to [0, 0, 1, 1, 2, 2], while tile
# leads to [0, 1, 2, 0, 1, 2]. The assignment below iterates
# over each channel and pixel simultaneously, *not* first
# over all pixels of channel 0, then all pixels in
# channel 1, ...
replacement_samples = np.repeat(replacement_samples,
nb_channels)
else:
replacement_samples = self.replacement.draw_samples(
(int(np.sum(mask_samples)),), random_state=rs_replacement)
batch.images[i] = replace_elementwise_(image, mask_samples,
replacement_samples)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.mask, self.replacement, self.per_channel]
class SaltAndPepper(ReplaceElementwise):
"""
Replace pixels in images with salt/pepper noise (white/black-ish colors).
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.ReplaceElementwise`.
Parameters
----------
p : float or tuple of float or list of float or imgaug.parameters.StochasticParameter, optional
Probability of replacing a pixel to salt/pepper noise.
* If a float, then that value will always be used as the
probability.
* If a tuple ``(a, b)``, then a probability will be sampled
uniformly per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a image-sized mask will be
sampled from that parameter per image. Any value ``>0.5`` in
that mask will be replaced with salt and pepper noise.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.SaltAndPepper(0.05)
Replace ``5%`` of all pixels with salt and pepper noise.
>>> import imgaug.augmenters as iaa
>>> aug = iaa.SaltAndPepper(0.05, per_channel=True)
Replace *channelwise* ``5%`` of all pixels with salt and pepper
noise.
"""
def __init__(self, p=(0.0, 0.03), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(SaltAndPepper, self).__init__(
mask=p,
replacement=iap.Beta(0.5, 0.5) * 255,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ImpulseNoise(SaltAndPepper):
"""
Add impulse noise to images.
This is identical to ``SaltAndPepper``, except that `per_channel` is
always set to ``True``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.SaltAndPepper`.
Parameters
----------
p : float or tuple of float or list of float or imgaug.parameters.StochasticParameter, optional
Probability of replacing a pixel to impulse noise.
* If a float, then that value will always be used as the
probability.
* If a tuple ``(a, b)``, then a probability will be sampled
uniformly per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a image-sized mask will be
sampled from that parameter per image. Any value ``>0.5`` in
that mask will be replaced with impulse noise noise.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.ImpulseNoise(0.1)
Replace ``10%`` of all pixels with impulse noise.
"""
def __init__(self, p=(0.0, 0.03),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ImpulseNoise, self).__init__(
p=p,
per_channel=True,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CoarseSaltAndPepper(ReplaceElementwise):
"""
Replace rectangular areas in images with white/black-ish pixel noise.
This adds salt and pepper noise (noisy white-ish and black-ish pixels) to
rectangular areas within the image. Note that this means that within these
rectangular areas the color varies instead of each rectangle having only
one color.
See also the similar ``CoarseDropout``.
TODO replace dtype support with uint8 only, because replacement is
geared towards that value range
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.ReplaceElementwise`.
Parameters
----------
p : float or tuple of float or list of float or imgaug.parameters.StochasticParameter, optional
Probability of changing a pixel to salt/pepper noise.
* If a float, then that value will always be used as the
probability.
* If a tuple ``(a, b)``, then a probability will be sampled
uniformly per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a lower-resolution mask will
be sampled from that parameter per image. Any value ``>0.5`` in
that mask will denote a spatial location that is to be replaced
by salt and pepper noise.
size_px : int or tuple of int or imgaug.parameters.StochasticParameter, optional
The size of the lower resolution image from which to sample the
replacement mask in absolute pixel dimensions.
Note that this means that *lower* values of this parameter lead to
*larger* areas being replaced (as any pixel in the lower resolution
image will correspond to a larger area at the original resolution).
* If ``None`` then `size_percent` must be set.
* If an integer, then that size will always be used for both height
and width. E.g. a value of ``3`` would lead to a ``3x3`` mask,
which is then upsampled to ``HxW``, where ``H`` is the image size
and ``W`` the image width.
* If a tuple ``(a, b)``, then two values ``M``, ``N`` will be
sampled from the discrete interval ``[a..b]``. The mask
will then be generated at size ``MxN`` and upsampled to ``HxW``.
* If a ``StochasticParameter``, then this parameter will be used to
determine the sizes. It is expected to be discrete.
size_percent : float or tuple of float or imgaug.parameters.StochasticParameter, optional
The size of the lower resolution image from which to sample the
replacement mask *in percent* of the input image.
Note that this means that *lower* values of this parameter lead to
*larger* areas being replaced (as any pixel in the lower resolution
image will correspond to a larger area at the original resolution).
* If ``None`` then `size_px` must be set.
* If a float, then that value will always be used as the percentage
of the height and width (relative to the original size). E.g. for
value ``p``, the mask will be sampled from ``(p*H)x(p*W)`` and
later upsampled to ``HxW``.
* If a tuple ``(a, b)``, then two values ``m``, ``n`` will be
sampled from the interval ``(a, b)`` and used as the size
fractions, i.e the mask size will be ``(m*H)x(n*W)``.
* If a ``StochasticParameter``, then this parameter will be used to
sample the percentage values. It is expected to be continuous.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
min_size : int, optional
Minimum height and width of the low resolution mask. If
`size_percent` or `size_px` leads to a lower value than this,
`min_size` will be used instead. This should never have a value of
less than ``2``, otherwise one may end up with a ``1x1`` low resolution
mask, leading easily to the whole image being replaced.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CoarseSaltAndPepper(0.05, size_percent=(0.01, 0.1))
Marks ``5%`` of all pixels in a mask to be replaced by salt/pepper
noise. The mask has ``1%`` to ``10%`` the size of the input image.
The mask is then upscaled to the input image size, leading to large
rectangular areas being marked as to be replaced. These areas are then
replaced in the input image by salt/pepper noise.
>>> aug = iaa.CoarseSaltAndPepper(0.05, size_px=(4, 16))
Same as in the previous example, but the replacement mask before upscaling
has a size between ``4x4`` and ``16x16`` pixels (the axis sizes are sampled
independently, i.e. the mask may be rectangular).
>>> aug = iaa.CoarseSaltAndPepper(
>>> 0.05, size_percent=(0.01, 0.1), per_channel=True)
Same as in the first example, but mask and replacement are each sampled
independently per image channel.
"""
def __init__(self, p=(0.02, 0.1), size_px=None, size_percent=None,
per_channel=False, min_size=3,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
mask = iap.handle_probability_param(
p, "p", tuple_to_uniform=True, list_to_choice=True)
if size_px is not None:
mask_low = iap.FromLowerResolution(
other_param=mask, size_px=size_px, min_size=min_size)
elif size_percent is not None:
mask_low = iap.FromLowerResolution(
other_param=mask, size_percent=size_percent, min_size=min_size)
else:
mask_low = iap.FromLowerResolution(
other_param=mask, size_px=(3, 8), min_size=min_size)
replacement = iap.Beta(0.5, 0.5) * 255
super(CoarseSaltAndPepper, self).__init__(
mask=mask_low,
replacement=replacement,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Salt(ReplaceElementwise):
"""
Replace pixels in images with salt noise, i.e. white-ish pixels.
This augmenter is similar to ``SaltAndPepper``, but adds no pepper noise to
images.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.ReplaceElementwise`.
Parameters
----------
p : float or tuple of float or list of float or imgaug.parameters.StochasticParameter, optional
Probability of replacing a pixel with salt noise.
* If a float, then that value will always be used as the
probability.
* If a tuple ``(a, b)``, then a probability will be sampled
uniformly per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a image-sized mask will be
sampled from that parameter per image. Any value ``>0.5`` in
that mask will be replaced with salt noise.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Salt(0.05)
Replace ``5%`` of all pixels with salt noise (white-ish colors).
"""
def __init__(self, p=(0.0, 0.03), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
replacement01 = iap.ForceSign(
iap.Beta(0.5, 0.5) - 0.5,
positive=True,
mode="invert"
) + 0.5
# FIXME max replacement seems to essentially never exceed 254
replacement = replacement01 * 255
super(Salt, self).__init__(
mask=p,
replacement=replacement,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CoarseSalt(ReplaceElementwise):
"""
Replace rectangular areas in images with white-ish pixel noise.
See also the similar ``CoarseSaltAndPepper``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.ReplaceElementwise`.
Parameters
----------
p : float or tuple of float or list of float or imgaug.parameters.StochasticParameter, optional
Probability of changing a pixel to salt noise.
* If a float, then that value will always be used as the
probability.
* If a tuple ``(a, b)``, then a probability will be sampled
uniformly per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a lower-resolution mask will
be sampled from that parameter per image. Any value ``>0.5`` in
that mask will denote a spatial location that is to be replaced
by salt noise.
size_px : int or tuple of int or imgaug.parameters.StochasticParameter, optional
The size of the lower resolution image from which to sample the
replacement mask in absolute pixel dimensions.
Note that this means that *lower* values of this parameter lead to
*larger* areas being replaced (as any pixel in the lower resolution
image will correspond to a larger area at the original resolution).
* If ``None`` then `size_percent` must be set.
* If an integer, then that size will always be used for both height
and width. E.g. a value of ``3`` would lead to a ``3x3`` mask,
which is then upsampled to ``HxW``, where ``H`` is the image size
and ``W`` the image width.
* If a tuple ``(a, b)``, then two values ``M``, ``N`` will be
sampled from the discrete interval ``[a..b]``. The mask
will then be generated at size ``MxN`` and upsampled to ``HxW``.
* If a ``StochasticParameter``, then this parameter will be used to
determine the sizes. It is expected to be discrete.
size_percent : float or tuple of float or imgaug.parameters.StochasticParameter, optional
The size of the lower resolution image from which to sample the
replacement mask *in percent* of the input image.
Note that this means that *lower* values of this parameter lead to
*larger* areas being replaced (as any pixel in the lower resolution
image will correspond to a larger area at the original resolution).
* If ``None`` then `size_px` must be set.
* If a float, then that value will always be used as the percentage
of the height and width (relative to the original size). E.g. for
value ``p``, the mask will be sampled from ``(p*H)x(p*W)`` and
later upsampled to ``HxW``.
* If a tuple ``(a, b)``, then two values ``m``, ``n`` will be
sampled from the interval ``(a, b)`` and used as the size
fractions, i.e the mask size will be ``(m*H)x(n*W)``.
* If a ``StochasticParameter``, then this parameter will be used to
sample the percentage values. It is expected to be continuous.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
min_size : int, optional
Minimum height and width of the low resolution mask. If
`size_percent` or `size_px` leads to a lower value than this,
`min_size` will be used instead. This should never have a value of
less than ``2``, otherwise one may end up with a ``1x1`` low resolution
mask, leading easily to the whole image being replaced.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CoarseSalt(0.05, size_percent=(0.01, 0.1))
Mark ``5%`` of all pixels in a mask to be replaced by salt
noise. The mask has ``1%`` to ``10%`` the size of the input image.
The mask is then upscaled to the input image size, leading to large
rectangular areas being marked as to be replaced. These areas are then
replaced in the input image by salt noise.
"""
def __init__(self, p=(0.02, 0.1), size_px=None, size_percent=None,
per_channel=False, min_size=3,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
mask = iap.handle_probability_param(
p, "p", tuple_to_uniform=True, list_to_choice=True)
if size_px is not None:
mask_low = iap.FromLowerResolution(
other_param=mask, size_px=size_px, min_size=min_size)
elif size_percent is not None:
mask_low = iap.FromLowerResolution(
other_param=mask, size_percent=size_percent, min_size=min_size)
else:
mask_low = iap.FromLowerResolution(
other_param=mask, size_px=(3, 8), min_size=min_size)
replacement01 = iap.ForceSign(
iap.Beta(0.5, 0.5) - 0.5,
positive=True,
mode="invert"
) + 0.5
replacement = replacement01 * 255
super(CoarseSalt, self).__init__(
mask=mask_low,
replacement=replacement,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Pepper(ReplaceElementwise):
"""
Replace pixels in images with pepper noise, i.e. black-ish pixels.
This augmenter is similar to ``SaltAndPepper``, but adds no salt noise to
images.
This augmenter is similar to ``Dropout``, but slower and the black pixels
are not uniformly black.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.ReplaceElementwise`.
Parameters
----------
p : float or tuple of float or list of float or imgaug.parameters.StochasticParameter, optional
Probability of replacing a pixel with pepper noise.
* If a float, then that value will always be used as the
probability.
* If a tuple ``(a, b)``, then a probability will be sampled
uniformly per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a image-sized mask will be
sampled from that parameter per image. Any value ``>0.5`` in
that mask will be replaced with pepper noise.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Pepper(0.05)
Replace ``5%`` of all pixels with pepper noise (black-ish colors).
"""
def __init__(self, p=(0.0, 0.05), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
replacement01 = iap.ForceSign(
iap.Beta(0.5, 0.5) - 0.5,
positive=False,
mode="invert"
) + 0.5
replacement = replacement01 * 255
super(Pepper, self).__init__(
mask=p,
replacement=replacement,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CoarsePepper(ReplaceElementwise):
"""
Replace rectangular areas in images with black-ish pixel noise.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.ReplaceElementwise`.
Parameters
----------
p : float or tuple of float or list of float or imgaug.parameters.StochasticParameter, optional
Probability of changing a pixel to pepper noise.
* If a float, then that value will always be used as the
probability.
* If a tuple ``(a, b)``, then a probability will be sampled
uniformly per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a lower-resolution mask will
be sampled from that parameter per image. Any value ``>0.5`` in
that mask will denote a spatial location that is to be replaced
by pepper noise.
size_px : int or tuple of int or imgaug.parameters.StochasticParameter, optional
The size of the lower resolution image from which to sample the
replacement mask in absolute pixel dimensions.
Note that this means that *lower* values of this parameter lead to
*larger* areas being replaced (as any pixel in the lower resolution
image will correspond to a larger area at the original resolution).
* If ``None`` then `size_percent` must be set.
* If an integer, then that size will always be used for both height
and width. E.g. a value of ``3`` would lead to a ``3x3`` mask,
which is then upsampled to ``HxW``, where ``H`` is the image size
and ``W`` the image width.
* If a tuple ``(a, b)``, then two values ``M``, ``N`` will be
sampled from the discrete interval ``[a..b]``. The mask
will then be generated at size ``MxN`` and upsampled to ``HxW``.
* If a ``StochasticParameter``, then this parameter will be used to
determine the sizes. It is expected to be discrete.
size_percent : float or tuple of float or imgaug.parameters.StochasticParameter, optional
The size of the lower resolution image from which to sample the
replacement mask *in percent* of the input image.
Note that this means that *lower* values of this parameter lead to
*larger* areas being replaced (as any pixel in the lower resolution
image will correspond to a larger area at the original resolution).
* If ``None`` then `size_px` must be set.
* If a float, then that value will always be used as the percentage
of the height and width (relative to the original size). E.g. for
value ``p``, the mask will be sampled from ``(p*H)x(p*W)`` and
later upsampled to ``HxW``.
* If a tuple ``(a, b)``, then two values ``m``, ``n`` will be
sampled from the interval ``(a, b)`` and used as the size
fractions, i.e the mask size will be ``(m*H)x(n*W)``.
* If a ``StochasticParameter``, then this parameter will be used to
sample the percentage values. It is expected to be continuous.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
min_size : int, optional
Minimum size of the low resolution mask, both width and height. If
`size_percent` or `size_px` leads to a lower value than this, `min_size`
will be used instead. This should never have a value of less than 2,
otherwise one may end up with a ``1x1`` low resolution mask, leading
easily to the whole image being replaced.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CoarsePepper(0.05, size_percent=(0.01, 0.1))
Mark ``5%`` of all pixels in a mask to be replaced by pepper
noise. The mask has ``1%`` to ``10%`` the size of the input image.
The mask is then upscaled to the input image size, leading to large
rectangular areas being marked as to be replaced. These areas are then
replaced in the input image by pepper noise.
"""
def __init__(self, p=(0.02, 0.1), size_px=None, size_percent=None,
per_channel=False, min_size=3,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
mask = iap.handle_probability_param(
p, "p", tuple_to_uniform=True, list_to_choice=True)
if size_px is not None:
mask_low = iap.FromLowerResolution(
other_param=mask, size_px=size_px, min_size=min_size)
elif size_percent is not None:
mask_low = iap.FromLowerResolution(
other_param=mask, size_percent=size_percent, min_size=min_size)
else:
mask_low = iap.FromLowerResolution(
other_param=mask, size_px=(3, 8), min_size=min_size)
replacement01 = iap.ForceSign(
iap.Beta(0.5, 0.5) - 0.5,
positive=False,
mode="invert"
) + 0.5
replacement = replacement01 * 255
super(CoarsePepper, self).__init__(
mask=mask_low,
replacement=replacement,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Invert(meta.Augmenter):
"""
Invert all values in images, e.g. turn ``5`` into ``255-5=250``.
For the standard value range of 0-255 it converts ``0`` to ``255``,
``255`` to ``0`` and ``10`` to ``(255-10)=245``.
Let ``M`` be the maximum value possible, ``m`` the minimum value possible,
``v`` a value. Then the distance of ``v`` to ``m`` is ``d=abs(v-m)`` and
the new value is given by ``v'=M-d``.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.invert_`.
Parameters
----------
p : float or imgaug.parameters.StochasticParameter, optional
The probability of an image to be inverted.
* If a float, then that probability will be used for all images,
i.e. `p` percent of all images will be inverted.
* If a ``StochasticParameter``, then that parameter will be queried
per image and is expected to return values in the interval
``[0.0, 1.0]``, where values ``>0.5`` mean that the image
is supposed to be inverted. Recommended to be some form of
``imgaug.parameters.Binomial``.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
min_value : None or number, optional
Minimum of the value range of input images, e.g. ``0`` for ``uint8``
images. If set to ``None``, the value will be automatically derived
from the image's dtype.
max_value : None or number, optional
Maximum of the value range of input images, e.g. ``255`` for ``uint8``
images. If set to ``None``, the value will be automatically derived
from the image's dtype.
threshold : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
A threshold to use in order to invert only numbers above or below
the threshold. If ``None`` no thresholding will be used.
* If ``None``: No thresholding will be used.
* If ``number``: The value will be used for all images.
* If ``tuple`` ``(a, b)``: A value will be uniformly sampled per
image from the interval ``[a, b)``.
* If ``list``: A random value will be picked from the list per
image.
* If ``StochasticParameter``: Per batch of size ``N``, the
parameter will be queried once to return ``(N,)`` samples.
invert_above_threshold : bool or float or imgaug.parameters.StochasticParameter, optional
If ``True``, only values ``>=threshold`` will be inverted.
Otherwise, only values ``0.5``.
If `threshold` is ``None`` this parameter has no effect.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Invert(0.1)
Inverts the colors in ``10`` percent of all images.
>>> aug = iaa.Invert(0.1, per_channel=True)
Inverts the colors in ``10`` percent of all image channels. This may or
may not lead to multiple channels in an image being inverted.
>>> aug = iaa.Invert(0.1, per_channel=0.5)
Identical to the previous example, but the `per_channel` feature is only
active for 50 percent of all images.
"""
# when no custom min/max are chosen, all bool, uint, int and float dtypes
# should be invertable (float tested only up to 64bit)
# when chosing custom min/max:
# - bool makes no sense, not allowed
# - int and float must be increased in resolution if custom min/max values
# are chosen, hence they are limited to 32 bit and below
# - uint64 is converted by numpy's clip to float64, hence loss of accuracy
# - float16 seems to not be perfectly accurate, but still ok-ish -- was
# off by 10 for center value of range (float 16 min, 16), where float
# 16 min is around -65500
ALLOW_DTYPES_CUSTOM_MINMAX = [
np.dtype(dt) for dt in [
np.uint8, np.uint16, np.uint32,
np.int8, np.int16, np.int32,
np.float16, np.float32
]
]
def __init__(self, p=1, per_channel=False, min_value=None, max_value=None,
threshold=None, invert_above_threshold=0.5,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Invert, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO allow list and tuple for p
self.p = iap.handle_probability_param(p, "p")
self.per_channel = iap.handle_probability_param(per_channel,
"per_channel")
self.min_value = min_value
self.max_value = max_value
if threshold is None:
self.threshold = None
else:
self.threshold = iap.handle_continuous_param(
threshold, "threshold", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
self.invert_above_threshold = iap.handle_probability_param(
invert_above_threshold, "invert_above_threshold")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
samples = self._draw_samples(batch, random_state)
for i, image in enumerate(batch.images):
if 0 in image.shape:
continue
kwargs = {
"min_value": samples.min_value[i],
"max_value": samples.max_value[i],
"threshold": samples.threshold[i],
"invert_above_threshold": samples.invert_above_threshold[i]
}
if samples.per_channel[i]:
nb_channels = image.shape[2]
mask = samples.p[i, :nb_channels]
image[..., mask] = invert_(image[..., mask], **kwargs)
else:
if samples.p[i, 0]:
image[:, :, :] = invert_(image, **kwargs)
return batch
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
nb_images = batch.nb_rows
nb_channels = meta.estimate_max_number_of_channels(batch.images)
p = self.p.draw_samples((nb_images, nb_channels),
random_state=random_state)
p = (p > 0.5)
per_channel = self.per_channel.draw_samples((nb_images,),
random_state=random_state)
per_channel = (per_channel > 0.5)
min_value = [self.min_value] * nb_images
max_value = [self.max_value] * nb_images
if self.threshold is None:
threshold = [None] * nb_images
else:
threshold = self.threshold.draw_samples(
(nb_images,), random_state=random_state)
invert_above_threshold = self.invert_above_threshold.draw_samples(
(nb_images,), random_state=random_state)
invert_above_threshold = (invert_above_threshold > 0.5)
return _InvertSamples(
p=p,
per_channel=per_channel,
min_value=min_value,
max_value=max_value,
threshold=threshold,
invert_above_threshold=invert_above_threshold
)
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.p, self.per_channel, self.min_value, self.max_value,
self.threshold, self.invert_above_threshold]
# Added in 0.4.0.
class _InvertSamples(object):
# Added in 0.4.0.
def __init__(self, p, per_channel, min_value, max_value,
threshold, invert_above_threshold):
self.p = p
self.per_channel = per_channel
self.min_value = min_value
self.max_value = max_value
self.threshold = threshold
self.invert_above_threshold = invert_above_threshold
class Solarize(Invert):
"""Invert all pixel values above a threshold.
This is the same as :class:`Invert`, but sets a default threshold around
``128`` (+/- 64, decided per image) and default `invert_above_threshold`
to ``True`` (i.e. only values above the threshold will be inverted).
See :class:`Invert` for more details.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.arithmetic.Invert`.
Parameters
----------
p : float or imgaug.parameters.StochasticParameter
See :class:`Invert`.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
See :class:`Invert`.
min_value : None or number, optional
See :class:`Invert`.
max_value : None or number, optional
See :class:`Invert`.
threshold : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`Invert`.
invert_above_threshold : bool or float or imgaug.parameters.StochasticParameter, optional
See :class:`Invert`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Solarize(0.5, threshold=(32, 128))
Invert the colors in ``50`` percent of all images for pixels with a
value between ``32`` and ``128`` or more. The threshold is sampled once
per image. The thresholding operation happens per channel.
"""
def __init__(self, p=1, per_channel=False, min_value=None, max_value=None,
threshold=(128-64, 128+64), invert_above_threshold=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Solarize, self).__init__(
p=p, per_channel=per_channel,
min_value=min_value, max_value=max_value,
threshold=threshold, invert_above_threshold=invert_above_threshold,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO remove from examples
@ia.deprecated("imgaug.contrast.LinearContrast")
def ContrastNormalization(alpha=1.0, per_channel=False,
seed=None, name=None,
random_state="deprecated",
deterministic="deprecated"):
"""
Change the contrast of images.
dtype support:
See ``imgaug.augmenters.contrast.LinearContrast``.
Deprecated since 0.3.0.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Strength of the contrast normalization. Higher values than 1.0
lead to higher contrast, lower values decrease the contrast.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be sampled per image
uniformly from the interval ``[a, b]`` and be used as the alpha
value.
* If a list, then a random value will be picked per image from
that list.
* If a ``StochasticParameter``, then this parameter will be used to
sample the alpha value per image.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use (imagewise) the same sample(s) for all
channels (``False``) or to sample value(s) for each channel (``True``).
Setting this to ``True`` will therefore lead to different
transformations per image *and* channel, otherwise only per image.
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``.
If it is a ``StochasticParameter`` it is expected to produce samples
with values between ``0.0`` and ``1.0``, where values ``>0.5`` will
lead to per-channel behaviour (i.e. same as ``True``).
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> iaa.ContrastNormalization((0.5, 1.5))
Decreases oder improves contrast per image by a random factor between
``0.5`` and ``1.5``. The factor ``0.5`` means that any difference from
the center value (i.e. 128) will be halved, leading to less contrast.
>>> iaa.ContrastNormalization((0.5, 1.5), per_channel=0.5)
Same as before, but for 50 percent of all images the normalization is done
independently per channel (i.e. factors can vary per channel for the same
image). In the other 50 percent of all images, the factor is the same for
all channels.
"""
# pylint: disable=invalid-name
# placed here to avoid cyclic dependency
from . import contrast as contrast_lib
return contrast_lib.LinearContrast(
alpha=alpha, per_channel=per_channel,
seed=seed, name=name, random_state=random_state, deterministic=deterministic)
# TODO try adding per channel somehow
class JpegCompression(meta.Augmenter):
"""
Degrade the quality of images by JPEG-compressing them.
During JPEG compression, high frequency components (e.g. edges) are removed.
With low compression (strength) only the highest frequency components are
removed, while very high compression (strength) will lead to only the
lowest frequency components "surviving". This lowers the image quality.
For more details, see https://en.wikipedia.org/wiki/Compression_artifact.
Note that this augmenter still returns images as numpy arrays (i.e. saves
the images with JPEG compression and then reloads them into arrays). It
does not return the raw JPEG file content.
**Supported dtypes**:
See :func:`~imgaug.augmenters.arithmetic.compress_jpeg`.
Parameters
----------
compression : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Degree of compression used during JPEG compression within value range
``[0, 100]``. Higher values denote stronger compression and will cause
low-frequency components to disappear. Note that JPEG's compression
strength is also often set as a *quality*, which is the inverse of this
parameter. Common choices for the *quality* setting are around 80 to 95,
depending on the image. This translates here to a *compression*
parameter of around 20 to 5.
* If a single number, then that value always will be used as the
compression.
* If a tuple ``(a, b)``, then the compression will be
a value sampled uniformly from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image and used as the compression.
* If a ``StochasticParameter``, then ``N`` samples will be drawn
from that parameter per ``N`` input images, each representing the
compression for the ``n``-th image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.JpegCompression(compression=(70, 99))
Remove high frequency components in images via JPEG compression with
a *compression strength* between ``70`` and ``99`` (randomly and
uniformly sampled per image). This corresponds to a (very low) *quality*
setting of ``1`` to ``30``.
"""
def __init__(self, compression=(0, 100),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(JpegCompression, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# will be converted to int during augmentation, which is why we allow
# floats here
self.compression = iap.handle_continuous_param(
compression, "compression",
value_range=(0, 100), tuple_to_uniform=True, list_to_choice=True)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
samples = self.compression.draw_samples((nb_images,),
random_state=random_state)
for i, (image, sample) in enumerate(zip(images, samples)):
batch.images[i] = compress_jpeg(image, int(sample))
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.compression]
================================================
FILE: imgaug/augmenters/artistic.py
================================================
"""
Augmenters that apply artistic image filters.
List of augmenters:
* :class:`Cartoon`
Added in 0.4.0.
"""
from __future__ import print_function, division, absolute_import
import numpy as np
import cv2
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from . import color as colorlib
from .. import dtypes as iadt
from .. import parameters as iap
def stylize_cartoon(image, blur_ksize=3, segmentation_size=1.0,
saturation=2.0, edge_prevalence=1.0,
suppress_edges=True,
from_colorspace=colorlib.CSPACE_RGB):
"""Convert the style of an image to a more cartoonish one.
This function was primarily designed for images with a size of ``200``
to ``800`` pixels. Smaller or larger images may cause issues.
Note that the quality of the results can currently not compete with
learned style transfer, let alone human-made images. A lack of detected
edges or also too many detected edges are probably the most significant
drawbacks.
This method is loosely based on the one proposed in
https://stackoverflow.com/a/11614479/3760780
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
A ``(H,W,3) uint8`` image array.
blur_ksize : int, optional
Kernel size of the median blur filter applied initially to the input
image. Expected to be an odd value and ``>=0``. If an even value,
thn automatically increased to an odd one. If ``<=1``, no blur will
be applied.
segmentation_size : float, optional
Size multiplier to decrease/increase the base size of the initial
mean-shift segmentation of the image. Expected to be ``>=0``.
Note that the base size is increased by roughly a factor of two for
images with height and/or width ``>=400``.
edge_prevalence : float, optional
Multiplier for the prevalance of edges. Higher values lead to more
edges. Note that the default value of ``1.0`` is already fairly
conservative, so there is limit effect from lowerin it further.
saturation : float, optional
Multiplier for the saturation. Set to ``1.0`` to not change the
image's saturation.
suppress_edges : bool, optional
Whether to run edge suppression to remove blobs containing too many
or too few edge pixels.
from_colorspace : str, optional
The source colorspace. Use one of ``imgaug.augmenters.color.CSPACE_*``.
Defaults to ``RGB``.
Returns
-------
ndarray
Image in cartoonish style.
"""
iadt.allow_only_uint8({image.dtype})
assert image.ndim == 3 and image.shape[2] == 3, (
"Expected to get a (H,W,C) image, got shape %s." % (image.shape,))
blur_ksize = max(int(np.round(blur_ksize)), 1)
segmentation_size = max(segmentation_size, 0.0)
saturation = max(saturation, 0.0)
is_small_image = max(image.shape[0:2]) < 400
image = _blur_median(image, blur_ksize)
image_seg = np.zeros_like(image)
if is_small_image:
spatial_window_radius = int(10 * segmentation_size)
color_window_radius = int(20 * segmentation_size)
else:
spatial_window_radius = int(15 * segmentation_size)
color_window_radius = int(40 * segmentation_size)
if segmentation_size <= 0:
image_seg = image
else:
cv2.pyrMeanShiftFiltering(_normalize_cv2_input_arr_(image),
sp=spatial_window_radius,
sr=color_window_radius,
dst=image_seg)
if is_small_image:
edges_raw = _find_edges_canny(image_seg,
edge_prevalence,
from_colorspace)
else:
edges_raw = _find_edges_laplacian(image_seg,
edge_prevalence,
from_colorspace)
edges = edges_raw
edges = ((edges > 100) * 255).astype(np.uint8)
if suppress_edges:
# Suppress dense 3x3 blobs full of detected edges. They are visually
# ugly.
edges = _suppress_edge_blobs(edges, 3, 8, inverse=False)
# Suppress spurious few-pixel edges (5x5 size with <=3 edge pixels).
edges = _suppress_edge_blobs(edges, 5, 3, inverse=True)
return _saturate(_blend_edges(image_seg, edges),
saturation,
from_colorspace)
# Added in 0.4.0.
def _find_edges_canny(image, edge_multiplier, from_colorspace):
image_gray = colorlib.change_colorspace_(np.copy(image),
to_colorspace=colorlib.CSPACE_GRAY,
from_colorspace=from_colorspace)
image_gray = image_gray[..., 0]
thresh = min(int(200 * (1/edge_multiplier)), 254)
edges = cv2.Canny(_normalize_cv2_input_arr_(image_gray), thresh, thresh)
return edges
# Added in 0.4.0.
def _find_edges_laplacian(image, edge_multiplier, from_colorspace):
image_gray = colorlib.change_colorspace_(np.copy(image),
to_colorspace=colorlib.CSPACE_GRAY,
from_colorspace=from_colorspace)
image_gray = image_gray[..., 0]
edges_f = cv2.Laplacian(_normalize_cv2_input_arr_(image_gray / 255.0),
cv2.CV_64F)
edges_f = np.abs(edges_f)
edges_f = edges_f ** 2
vmax = np.percentile(edges_f, min(int(90 * (1/edge_multiplier)), 99))
edges_f = np.clip(edges_f, 0.0, vmax) / vmax
edges_uint8 = np.clip(np.round(edges_f * 255), 0, 255.0).astype(np.uint8)
edges_uint8 = _blur_median(edges_uint8, 3)
edges_uint8 = _threshold(edges_uint8, 50)
return edges_uint8
# Added in 0.4.0.
def _blur_median(image, ksize):
if ksize % 2 == 0:
ksize += 1
if ksize <= 1:
return image
return cv2.medianBlur(_normalize_cv2_input_arr_(image), ksize)
# Added in 0.4.0.
def _threshold(image, thresh):
mask = (image < thresh)
result = np.copy(image)
result[mask] = 0
return result
# Added in 0.4.0.
def _suppress_edge_blobs(edges, size, thresh, inverse):
kernel = np.ones((size, size), dtype=np.float32)
counts = cv2.filter2D(_normalize_cv2_input_arr_(edges / 255.0), -1, kernel)
if inverse:
mask = (counts < thresh)
else:
mask = (counts >= thresh)
edges = np.copy(edges)
edges[mask] = 0
return edges
# Added in 0.4.0.
def _saturate(image, factor, from_colorspace):
image = np.copy(image)
if np.isclose(factor, 1.0, atol=1e-2):
return image
hsv = colorlib.change_colorspace_(image,
to_colorspace=colorlib.CSPACE_HSV,
from_colorspace=from_colorspace)
sat = hsv[:, :, 1]
sat = np.clip(sat.astype(np.int32) * factor, 0, 255).astype(np.uint8)
hsv[:, :, 1] = sat
image_sat = colorlib.change_colorspace_(hsv,
to_colorspace=from_colorspace,
from_colorspace=colorlib.CSPACE_HSV)
return image_sat
# Added in 0.4.0.
def _blend_edges(image, image_edges):
image_edges = 1.0 - (image_edges / 255.0)
image_edges = np.tile(image_edges[..., np.newaxis], (1, 1, 3))
return np.clip(
np.round(image * image_edges),
0.0, 255.0
).astype(np.uint8)
class Cartoon(meta.Augmenter):
"""Convert the style of images to a more cartoonish one.
This augmenter was primarily designed for images with a size of ``200``
to ``800`` pixels. Smaller or larger images may cause issues.
Note that the quality of the results can currently not compete with
learned style transfer, let alone human-made images. A lack of detected
edges or also too many detected edges are probably the most significant
drawbacks.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.artistic.stylize_cartoon`.
Parameters
----------
blur_ksize : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Median filter kernel size.
See :func:`~imgaug.augmenters.artistic.stylize_cartoon` for details.
* If ``number``: That value will be used for all images.
* If ``tuple (a, b) of number``: A random value will be uniformly
sampled per image from the interval ``[a, b)``.
* If ``list``: A random value will be picked per image from the
``list``.
* If ``StochasticParameter``: The parameter will be queried once
per batch for ``(N,)`` values, where ``N`` is the number of
images.
segmentation_size : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Mean-Shift segmentation size multiplier.
See :func:`~imgaug.augmenters.artistic.stylize_cartoon` for details.
* If ``number``: That value will be used for all images.
* If ``tuple (a, b) of number``: A random value will be uniformly
sampled per image from the interval ``[a, b)``.
* If ``list``: A random value will be picked per image from the
``list``.
* If ``StochasticParameter``: The parameter will be queried once
per batch for ``(N,)`` values, where ``N`` is the number of
images.
saturation : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Saturation multiplier.
See :func:`~imgaug.augmenters.artistic.stylize_cartoon` for details.
* If ``number``: That value will be used for all images.
* If ``tuple (a, b) of number``: A random value will be uniformly
sampled per image from the interval ``[a, b)``.
* If ``list``: A random value will be picked per image from the
``list``.
* If ``StochasticParameter``: The parameter will be queried once
per batch for ``(N,)`` values, where ``N`` is the number of
images.
edge_prevalence : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier for the prevalence of edges.
See :func:`~imgaug.augmenters.artistic.stylize_cartoon` for details.
* If ``number``: That value will be used for all images.
* If ``tuple (a, b) of number``: A random value will be uniformly
sampled per image from the interval ``[a, b)``.
* If ``list``: A random value will be picked per image from the
``list``.
* If ``StochasticParameter``: The parameter will be queried once
per batch for ``(N,)`` values, where ``N`` is the number of
images.
from_colorspace : str, optional
The source colorspace. Use one of ``imgaug.augmenters.color.CSPACE_*``.
Defaults to ``RGB``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Cartoon()
Create an example image, then apply a cartoon filter to it.
>>> aug = iaa.Cartoon(blur_ksize=3, segmentation_size=1.0,
>>> saturation=2.0, edge_prevalence=1.0)
Create a non-stochastic cartoon augmenter that produces decent-looking
images.
"""
# Added in 0.4.0.
def __init__(self, blur_ksize=(1, 5), segmentation_size=(0.8, 1.2),
saturation=(1.5, 2.5), edge_prevalence=(0.9, 1.1),
from_colorspace=colorlib.CSPACE_RGB,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Cartoon, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.blur_ksize = iap.handle_continuous_param(
blur_ksize, "blur_ksize", value_range=(0, None),
tuple_to_uniform=True, list_to_choice=True)
self.segmentation_size = iap.handle_continuous_param(
segmentation_size, "segmentation_size", value_range=(0.0, None),
tuple_to_uniform=True, list_to_choice=True)
self.saturation = iap.handle_continuous_param(
saturation, "saturation", value_range=(0.0, None),
tuple_to_uniform=True, list_to_choice=True)
self.edge_prevalence = iap.handle_continuous_param(
edge_prevalence, "edge_prevalence", value_range=(0.0, None),
tuple_to_uniform=True, list_to_choice=True)
self.from_colorspace = from_colorspace
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is not None:
samples = self._draw_samples(batch, random_state)
for i, image in enumerate(batch.images):
image[...] = stylize_cartoon(
image,
blur_ksize=samples[0][i],
segmentation_size=samples[1][i],
saturation=samples[2][i],
edge_prevalence=samples[3][i],
from_colorspace=self.from_colorspace
)
return batch
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
nb_rows = batch.nb_rows
return (
self.blur_ksize.draw_samples((nb_rows,), random_state=random_state),
self.segmentation_size.draw_samples((nb_rows,),
random_state=random_state),
self.saturation.draw_samples((nb_rows,), random_state=random_state),
self.edge_prevalence.draw_samples((nb_rows,),
random_state=random_state)
)
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.blur_ksize, self.segmentation_size, self.saturation,
self.edge_prevalence, self.from_colorspace]
================================================
FILE: imgaug/augmenters/base.py
================================================
"""Base classes and functions used by all/most augmenters.
This module is planned to contain :class:`imgaug.augmenters.meta.Augmenter`
in the future.
Added in 0.4.0.
"""
from __future__ import print_function, division, absolute_import
import imgaug as ia
class SuspiciousMultiImageShapeWarning(UserWarning):
"""Warning multi-image inputs that look like a single image."""
class SuspiciousSingleImageShapeWarning(UserWarning):
"""Warning for single-image inputs that look like multiple images."""
def _warn_on_suspicious_multi_image_shapes(images):
if images is None:
return
# check if it looks like (H, W, C) instead of (N, H, W)
if ia.is_np_array(images):
if images.ndim == 3 and images.shape[-1] in [1, 3]:
ia.warn(
"You provided a numpy array of shape %s as a "
"multi-image augmentation input, which was interpreted as "
"(N, H, W). The last dimension however has value 1 or "
"3, which indicates that you provided a single image "
"with shape (H, W, C) instead. If that is the case, "
"you should use e.g. augmenter(image=) or "
"augment_image() -- note the singular 'image' "
"instead of 'imageS'. Otherwise your single input image "
"will be interpreted as multiple images of shape (H, W) "
"during augmentation." % (images.shape,),
category=SuspiciousMultiImageShapeWarning)
def _warn_on_suspicious_single_image_shape(image):
if image is None:
return
# Check if it looks like (N, H, W) instead of (H, W, C).
# We don't react to (1, 1, C) though, mostly because that is used in many
# unittests.
if image.ndim == 3 and image.shape[-1] >= 32 and image.shape[0:2] != (1, 1):
ia.warn(
"You provided a numpy array of shape %s as a "
"single-image augmentation input, which was interpreted as "
"(H, W, C). The last dimension however has a size of >=32, "
"which indicates that you provided a multi-image array "
"with shape (N, H, W) instead. If that is the case, "
"you should use e.g. augmenter(imageS=) or "
"augment_imageS(). Otherwise your multi-image "
"input will be interpreted as a single image during "
"augmentation." % (image.shape,),
category=SuspiciousSingleImageShapeWarning)
================================================
FILE: imgaug/augmenters/blend.py
================================================
"""
Augmenters that blend two images with each other.
List of augmenters:
* :class:`BlendAlpha`
* :class:`BlendAlphaMask`
* :class:`BlendAlphaElementwise`
* :class:`BlendAlphaSimplexNoise`
* :class:`BlendAlphaFrequencyNoise`
* :class:`BlendAlphaSomeColors`
* :class:`BlendAlphaHorizontalLinearGradient`
* :class:`BlendAlphaVerticalLinearGradient`
* :class:`BlendAlphaSegMapClassIds`
* :class:`BlendAlphaBoundingBoxes`
* :class:`BlendAlphaRegularGrid`
* :class:`BlendAlphaCheckerboard`
"""
from __future__ import print_function, division, absolute_import
from abc import ABCMeta, abstractmethod
import numpy as np
import six
import cv2
import imgaug as ia
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from .. import parameters as iap
from .. import dtypes as iadt
from .. import random as iarandom
from ..augmentables import utils as augm_utils
def _split_1d_array_to_list(arr, sizes):
result = []
i = 0
for size in sizes:
result.append(arr[i:i+size])
i += size
return result
def blend_alpha(image_fg, image_bg, alpha, eps=1e-2):
"""
Blend two images using an alpha blending.
In alpha blending, the two images are naively mixed using a multiplier.
Let ``A`` be the foreground image and ``B`` the background image and
``a`` is the alpha value. Each pixel intensity is then computed as
``a * A_ij + (1-a) * B_ij``.
**Supported dtypes**:
See :func:`imgaug.augmenters.blend.blend_alpha_`.
Parameters
----------
image_fg : (H,W,[C]) ndarray
Foreground image. Shape and dtype kind must match the one of the
background image.
image_bg : (H,W,[C]) ndarray
Background image. Shape and dtype kind must match the one of the
foreground image.
alpha : number or iterable of number or ndarray
The blending factor, between ``0.0`` and ``1.0``. Can be interpreted
as the opacity of the foreground image. Values around ``1.0`` result
in only the foreground image being visible. Values around ``0.0``
result in only the background image being visible. Multiple alphas
may be provided. In these cases, there must be exactly one alpha per
channel in the foreground/background image. Alternatively, for
``(H,W,C)`` images, either one ``(H,W)`` array or an ``(H,W,C)``
array of alphas may be provided, denoting the elementwise alpha value.
eps : number, optional
Controls when an alpha is to be interpreted as exactly ``1.0`` or
exactly ``0.0``, resulting in only the foreground/background being
visible and skipping the actual computation.
Returns
-------
image_blend : (H,W,C) ndarray
Blend of foreground and background image.
"""
return blend_alpha_(np.copy(image_fg), image_bg, alpha, eps=eps)
def blend_alpha_(image_fg, image_bg, alpha, eps=1e-2):
"""
Blend two images in-place using an alpha blending.
In alpha blending, the two images are naively mixed using a multiplier.
Let ``A`` be the foreground image and ``B`` the background image and
``a`` is the alpha value. Each pixel intensity is then computed as
``a * A_ij + (1-a) * B_ij``.
Added in 0.5.0. (Extracted from :func:`blend_alpha`.)
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; fully tested
* ``uint32``: yes; fully tested
* ``uint64``: limited; fully tested (1)
* ``int8``: yes; fully tested
* ``int16``: yes; fully tested
* ``int32``: yes; fully tested
* ``int64``: limited; fully tested (1)
* ``float16``: yes; fully tested
* ``float32``: yes; fully tested
* ``float64``: limited; fully tested (1)
* ``float128``: no (2)
* ``bool``: yes; fully tested (2)
- (1) Tests show that these dtypes work, but a conversion to
``float128`` happens, which only has 96 bits of size instead of
true 128 bits and hence not twice as much resolution. It is
possible that these dtypes result in inaccuracies, though the
tests did not indicate that.
Note that ``float128`` support is required for these dtypes
and thus they are not expected to work on Windows machines.
- (2) Not available due to the input dtype having to be increased to
an equivalent float dtype with two times the input resolution.
- (3) Mapped internally to ``float16``.
Parameters
----------
image_fg : (H,W,[C]) ndarray
Foreground image. Shape and dtype kind must match the one of the
background image.
This image might be modified in-place.
image_bg : (H,W,[C]) ndarray
Background image. Shape and dtype kind must match the one of the
foreground image.
alpha : number or iterable of number or ndarray
The blending factor, between ``0.0`` and ``1.0``. Can be interpreted
as the opacity of the foreground image. Values around ``1.0`` result
in only the foreground image being visible. Values around ``0.0``
result in only the background image being visible. Multiple alphas
may be provided. In these cases, there must be exactly one alpha per
channel in the foreground/background image. Alternatively, for
``(H,W,C)`` images, either one ``(H,W)`` array or an ``(H,W,C)``
array of alphas may be provided, denoting the elementwise alpha value.
eps : number, optional
Controls when an alpha is to be interpreted as exactly ``1.0`` or
exactly ``0.0``, resulting in only the foreground/background being
visible and skipping the actual computation.
Returns
-------
image_blend : (H,W,C) ndarray
Blend of foreground and background image.
This might be an in-place modified version of `image_fg`.
"""
assert image_fg.shape == image_bg.shape, (
"Expected foreground and background images to have the same shape. "
"Got %s and %s." % (image_fg.shape, image_bg.shape))
assert image_fg.dtype.kind == image_bg.dtype.kind, (
"Expected foreground and background images to have the same dtype "
"kind. Got %s and %s." % (image_fg.dtype.kind, image_bg.dtype.kind))
# Note: If float128 is not available on the system, _FLOAT128_DTYPE is
# None, but 'np.dtype("float64") == None' actually equates to True
# for whatever reason, so we check first if the constant is not None
# (i.e. if float128 exists).
if iadt._FLOAT128_DTYPE is not None:
assert image_fg.dtype != iadt._FLOAT128_DTYPE, (
"Foreground image was float128, but blend_alpha_() cannot handle "
"that dtype.")
assert image_bg.dtype != iadt._FLOAT128_DTYPE, (
"Background image was float128, but blend_alpha_() cannot handle "
"that dtype.")
if image_fg.size == 0:
return image_fg
input_was_2d = (image_fg.ndim == 2)
if input_was_2d:
image_fg = image_fg[..., np.newaxis]
image_bg = image_bg[..., np.newaxis]
input_was_bool = False
if image_fg.dtype.kind == "b":
input_was_bool = True
# use float32 instead of float16 here because it seems to be faster
image_fg = image_fg.astype(np.float32)
image_bg = image_bg.astype(np.float32)
alpha = np.array(alpha, dtype=np.float64)
if alpha.size == 1:
pass
else:
if alpha.ndim == 2:
assert alpha.shape == image_fg.shape[0:2], (
"'alpha' given as an array must match the height and width "
"of the foreground and background image. Got shape %s vs "
"foreground/background shape %s." % (
alpha.shape, image_fg.shape))
elif alpha.ndim == 3:
assert (
alpha.shape == image_fg.shape
or alpha.shape == image_fg.shape[0:2] + (1,)), (
"'alpha' given as an array must match the height and "
"width of the foreground and background image. Got "
"shape %s vs foreground/background shape %s." % (
alpha.shape, image_fg.shape))
else:
alpha = alpha.reshape((1, 1, -1))
if not input_was_bool:
if np.all(alpha >= 1.0 - eps):
if input_was_2d:
image_fg = image_fg[..., 0]
return image_fg
if np.all(alpha <= eps):
if input_was_2d:
image_bg = image_bg[..., 0]
# use copy() here so that only image_fg has to be copied in
# blend_alpha()
return np.copy(image_bg)
# for efficiency reasons, only test one value of alpha here, even if alpha
# is much larger
if alpha.size > 0:
assert 0 <= alpha.item(0) <= 1.0, (
"Expected 'alpha' value(s) to be in the interval [0.0, 1.0]. "
"Got min %.4f and max %.4f." % (np.min(alpha), np.max(alpha)))
uint8 = iadt._UINT8_DTYPE
both_uint8 = (image_fg.dtype, image_bg.dtype) == (uint8, uint8)
if both_uint8:
if alpha.size == 1:
image_blend = _blend_alpha_uint8_single_alpha_(
image_fg, image_bg, float(alpha), inplace=True
)
elif alpha.shape == (1, 1, image_fg.shape[2]):
image_blend = _blend_alpha_uint8_channelwise_alphas_(
image_fg, image_bg, alpha[0, 0, :]
)
else:
image_blend = _blend_alpha_uint8_elementwise_(
image_fg, image_bg, alpha
)
else:
image_blend = _blend_alpha_non_uint8(image_fg, image_bg, alpha)
if input_was_bool:
image_blend = image_blend > 0.5
if input_was_2d:
return image_blend[:, :, 0]
return image_blend
# Added in 0.5.0.
def _blend_alpha_uint8_single_alpha_(image_fg, image_bg, alpha, inplace):
# here we are not guarantueed that inputs have ndim=3, can be ndim=2
result = cv2.addWeighted(
_normalize_cv2_input_arr_(image_fg),
alpha,
_normalize_cv2_input_arr_(image_bg),
beta=(1 - alpha),
gamma=0.0,
dst=image_fg if inplace else None
)
if result.ndim == 2 and image_fg.ndim == 3:
return result[:, :, np.newaxis]
return result
# Added in 0.5.0.
def _blend_alpha_uint8_channelwise_alphas_(image_fg, image_bg, alphas):
# we are guarantueed here that image_fg and image_bg have ndim=3
result = []
for i, alpha in enumerate(alphas):
result.append(
_blend_alpha_uint8_single_alpha_(
image_fg[:, :, i],
image_bg[:, :, i],
float(alpha),
inplace=False
)
)
image_blend = _merge_channels(result, image_fg.ndim == 3)
return image_blend
# Added in 0.5.0.
def _blend_alpha_uint8_elementwise_(image_fg, image_bg, alphas):
betas = 1.0 - alphas
is_2d = (alphas.ndim == 2 or alphas.shape[2] == 1)
area = image_fg.shape[0] * image_fg.shape[1]
if is_2d and area >= 64*64:
if alphas.ndim == 3:
alphas = alphas[:, :, 0]
betas = betas[:, :, 0]
result = []
for c in range(image_fg.shape[2]):
image_fg_mul = image_fg[:, :, c]
image_bg_mul = image_bg[:, :, c]
image_fg_mul = cv2.multiply(image_fg_mul, alphas, dtype=cv2.CV_8U)
image_bg_mul = cv2.multiply(image_bg_mul, betas, dtype=cv2.CV_8U)
image_fg_mul = cv2.add(image_fg_mul, image_bg_mul, dst=image_fg_mul)
result.append(image_fg_mul)
image_blend = _merge_channels(result, image_fg.ndim == 3)
return image_blend
else:
if alphas.ndim == 2:
alphas = alphas[..., np.newaxis]
betas = betas[..., np.newaxis]
if alphas.shape[2] != image_fg.shape[2]:
alphas = np.tile(alphas, (1, 1, image_fg.shape[2]))
betas = np.tile(betas, (1, 1, image_fg.shape[2]))
alphas = alphas.ravel()
betas = betas.ravel()
input_shape = image_fg.shape
image_fg_mul = image_fg.ravel()
image_bg_mul = image_bg.ravel()
image_fg_mul = cv2.multiply(
image_fg_mul, alphas, dtype=cv2.CV_8U, dst=image_fg_mul
)
image_bg_mul = cv2.multiply(
image_bg_mul, betas, dtype=cv2.CV_8U, dst=image_bg_mul
)
image_fg_mul = cv2.add(image_fg_mul, image_bg_mul, dst=image_fg_mul)
return image_fg_mul.reshape(input_shape)
# Added in 0.5.0.
# (Extracted from blend_alpha().)
def _blend_alpha_non_uint8(image_fg, image_bg, alpha):
if alpha.ndim == 2:
alpha = alpha[:, :, np.newaxis]
dt_images = iadt.get_minimal_dtype([image_fg, image_bg])
# doing the below itemsize increase only for non-float images led to
# inaccuracies for large float values
# we also use a minimum of 4 bytes (=float32), as float32 tends to be
# faster than float16
isize = dt_images.itemsize * 2
isize = max(isize, 4)
dt_name = "f%d" % (isize,)
# check if float128 (16*8=128) is supported
assert dt_name != "f16" or hasattr(np, "float128"), (
"The input images use dtype '%s', for which alpha-blending "
"requires float128 support to compute accurately its output, "
"but float128 seems to not be available on the current "
"system." % (image_fg.dtype.name,)
)
dt_blend = np.dtype(dt_name)
if alpha.dtype != dt_blend:
alpha = alpha.astype(dt_blend)
if image_fg.dtype != dt_blend:
image_fg = image_fg.astype(dt_blend)
if image_bg.dtype != dt_blend:
image_bg = image_bg.astype(dt_blend)
# the following is
# image_blend = image_bg + alpha * (image_fg - image_bg)
# which is equivalent to
# image_blend = alpha * image_fg + (1 - alpha) * image_bg
# but supposedly faster
image_blend = image_fg - image_bg
image_blend *= alpha
image_blend += image_bg
# Skip clip, because alpha is expected to be in range [0.0, 1.0] and
# both images must have same dtype.
# Dont skip round, because otherwise it is very unlikely to hit the
# image's max possible value
image_blend = iadt.restore_dtypes_(
image_blend, dt_images, clip=False, round=True)
return image_blend
def _merge_channels(channels, input_was_3d):
if len(channels) <= 512:
image_blend = cv2.merge(channels)
else:
image_blend = np.stack(channels, axis=-1)
if image_blend.ndim == 2 and input_was_3d:
image_blend = image_blend[:, :, np.newaxis]
return image_blend
# Added in 0.4.0.
def _generate_branch_outputs(augmenter, batch, hooks, parents):
parents_extended = parents + [augmenter]
# Note here that the propagation hook removes columns in the batch
# and re-adds them afterwards. So the batch should not be copied
# after the `with` statement.
outputs_fg = batch
if augmenter.foreground is not None:
outputs_fg = outputs_fg.deepcopy()
with outputs_fg.propagation_hooks_ctx(augmenter, hooks, parents):
if augmenter.foreground is not None:
outputs_fg = augmenter.foreground.augment_batch_(
outputs_fg,
parents=parents_extended,
hooks=hooks
)
outputs_bg = batch
if augmenter.background is not None:
outputs_bg = outputs_bg.deepcopy()
with outputs_bg.propagation_hooks_ctx(augmenter, hooks, parents):
outputs_bg = augmenter.background.augment_batch_(
outputs_bg,
parents=parents_extended,
hooks=hooks
)
return outputs_fg, outputs_bg
# Added in 0.4.0.
def _to_deterministic(augmenter):
aug = augmenter.copy()
aug.foreground = (
aug.foreground.to_deterministic()
if aug.foreground is not None
else None)
aug.background = (
aug.background.to_deterministic()
if aug.background is not None
else None)
aug.deterministic = True
aug.random_state = augmenter.random_state.derive_rng_()
return aug
class BlendAlpha(meta.Augmenter):
"""
Alpha-blend two image sources using an alpha/opacity value.
The two image sources can be imagined as branches.
If a source is not given, it is automatically the same as the input.
Let ``FG`` be the foreground branch and ``BG`` be the background branch.
Then the result images are defined as ``factor * FG + (1-factor) * BG``,
where ``factor`` is an overlay factor.
.. note::
It is not recommended to use ``BlendAlpha`` with augmenters
that change the geometry of images (e.g. horizontal flips, affine
transformations) if you *also* want to augment coordinates (e.g.
keypoints, polygons, ...), as it is unclear which of the two
coordinate results (foreground or background branch) should be used
as the coordinates after augmentation.
Currently, if ``factor >= 0.5`` (per image), the results of the
foreground branch are used as the new coordinates, otherwise the
results of the background branch.
Added in 0.4.0. (Before that named `Alpha`.)
**Supported dtypes**:
See :func:`~imgaug.augmenters.blend.blend_alpha_`.
Parameters
----------
factor : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Opacity of the results of the foreground branch. Values close to
``0.0`` mean that the results from the background branch (see
parameter `background`) make up most of the final image.
* If float, then that value will be used for all images.
* If tuple ``(a, b)``, then a random value from the interval
``[a, b]`` will be sampled per image.
* If a list, then a random value will be picked from that list per
image.
* If ``StochasticParameter``, then that parameter will be used to
sample a value per image.
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use the same factor for all channels (``False``)
or to sample a new value for each channel (``True``).
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as True, otherwise as False.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlpha(0.5, iaa.Grayscale(1.0))
Convert each image to pure grayscale and alpha-blend the result with the
original image using an alpha of ``50%``, thereby removing about ``50%`` of
all color. This is equivalent to ``iaa.Grayscale(0.5)``.
>>> aug = iaa.BlendAlpha((0.0, 1.0), iaa.Grayscale(1.0))
Same as in the previous example, but the alpha factor is sampled uniformly
from the interval ``[0.0, 1.0]`` once per image, thereby removing a random
fraction of all colors. This is equivalent to
``iaa.Grayscale((0.0, 1.0))``.
>>> aug = iaa.BlendAlpha(
>>> (0.0, 1.0),
>>> iaa.Affine(rotate=(-20, 20)),
>>> per_channel=0.5)
First, rotate each image by a random degree sampled uniformly from the
interval ``[-20, 20]``. Then, alpha-blend that new image with the original
one using a random factor sampled uniformly from the interval
``[0.0, 1.0]``. For ``50%`` of all images, the blending happens
channel-wise and the factor is sampled independently per channel
(``per_channel=0.5``). As a result, e.g. the red channel may look visibly
rotated (factor near ``1.0``), while the green and blue channels may not
look rotated (factors near ``0.0``).
>>> aug = iaa.BlendAlpha(
>>> (0.0, 1.0),
>>> foreground=iaa.Add(100),
>>> background=iaa.Multiply(0.2))
Apply two branches of augmenters -- ``A`` and ``B`` -- *independently*
to input images and alpha-blend the results of these branches using a
factor ``f``. Branch ``A`` increases image pixel intensities by ``100``
and ``B`` multiplies the pixel intensities by ``0.2``. ``f`` is sampled
uniformly from the interval ``[0.0, 1.0]`` per image. The resulting images
contain a bit of ``A`` and a bit of ``B``.
>>> aug = iaa.BlendAlpha([0.25, 0.75], iaa.MedianBlur(13))
Apply median blur to each image and alpha-blend the result with the
original image using an alpha factor of either exactly ``0.25`` or
exactly ``0.75`` (sampled once per image).
"""
# Added in 0.4.0.
def __init__(self, factor=(0.0, 1.0), foreground=None, background=None,
per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(BlendAlpha, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.factor = iap.handle_continuous_param(
factor, "factor", value_range=(0, 1.0), tuple_to_uniform=True,
list_to_choice=True)
assert foreground is not None or background is not None, (
"Expected 'foreground' and/or 'background' to not be None (i.e. "
"at least one Augmenter), but got two None values.")
self.foreground = meta.handle_children_list(
foreground, self.name, "foreground", default=None)
self.background = meta.handle_children_list(
background, self.name, "background", default=None)
self.per_channel = iap.handle_probability_param(per_channel,
"per_channel")
self.epsilon = 1e-2
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
batch_fg, batch_bg = _generate_branch_outputs(
self, batch, hooks, parents)
columns = batch.columns
shapes = batch.get_rowwise_shapes()
nb_images = len(shapes)
nb_channels_max = max([shape[2] if len(shape) > 2 else 1
for shape in shapes])
rngs = random_state.duplicate(2)
per_channel = self.per_channel.draw_samples(nb_images,
random_state=rngs[0])
alphas = self.factor.draw_samples((nb_images, nb_channels_max),
random_state=rngs[1])
for i, shape in enumerate(shapes):
if per_channel[i] > 0.5:
nb_channels = shape[2] if len(shape) > 2 else 1
alphas_i = alphas[i, 0:nb_channels]
else:
# We catch here the case of alphas[i] being empty, which can
# happen if all images have 0 channels.
# In that case the alpha value doesn't matter as the image
# contains zero values anyways.
alphas_i = alphas[i, 0] if alphas[i].size > 0 else 0
# compute alpha for non-image data -- average() also works with
# scalars
alphas_i_avg = np.average(alphas_i)
use_fg_branch = alphas_i_avg >= 0.5
# blend images
if batch.images is not None:
batch.images[i] = blend_alpha_(batch_fg.images[i],
batch_bg.images[i],
alphas_i, eps=self.epsilon)
# blend non-images
# TODO Use gradual blending for heatmaps here (as for images)?
# Heatmaps are probably the only augmentable where this makes
# sense.
for column in columns:
if column.name != "images":
batch_use = (batch_fg if use_fg_branch
else batch_bg)
column.value[i] = getattr(batch_use, column.attr_name)[i]
return batch
# Added in 0.4.0.
def _to_deterministic(self):
return _to_deterministic(self)
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.factor, self.per_channel]
# Added in 0.4.0.
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [lst for lst in [self.foreground, self.background]
if lst is not None]
# Added in 0.4.0.
def __str__(self):
pattern = (
"%s("
"factor=%s, per_channel=%s, name=%s, "
"foreground=%s, background=%s, "
"deterministic=%s"
")"
)
return pattern % (
self.__class__.__name__, self.factor, self.per_channel, self.name,
self.foreground, self.background, self.deterministic)
# tested indirectly via BlendAlphaElementwise for historic reasons
class BlendAlphaMask(meta.Augmenter):
"""
Alpha-blend two image sources using non-binary masks generated per image.
This augmenter queries for each image a mask generator to generate
a ``(H,W)`` or ``(H,W,C)`` channelwise mask ``[0.0, 1.0]``, where
``H`` is the image height and ``W`` the width.
The mask will then be used to alpha-blend pixel- and possibly channel-wise
between a foreground branch of augmenters and a background branch.
(Both branches default to the identity operation if not provided.)
See also :class:`~imgaug.augmenters.blend.BlendAlpha`.
.. note::
It is not recommended to use ``BlendAlphaMask`` with augmenters
that change the geometry of images (e.g. horizontal flips, affine
transformations) if you *also* want to augment coordinates (e.g.
keypoints, polygons, ...), as it is unclear which of the two
coordinate results (foreground or background branch) should be used
as the final output coordinates after augmentation.
Currently, for keypoints the results of the
foreground and background branch will be mixed. That means that for
each coordinate the augmented result will be picked from the
foreground or background branch based on the average alpha mask value
at the corresponding spatial location.
For bounding boxes, line strings and polygons, either all objects
(on an image) of the foreground or all of the background branch will
be used, based on the average over the whole alpha mask.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.blend.blend_alpha_`.
Parameters
----------
mask_generator : IBatchwiseMaskGenerator
A generator that will be queried per image to generate a mask.
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch (i.e. identity function).
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch (i.e. identity function).
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaMask(
>>> iaa.InvertMaskGen(0.5, iaa.VerticalLinearGradientMaskGen()),
>>> iaa.Sequential([
>>> iaa.Clouds(),
>>> iaa.WithChannels([1, 2], iaa.Multiply(0.5))
>>> ])
>>> )
Create an augmenter that sometimes adds clouds at the bottom and sometimes
at the top of the image.
"""
# Currently the mode is only used for keypoint augmentation.
# either or: use all keypoints from fg or all from bg branch (based
# on average of the whole mask).
# pointwise: decide for each point whether to use the fg or bg
# branch's keypoint (based on the average mask value at the point's
# xy-location).
_MODE_EITHER_OR = "either-or"
_MODE_POINTWISE = "pointwise"
_MODES = [_MODE_POINTWISE, _MODE_EITHER_OR]
# Added in 0.4.0.
def __init__(self, mask_generator,
foreground=None, background=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(BlendAlphaMask, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.mask_generator = mask_generator
assert foreground is not None or background is not None, (
"Expected 'foreground' and/or 'background' to not be None (i.e. "
"at least one Augmenter), but got two None values.")
self.foreground = meta.handle_children_list(
foreground, self.name, "foreground", default=None)
self.background = meta.handle_children_list(
background, self.name, "background", default=None)
# this controls how keypoints and polygons are augmented
# Non-keypoints currently uses an either-or approach.
# Using pointwise augmentation is problematic for polygons and line
# strings, because the order of the points may have changed (e.g.
# from clockwise to counter-clockwise). For polygons, it is also
# overall more likely that some child-augmenter added/deleted points
# and we would need a polygon recoverer.
# Overall it seems to be the better approach to use all polygons
# from one branch or the other, which guarantuees their validity.
# TODO decide the either-or not based on the whole average mask
# value but on the average mask value within the polygon's area?
self._coord_modes = {
"keypoints": self._MODE_POINTWISE,
"polygons": self._MODE_EITHER_OR,
"line_strings": self._MODE_EITHER_OR,
"bounding_boxes": self._MODE_EITHER_OR
}
self.epsilon = 1e-2
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
batch_fg, batch_bg = _generate_branch_outputs(
self, batch, hooks, parents)
masks = self.mask_generator.draw_masks(batch, random_state)
for i, mask in enumerate(masks):
if batch.images is not None:
batch.images[i] = blend_alpha_(batch_fg.images[i],
batch_bg.images[i],
mask, eps=self.epsilon)
if batch.heatmaps is not None:
arr = batch.heatmaps[i].arr_0to1
arr_height, arr_width = arr.shape[0:2]
mask_binarized = self._binarize_mask(mask,
arr_height, arr_width)
batch.heatmaps[i].arr_0to1 = blend_alpha_(
batch_fg.heatmaps[i].arr_0to1,
batch_bg.heatmaps[i].arr_0to1,
mask_binarized, eps=self.epsilon)
if batch.segmentation_maps is not None:
arr = batch.segmentation_maps[i].arr
arr_height, arr_width = arr.shape[0:2]
mask_binarized = self._binarize_mask(mask,
arr_height, arr_width)
batch.segmentation_maps[i].arr = blend_alpha_(
batch_fg.segmentation_maps[i].arr,
batch_bg.segmentation_maps[i].arr,
mask_binarized, eps=self.epsilon)
for augm_attr_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
augm_value = getattr(batch, augm_attr_name)
if augm_value is not None:
augm_value[i] = self._blend_coordinates(
augm_value[i],
getattr(batch_fg, augm_attr_name)[i],
getattr(batch_bg, augm_attr_name)[i],
mask,
self._coord_modes[augm_attr_name]
)
return batch
# Added in 0.4.0.
@classmethod
def _binarize_mask(cls, mask, arr_height, arr_width):
# Average over channels, resize to heatmap/segmap array size
# (+clip for cubic interpolation). We can use none-NN interpolation
# for segmaps here as this is just the mask and not the segmap
# array.
mask_3d = np.atleast_3d(mask)
# masks with zero-sized axes crash in np.average() and cannot be
# upscaled in imresize_single_image()
if mask.size == 0:
mask_rs = np.zeros((arr_height, arr_width),
dtype=np.float32)
else:
mask_avg = (
np.average(mask_3d, axis=2) if mask_3d.shape[2] > 0 else 1.0)
mask_rs = ia.imresize_single_image(mask_avg,
(arr_height, arr_width))
mask_arr = iadt.clip_(mask_rs, 0, 1.0)
mask_arr_binarized = (mask_arr >= 0.5)
return mask_arr_binarized
# Added in 0.4.0.
@classmethod
def _blend_coordinates(cls, cbaoi, cbaoi_fg, cbaoi_bg, mask_image,
mode):
coords = augm_utils.convert_cbaois_to_kpsois(cbaoi)
coords_fg = augm_utils.convert_cbaois_to_kpsois(cbaoi_fg)
coords_bg = augm_utils.convert_cbaois_to_kpsois(cbaoi_bg)
coords = coords.to_xy_array()
coords_fg = coords_fg.to_xy_array()
coords_bg = coords_bg.to_xy_array()
assert coords.shape == coords_fg.shape == coords_bg.shape, (
"Expected number of coordinates to not be changed by foreground "
"or background branch in BlendAlphaMask. But input coordinates "
"of shape %s were changed to %s (foreground) and %s "
"(background). Make sure to not use any augmenters that affect "
"the existence of coordinates." % (
coords.shape, coords_fg.shape, coords_bg.shape))
h_img, w_img = mask_image.shape[0:2]
if mode == cls._MODE_POINTWISE:
# Augment pointwise, i.e. check for each point and its
# xy-location the average mask value and pick based on that
# either the point from the foreground or background branch.
assert len(coords_fg) == len(coords_bg), (
"Got different numbers of coordinates before/after "
"augmentation in BlendAlphaMask. The number of "
"coordinates is currently not allowed to change for this "
"augmenter. Input contained %d coordinates, foreground "
"branch %d, backround branch %d." % (
len(coords), len(coords_fg), len(coords_bg)))
coords_aug = []
subgen = zip(coords, coords_fg, coords_bg)
for coord, coord_fg, coord_bg in subgen:
x_int = int(np.round(coord[0]))
y_int = int(np.round(coord[1]))
if 0 <= y_int < h_img and 0 <= x_int < w_img:
alphas_i = mask_image[y_int, x_int, ...]
alpha = (
np.average(alphas_i) if alphas_i.size > 0 else 1.0)
if alpha > 0.5:
coords_aug.append(coord_fg)
else:
coords_aug.append(coord_bg)
else:
coords_aug.append((x_int, y_int))
else:
# Augment with an either-or approach over all points, i.e.
# based on the average of the whole mask, either all points
# from the foreground or all points from the background branch
# are used.
# Note that we ensured above that _keypoint_mode must be
# _MODE_EITHER_OR if it wasn't _MODE_POINTWISE.
mask_image_avg = (
np.average(mask_image) if mask_image.size > 0 else 1.0)
if mask_image_avg > 0.5:
coords_aug = coords_fg
else:
coords_aug = coords_bg
kpsoi_aug = ia.KeypointsOnImage.from_xy_array(
coords_aug, shape=cbaoi.shape)
return augm_utils.invert_convert_cbaois_to_kpsois_(cbaoi, kpsoi_aug)
# Added in 0.4.0.
def _to_deterministic(self):
return _to_deterministic(self)
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.mask_generator]
# Added in 0.4.0.
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [lst for lst in [self.foreground, self.background]
if lst is not None]
# Added in 0.4.0.
def __str__(self):
pattern = (
"%s("
"mask_generator=%s, name=%s, foreground=%s, background=%s, "
"deterministic=%s"
")"
)
return pattern % (
self.__class__.__name__, self.mask_generator, self.name,
self.foreground, self.background, self.deterministic)
# FIXME the output of the third example makes it look like per_channel isn't
# working
class BlendAlphaElementwise(BlendAlphaMask):
"""
Alpha-blend two image sources using alpha/opacity values sampled per pixel.
This is the same as :class:`BlendAlpha`, except that the opacity factor is
sampled once per *pixel* instead of once per *image* (or a few times per
image, if ``BlendAlpha.per_channel`` is set to ``True``).
See :class:`BlendAlpha` for more details.
This class is a wrapper around
:class:`~imgaug.augmenters.blend.BlendAlphaMask`.
.. note::
Avoid using augmenters as children that affect pixel locations (e.g.
horizontal flips). See
:class:`~imgaug.augmenters.blend.BlendAlphaMask` for details.
Added in 0.4.0. (Before that named `AlphaElementwise`.)
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaMask`.
Parameters
----------
factor : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Opacity of the results of the foreground branch. Values close to
``0.0`` mean that the results from the background branch (see
parameter `background`) make up most of the final image.
* If float, then that value will be used for all images.
* If tuple ``(a, b)``, then a random value from the interval
``[a, b]`` will be sampled per image.
* If a list, then a random value will be picked from that list per
image.
* If ``StochasticParameter``, then that parameter will be used to
sample a value per image.
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
per_channel : bool or float, optional
Whether to use the same factor for all channels (``False``)
or to sample a new value for each channel (``True``).
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as True, otherwise as False.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaElementwise(0.5, iaa.Grayscale(1.0))
Convert each image to pure grayscale and alpha-blend the result with the
original image using an alpha of ``50%`` for all pixels, thereby removing
about ``50%`` of all color. This is equivalent to ``iaa.Grayscale(0.5)``.
This is also equivalent to ``iaa.BlendAlpha(0.5, iaa.Grayscale(1.0))``, as
the opacity has a fixed value of ``0.5`` and is hence identical for all
pixels.
>>> aug = iaa.BlendAlphaElementwise((0, 1.0), iaa.AddToHue(100))
Same as in the previous example, but here with hue-shift instead
of grayscaling and additionally the alpha factor is sampled uniformly
from the interval ``[0.0, 1.0]`` once per pixel, thereby shifting the
hue by a random fraction for each pixel.
>>> aug = iaa.BlendAlphaElementwise(
>>> (0.0, 1.0),
>>> iaa.Affine(rotate=(-20, 20)),
>>> per_channel=0.5)
First, rotate each image by a random degree sampled uniformly from the
interval ``[-20, 20]``. Then, alpha-blend that new image with the original
one using a random factor sampled uniformly from the interval
``[0.0, 1.0]`` per pixel. For ``50%`` of all images, the blending happens
channel-wise and the factor is sampled independently per pixel *and*
channel (``per_channel=0.5``). As a result, e.g. the red channel may look
visibly rotated (factor near ``1.0``), while the green and blue channels
may not look rotated (factors near ``0.0``).
>>> aug = iaa.BlendAlphaElementwise(
>>> (0.0, 1.0),
>>> foreground=iaa.Add(100),
>>> background=iaa.Multiply(0.2))
Apply two branches of augmenters -- ``A`` and ``B`` -- *independently*
to input images and alpha-blend the results of these branches using a
factor ``f``. Branch ``A`` increases image pixel intensities by ``100``
and ``B`` multiplies the pixel intensities by ``0.2``. ``f`` is sampled
uniformly from the interval ``[0.0, 1.0]`` per pixel. The resulting images
contain a bit of ``A`` and a bit of ``B``.
>>> aug = iaa.BlendAlphaElementwise([0.25, 0.75], iaa.MedianBlur(13))
Apply median blur to each image and alpha-blend the result with the
original image using an alpha factor of either exactly ``0.25`` or
exactly ``0.75`` (sampled once per pixel).
"""
# Added in 0.4.0.
def __init__(self, factor=(0.0, 1.0), foreground=None, background=None,
per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
factor = iap.handle_continuous_param(
factor, "factor", value_range=(0, 1.0), tuple_to_uniform=True,
list_to_choice=True)
mask_gen = StochasticParameterMaskGen(factor, per_channel)
super(BlendAlphaElementwise, self).__init__(
mask_gen, foreground, background,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
@property
def factor(self):
return self.mask_generator.parameter
class BlendAlphaSimplexNoise(BlendAlphaElementwise):
"""Alpha-blend two image sources using simplex noise alpha masks.
The alpha masks are sampled using a simplex noise method, roughly creating
connected blobs of 1s surrounded by 0s. If nearest neighbour
upsampling is used, these blobs can be rectangular with sharp edges.
Added in 0.4.0. (Before that named `SimplexNoiseAlpha`.)
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaElementwise`.
Parameters
----------
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
per_channel : bool or float, optional
Whether to use the same factor for all channels (``False``)
or to sample a new value for each channel (``True``).
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``, otherwise as ``False``.
size_px_max : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
The simplex noise is always generated in a low resolution environment.
This parameter defines the maximum size of that environment (in
pixels). The environment is initialized at the same size as the input
image and then downscaled, so that no side exceeds `size_px_max`
(aspect ratio is kept).
* If int, then that number will be used as the size for all
iterations.
* If tuple of two ``int`` s ``(a, b)``, then a value will be
sampled per iteration from the discrete interval ``[a..b]``.
* If a list of ``int`` s, then a value will be picked per iteration
at random from that list.
* If a ``StochasticParameter``, then a value will be sampled from
that parameter per iteration.
upscale_method : None or imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
After generating the noise maps in low resolution environments, they
have to be upscaled to the input image size. This parameter controls
the upscaling method.
* If ``None``, then either ``nearest`` or ``linear`` or ``cubic``
is picked. Most weight is put on ``linear``, followed by
``cubic``.
* If ``imgaug.ALL``, then either ``nearest`` or ``linear`` or
``area`` or ``cubic`` is picked per iteration (all same
probability).
* If a string, then that value will be used as the method (must be
``nearest`` or ``linear`` or ``area`` or ``cubic``).
* If list of string, then a random value will be picked from that
list per iteration.
* If ``StochasticParameter``, then a random value will be sampled
from that parameter per iteration.
iterations : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
How often to repeat the simplex noise generation process per image.
* If ``int``, then that number will be used as the iterations for
all images.
* If tuple of two ``int`` s ``(a, b)``, then a value will be
sampled per image from the discrete interval ``[a..b]``.
* If a list of ``int`` s, then a value will be picked per image at
random from that list.
* If a ``StochasticParameter``, then a value will be sampled from
that parameter per image.
aggregation_method : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
The noise maps (from each iteration) are combined to one noise map
using an aggregation process. This parameter defines the method used
for that process. Valid methods are ``min``, ``max`` or ``avg``,
where ``min`` combines the noise maps by taking the (elementwise)
minimum over all iteration's results, ``max`` the (elementwise)
maximum and ``avg`` the (elementwise) average.
* If ``imgaug.ALL``, then a random value will be picked per image
from the valid ones.
* If a string, then that value will always be used as the method.
* If a list of string, then a random value will be picked from
that list per image.
* If a ``StochasticParameter``, then a random value will be
sampled from that paramter per image.
sigmoid : bool or number, optional
Whether to apply a sigmoid function to the final noise maps, resulting
in maps that have more extreme values (close to 0.0 or 1.0).
* If ``bool``, then a sigmoid will always (``True``) or never
(``False``) be applied.
* If a number ``p`` with ``0<=p<=1``, then a sigmoid will be
applied to ``p`` percent of all final noise maps.
sigmoid_thresh : None or number or tuple of number or imgaug.parameters.StochasticParameter, optional
Threshold of the sigmoid, when applied. Thresholds above zero
(e.g. ``5.0``) will move the saddle point towards the right, leading
to more values close to 0.0.
* If ``None``, then ``Normal(0, 5.0)`` will be used.
* If number, then that threshold will be used for all images.
* If tuple of two numbers ``(a, b)``, then a random value will
be sampled per image from the interval ``[a, b]``.
* If ``StochasticParameter``, then a random value will be sampled
from that parameter per image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaSimplexNoise(iaa.EdgeDetect(1.0))
Detect per image all edges, mark them in a black and white image and
then alpha-blend the result with the original image using simplex noise
masks.
>>> aug = iaa.BlendAlphaSimplexNoise(
>>> iaa.EdgeDetect(1.0),
>>> upscale_method="nearest")
Same as in the previous example, but using only nearest neighbour
upscaling to scale the simplex noise masks to the final image sizes, i.e.
no nearest linear upsampling is used. This leads to rectangles with sharp
edges.
>>> aug = iaa.BlendAlphaSimplexNoise(
>>> iaa.EdgeDetect(1.0),
>>> upscale_method="linear")
Same as in the previous example, but using only linear upscaling to
scale the simplex noise masks to the final image sizes, i.e. no nearest
neighbour upsampling is used. This leads to rectangles with smooth edges.
>>> aug = iaa.BlendAlphaSimplexNoise(
>>> iaa.EdgeDetect(1.0),
>>> sigmoid_thresh=iap.Normal(10.0, 5.0))
Same as in the first example, but using a threshold for the sigmoid
function that is further to the right. This is more conservative, i.e.
the generated noise masks will be mostly black (values around ``0.0``),
which means that most of the original images (parameter/branch
`background`) will be kept, rather than using the results of the
augmentation (parameter/branch `foreground`).
"""
# Added in 0.4.0.
def __init__(self, foreground=None, background=None, per_channel=False,
size_px_max=(2, 16), upscale_method=None,
iterations=(1, 3), aggregation_method="max",
sigmoid=True, sigmoid_thresh=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
upscale_method_default = iap.Choice(["nearest", "linear", "cubic"],
p=[0.05, 0.6, 0.35])
sigmoid_thresh_default = iap.Normal(0.0, 5.0)
noise = iap.SimplexNoise(
size_px_max=size_px_max,
upscale_method=(upscale_method
if upscale_method is not None
else upscale_method_default)
)
if iterations != 1:
noise = iap.IterativeNoiseAggregator(
noise,
iterations=iterations,
aggregation_method=aggregation_method
)
use_sigmoid = (
sigmoid is True
or (ia.is_single_number(sigmoid) and sigmoid >= 0.01))
if use_sigmoid:
noise = iap.Sigmoid.create_for_noise(
noise,
threshold=(sigmoid_thresh
if sigmoid_thresh is not None
else sigmoid_thresh_default),
activated=sigmoid
)
super(BlendAlphaSimplexNoise, self).__init__(
factor=noise, foreground=foreground, background=background,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class BlendAlphaFrequencyNoise(BlendAlphaElementwise):
"""Alpha-blend two image sources using frequency noise masks.
The alpha masks are sampled using frequency noise of varying scales,
which can sometimes create large connected blobs of ``1`` s surrounded
by ``0`` s and other times results in smaller patterns. If nearest
neighbour upsampling is used, these blobs can be rectangular with sharp
edges.
Added in 0.4.0. (Before that named `FrequencyNoiseAlpha`.)
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaElementwise`.
Parameters
----------
exponent : number or tuple of number of list of number or imgaug.parameters.StochasticParameter, optional
Exponent to use when scaling in the frequency domain.
Sane values are in the range ``-4`` (large blobs) to ``4`` (small
patterns). To generate cloud-like structures, use roughly ``-2``.
* If number, then that number will be used as the exponent for all
iterations.
* If tuple of two numbers ``(a, b)``, then a value will be sampled
per iteration from the interval ``[a, b]``.
* If a list of numbers, then a value will be picked per iteration
at random from that list.
* If a ``StochasticParameter``, then a value will be sampled from
that parameter per iteration.
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
per_channel : bool or float, optional
Whether to use the same factor for all channels (``False``)
or to sample a new value for each channel (``True``).
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``, otherwise as ``False``.
size_px_max : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
The noise is generated in a low resolution environment.
This parameter defines the maximum size of that environment (in
pixels). The environment is initialized at the same size as the input
image and then downscaled, so that no side exceeds `size_px_max`
(aspect ratio is kept).
* If ``int``, then that number will be used as the size for all
iterations.
* If tuple of two ``int`` s ``(a, b)``, then a value will be
sampled per iteration from the discrete interval ``[a..b]``.
* If a list of ``int`` s, then a value will be picked per
iteration at random from that list.
* If a ``StochasticParameter``, then a value will be sampled from
that parameter per iteration.
upscale_method : None or imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
After generating the noise maps in low resolution environments, they
have to be upscaled to the input image size. This parameter controls
the upscaling method.
* If ``None``, then either ``nearest`` or ``linear`` or ``cubic``
is picked. Most weight is put on ``linear``, followed by
``cubic``.
* If ``imgaug.ALL``, then either ``nearest`` or ``linear`` or
``area`` or ``cubic`` is picked per iteration (all same
probability).
* If string, then that value will be used as the method (must be
``nearest`` or ``linear`` or ``area`` or ``cubic``).
* If list of string, then a random value will be picked from that
list per iteration.
* If ``StochasticParameter``, then a random value will be sampled
from that parameter per iteration.
iterations : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
How often to repeat the simplex noise generation process per
image.
* If ``int``, then that number will be used as the iterations for
all images.
* If tuple of two ``int`` s ``(a, b)``, then a value will be
sampled per image from the discrete interval ``[a..b]``.
* If a list of ``int`` s, then a value will be picked per image at
random from that list.
* If a ``StochasticParameter``, then a value will be sampled from
that parameter per image.
aggregation_method : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
The noise maps (from each iteration) are combined to one noise map
using an aggregation process. This parameter defines the method used
for that process. Valid methods are ``min``, ``max`` or ``avg``,
where 'min' combines the noise maps by taking the (elementwise) minimum
over all iteration's results, ``max`` the (elementwise) maximum and
``avg`` the (elementwise) average.
* If ``imgaug.ALL``, then a random value will be picked per image
from the valid ones.
* If a string, then that value will always be used as the method.
* If a list of string, then a random value will be picked from
that list per image.
* If a ``StochasticParameter``, then a random value will be sampled
from that parameter per image.
sigmoid : bool or number, optional
Whether to apply a sigmoid function to the final noise maps, resulting
in maps that have more extreme values (close to ``0.0`` or ``1.0``).
* If ``bool``, then a sigmoid will always (``True``) or never
(``False``) be applied.
* If a number ``p`` with ``0<=p<=1``, then a sigmoid will be applied to
``p`` percent of all final noise maps.
sigmoid_thresh : None or number or tuple of number or imgaug.parameters.StochasticParameter, optional
Threshold of the sigmoid, when applied. Thresholds above zero
(e.g. ``5.0``) will move the saddle point towards the right, leading to
more values close to ``0.0``.
* If ``None``, then ``Normal(0, 5.0)`` will be used.
* If number, then that threshold will be used for all images.
* If tuple of two numbers ``(a, b)``, then a random value will
be sampled per image from the range ``[a, b]``.
* If ``StochasticParameter``, then a random value will be sampled
from that parameter per image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaFrequencyNoise(foreground=iaa.EdgeDetect(1.0))
Detect per image all edges, mark them in a black and white image and
then alpha-blend the result with the original image using frequency noise
masks.
>>> aug = iaa.BlendAlphaFrequencyNoise(
>>> foreground=iaa.EdgeDetect(1.0),
>>> upscale_method="nearest")
Same as the first example, but using only linear upscaling to
scale the frequency noise masks to the final image sizes, i.e. no nearest
neighbour upsampling is used. This results in smooth edges.
>>> aug = iaa.BlendAlphaFrequencyNoise(
>>> foreground=iaa.EdgeDetect(1.0),
>>> upscale_method="linear")
Same as the first example, but using only linear upscaling to
scale the frequency noise masks to the final image sizes, i.e. no nearest
neighbour upsampling is used. This results in smooth edges.
>>> aug = iaa.BlendAlphaFrequencyNoise(
>>> foreground=iaa.EdgeDetect(1.0),
>>> upscale_method="linear",
>>> exponent=-2,
>>> sigmoid=False)
Same as in the previous example, but with the exponent set to a constant
``-2`` and the sigmoid deactivated, resulting in cloud-like patterns
without sharp edges.
>>> aug = iaa.BlendAlphaFrequencyNoise(
>>> foreground=iaa.EdgeDetect(1.0),
>>> sigmoid_thresh=iap.Normal(10.0, 5.0))
Same as the first example, but using a threshold for the sigmoid function
that is further to the right. This is more conservative, i.e. the generated
noise masks will be mostly black (values around ``0.0``), which means that
most of the original images (parameter/branch `background`) will be kept,
rather than using the results of the augmentation (parameter/branch
`foreground`).
"""
# Added in 0.4.0.
def __init__(self, exponent=(-4, 4), foreground=None, background=None,
per_channel=False, size_px_max=(4, 16), upscale_method=None,
iterations=(1, 3), aggregation_method=["avg", "max"],
sigmoid=0.5, sigmoid_thresh=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
upscale_method_default = iap.Choice(["nearest", "linear", "cubic"],
p=[0.05, 0.6, 0.35])
sigmoid_thresh_default = iap.Normal(0.0, 5.0)
noise = iap.FrequencyNoise(
exponent=exponent,
size_px_max=size_px_max,
upscale_method=(upscale_method
if upscale_method is not None
else upscale_method_default)
)
if iterations != 1:
noise = iap.IterativeNoiseAggregator(
noise,
iterations=iterations,
aggregation_method=aggregation_method
)
use_sigmoid = (
sigmoid is True
or (ia.is_single_number(sigmoid) and sigmoid >= 0.01))
if use_sigmoid:
noise = iap.Sigmoid.create_for_noise(
noise,
threshold=(sigmoid_thresh
if sigmoid_thresh is not None
else sigmoid_thresh_default),
activated=sigmoid
)
super(BlendAlphaFrequencyNoise, self).__init__(
factor=noise, foreground=foreground, background=background,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class BlendAlphaSomeColors(BlendAlphaMask):
"""Blend images from two branches using colorwise masks.
This class generates masks that "mark" a few colors and replace the
pixels within these colors with the results of the foreground branch.
The remaining pixels are replaced with the results of the background
branch (usually the identity function). That allows to e.g. selectively
grayscale a few colors, while keeping other colors unchanged.
This class is a thin wrapper around
:class:`~imgaug.augmenters.blend.BlendAlphaMask` together with
:class:`~imgaug.augmenters.blend.SomeColorsMaskGen`.
.. note::
The underlying mask generator will produce an ``AssertionError`` for
batches that contain no images.
.. note::
Avoid using augmenters as children that affect pixel locations (e.g.
horizontal flips). See
:class:`~imgaug.augmenters.blend.BlendAlphaMask` for details.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspaces_`.
Parameters
----------
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
nb_bins : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.SomeColorsMaskGen`.
smoothness : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.SomeColorsMaskGen`.
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.SomeColorsMaskGen`.
rotation_deg : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.SomeColorsMaskGen`.
from_colorspace : str, optional
See :class:`~imgaug.augmenters.blend.SomeColorsMaskGen`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaSomeColors(iaa.Grayscale(1.0))
Create an augmenter that turns randomly removes some colors in images by
grayscaling them.
>>> aug = iaa.BlendAlphaSomeColors(iaa.TotalDropout(1.0))
Create an augmenter that removes some colors in images by replacing them
with black pixels.
>>> aug = iaa.BlendAlphaSomeColors(
>>> iaa.MultiplySaturation(0.5), iaa.MultiplySaturation(1.5))
Create an augmenter that desaturates some colors and increases the
saturation of the remaining ones.
>>> aug = iaa.BlendAlphaSomeColors(
>>> iaa.AveragePooling(7), alpha=[0.0, 1.0], smoothness=0.0)
Create an augmenter that applies average pooling to some colors.
Each color tune is either selected (alpha of ``1.0``) or not
selected (``0.0``). There is no gradual change between similar colors.
>>> aug = iaa.BlendAlphaSomeColors(
>>> iaa.AveragePooling(7), nb_bins=2, smoothness=0.0)
Create an augmenter that applies average pooling to some colors.
Choose on average half of all colors in images for the blending operation.
>>> aug = iaa.BlendAlphaSomeColors(
>>> iaa.AveragePooling(7), from_colorspace="BGR")
Create an augmenter that applies average pooling to some colors with
input images being in BGR colorspace.
"""
# Added in 0.4.0.
def __init__(self, foreground=None, background=None,
nb_bins=(5, 15), smoothness=(0.1, 0.3),
alpha=[0.0, 1.0], rotation_deg=(0, 360),
from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
super(BlendAlphaSomeColors, self).__init__(
SomeColorsMaskGen(
nb_bins=nb_bins,
smoothness=smoothness,
alpha=alpha,
rotation_deg=rotation_deg,
from_colorspace=from_colorspace
),
foreground=foreground,
background=background,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class BlendAlphaHorizontalLinearGradient(BlendAlphaMask):
"""Blend images from two branches along a horizontal linear gradient.
This class generates a horizontal linear gradient mask (i.e. usually a
mask with low values on the left and high values on the right) and
alphas-blends between foreground and background branch using that
mask.
This class is a thin wrapper around
:class:`~imgaug.augmenters.blend.BlendAlphaMask` together with
:class:`~imgaug.augmenters.blend.HorizontalLinearGradientMaskGen`.
.. note::
Avoid using augmenters as children that affect pixel locations (e.g.
horizontal flips). See
:class:`~imgaug.augmenters.blend.BlendAlphaMask` for details.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaMask`.
Parameters
----------
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
min_value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.HorizontalLinearGradientMaskGen`.
max_value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.HorizontalLinearGradientMaskGen`.
start_at : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.HorizontalLinearGradientMaskGen`.
end_at : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.HorizontalLinearGradientMaskGen`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaHorizontalLinearGradient(iaa.AddToHue((-100, 100)))
Create an augmenter that randomizes the hue towards the right of the
image.
>>> aug = iaa.BlendAlphaHorizontalLinearGradient(
>>> iaa.TotalDropout(1.0),
>>> min_value=0.2, max_value=0.8)
Create an augmenter that replaces pixels towards the right with darker
and darker values. However it always keeps at least
20% (``1.0 - max_value``) of the original pixel value on the far right
and always replaces at least 20% on the far left (``min_value=0.2``).
>>> aug = iaa.BlendAlphaHorizontalLinearGradient(
>>> iaa.AveragePooling(11),
>>> start_at=(0.0, 1.0), end_at=(0.0, 1.0))
Create an augmenter that blends with an average-pooled image according
to a horizontal gradient that starts at a random x-coordinate and reaches
its maximum at another random x-coordinate. Due to that randomness,
the gradient may increase towards the left or right.
"""
# Added in 0.4.0.
def __init__(self, foreground=None, background=None,
min_value=(0.0, 0.2), max_value=(0.8, 1.0),
start_at=(0.0, 0.2), end_at=(0.8, 1.0),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(BlendAlphaHorizontalLinearGradient, self).__init__(
HorizontalLinearGradientMaskGen(
min_value=min_value,
max_value=max_value,
start_at=start_at,
end_at=end_at
),
foreground=foreground,
background=background,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class BlendAlphaVerticalLinearGradient(BlendAlphaMask):
"""Blend images from two branches along a vertical linear gradient.
This class generates a vertical linear gradient mask (i.e. usually a
mask with low values on the left and high values on the right) and
alphas-blends between foreground and background branch using that
mask.
This class is a thin wrapper around
:class:`~imgaug.augmenters.blend.BlendAlphaMask` together with
:class:`~imgaug.augmenters.blend.VerticalLinearGradientMaskGen`.
.. note::
Avoid using augmenters as children that affect pixel locations (e.g.
horizontal flips). See
:class:`~imgaug.augmenters.blend.BlendAlphaMask` for details.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaMask`.
Parameters
----------
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
min_value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.VerticalLinearGradientMaskGen`.
max_value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.VerticalLinearGradientMaskGen`.
start_at : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.VerticalLinearGradientMaskGen`.
end_at : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.VerticalLinearGradientMaskGen`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaVerticalLinearGradient(iaa.AddToHue((-100, 100)))
Create an augmenter that randomizes the hue towards the bottom of the
image.
>>> aug = iaa.BlendAlphaVerticalLinearGradient(
>>> iaa.TotalDropout(1.0),
>>> min_value=0.2, max_value=0.8)
Create an augmenter that replaces pixels towards the bottom with darker
and darker values. However it always keeps at least
20% (``1.0 - max_value``) of the original pixel value on the far bottom
and always replaces at least 20% on the far top (``min_value=0.2``).
>>> aug = iaa.BlendAlphaVerticalLinearGradient(
>>> iaa.AveragePooling(11),
>>> start_at=(0.0, 1.0), end_at=(0.0, 1.0))
Create an augmenter that blends with an average-pooled image according
to a vertical gradient that starts at a random y-coordinate and reaches
its maximum at another random y-coordinate. Due to that randomness,
the gradient may increase towards the bottom or top.
>>> aug = iaa.BlendAlphaVerticalLinearGradient(
>>> iaa.Clouds(),
>>> start_at=(0.15, 0.35), end_at=0.0)
Create an augmenter that draws clouds in roughly the top quarter of the
image.
"""
# Added in 0.4.0.
def __init__(self, foreground=None, background=None,
min_value=(0.0, 0.2), max_value=(0.8, 1.0),
start_at=(0.0, 0.2), end_at=(0.8, 1.0),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(BlendAlphaVerticalLinearGradient, self).__init__(
VerticalLinearGradientMaskGen(
min_value=min_value,
max_value=max_value,
start_at=start_at,
end_at=end_at
),
foreground=foreground,
background=background,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class BlendAlphaRegularGrid(BlendAlphaMask):
"""Blend images from two branches according to a regular grid.
This class generates for each image a mask that splits the image into a
grid-like pattern of ``H`` rows and ``W`` columns. Each cell is then
filled with an alpha value, sampled randomly per cell.
The difference to :class:`AlphaBlendCheckerboard` is that this class
samples random alpha values per grid cell, while in the checkerboard the
alpha values follow a fixed pattern.
This class is a thin wrapper around
:class:`~imgaug.augmenters.blend.BlendAlphaMask` together with
:class:`~imgaug.augmenters.blend.RegularGridMaskGen`.
.. note::
Avoid using augmenters as children that affect pixel locations (e.g.
horizontal flips). See
:class:`~imgaug.augmenters.blend.BlendAlphaMask` for details.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaMask`.
Parameters
----------
nb_rows : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
Number of rows of the checkerboard.
See :class:`~imgaug.augmenters.blend.CheckerboardMaskGen` for details.
nb_cols : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
Number of columns of the checkerboard. Analogous to `nb_rows`.
See :class:`~imgaug.augmenters.blend.CheckerboardMaskGen` for details.
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Alpha value of each cell.
* If ``number``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the interval ``[a, b]``.
* If ``list``: A random value will be picked per image from that list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaRegularGrid(nb_rows=(4, 6), nb_cols=(1, 4),
>>> foreground=iaa.Multiply(0.0))
Create an augmenter that places a ``HxW`` grid on each image, where
``H`` (rows) is randomly and uniformly sampled from the interval ``[4, 6]``
and ``W`` is analogously sampled from the interval ``[1, 4]``. Roughly
half of the cells in the grid are filled with ``0.0``, the remaining ones
are unaltered. Which cells exactly are "dropped" is randomly decided
per image. The resulting effect is similar to
:class:`~imgaug.augmenters.arithmetic.CoarseDropout`.
>>> aug = iaa.BlendAlphaRegularGrid(nb_rows=2, nb_cols=2,
>>> foreground=iaa.Multiply(0.0),
>>> background=iaa.AveragePooling(8),
>>> alpha=[0.0, 0.0, 1.0])
Create an augmenter that always placed ``2x2`` cells on each image
and sets about ``1/3`` of them to zero (foreground branch) and
the remaining ``2/3`` to a pixelated version (background branch).
"""
# Added in 0.4.0.
def __init__(self, nb_rows, nb_cols,
foreground=None, background=None,
alpha=[0.0, 1.0],
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
super(BlendAlphaRegularGrid, self).__init__(
RegularGridMaskGen(
nb_rows=nb_rows,
nb_cols=nb_cols,
alpha=alpha
),
foreground=foreground,
background=background,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class BlendAlphaCheckerboard(BlendAlphaMask):
"""Blend images from two branches according to a checkerboard pattern.
This class generates for each image a mask following a checkboard layout of
``H`` rows and ``W`` columns. Each cell is then filled with either
``1.0`` or ``0.0``. The cell at the top-left is always ``1.0``. Its right
and bottom neighbour cells are ``0.0``. The 4-neighbours of any cell always
have a value opposite to the cell's value (``0.0`` vs. ``1.0``).
This class is a thin wrapper around
:class:`~imgaug.augmenters.blend.BlendAlphaMask` together with
:class:`~imgaug.augmenters.blend.CheckerboardMaskGen`.
.. note::
Avoid using augmenters as children that affect pixel locations (e.g.
horizontal flips). See
:class:`~imgaug.augmenters.blend.BlendAlphaMask` for details.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaMask`.
Parameters
----------
nb_rows : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
Number of rows of the checkerboard.
See :class:`~imgaug.augmenters.blend.CheckerboardMaskGen` for details.
nb_cols : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
Number of columns of the checkerboard. Analogous to `nb_rows`.
See :class:`~imgaug.augmenters.blend.CheckerboardMaskGen` for details.
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaCheckerboard(nb_rows=2, nb_cols=(1, 4),
>>> foreground=iaa.AddToHue((-100, 100)))
Create an augmenter that places a ``HxW`` grid on each image, where
``H`` (rows) is always ``2`` and ``W`` is randomly and uniformly sampled
from the interval ``[1, 4]``. For half of the cells in the grid the hue
is randomly modified, the other half of the cells is unaltered.
"""
# Added in 0.4.0.
def __init__(self, nb_rows, nb_cols,
foreground=None, background=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(BlendAlphaCheckerboard, self).__init__(
CheckerboardMaskGen(
nb_rows=nb_rows,
nb_cols=nb_cols
),
foreground=foreground,
background=background,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class BlendAlphaSegMapClassIds(BlendAlphaMask):
"""Blend images from two branches based on segmentation map ids.
This class generates masks that are ``1.0`` at pixel locations covered
by specific classes in segmentation maps.
This class is a thin wrapper around
:class:`~imgaug.augmenters.blend.BlendAlphaMask` together with
:class:`~imgaug.augmenters.blend.SegMapClassIdsMaskGen`.
.. note::
Avoid using augmenters as children that affect pixel locations (e.g.
horizontal flips). See
:class:`~imgaug.augmenters.blend.BlendAlphaMask` for details.
.. note::
Segmentation maps can have multiple channels. If that is the case
then for each position ``(x, y)`` it is sufficient that any class id
in any channel matches one of the desired class ids.
.. note::
This class will produce an ``AssertionError`` if there are no
segmentation maps in a batch.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaMask`.
Parameters
----------
class_ids : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
See :class:`~imgaug.augmenters.blend.SegMapClassIdsMaskGen`.
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
nb_sample_classes : None or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.SegMapClassIdsMaskGen`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaSegMapClassIds(
>>> [1, 3],
>>> foreground=iaa.AddToHue((-100, 100)))
Create an augmenter that randomizes the hue wherever the segmentation maps
contain the classes ``1`` or ``3``.
>>> aug = iaa.BlendAlphaSegMapClassIds(
>>> [1, 2, 3, 4],
>>> nb_sample_classes=2,
>>> foreground=iaa.GaussianBlur(3.0))
Create an augmenter that randomly picks ``2`` classes from the
list ``[1, 2, 3, 4]`` and blurs the image content wherever these classes
appear in the segmentation map. Note that as the sampling of class ids
happens *with replacement*, it is not guaranteed to sample two *unique*
class ids.
>>> aug = iaa.Sometimes(0.2,
>>> iaa.BlendAlphaSegMapClassIds(
>>> 2,
>>> background=iaa.TotalDropout(1.0)))
Create an augmenter that zeros for roughly every fifth image all
image pixels that do *not* belong to class id ``2`` (note that the
`background` branch was used, not the `foreground` branch).
Example use case: Human body landmark detection where both the
landmarks/keypoints and the body segmentation map are known. Train the
model to detect landmarks and sometimes remove all non-body information
to force the model to become more independent of the background.
"""
# Added in 0.4.0.
def __init__(self,
class_ids,
foreground=None, background=None,
nb_sample_classes=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(BlendAlphaSegMapClassIds, self).__init__(
SegMapClassIdsMaskGen(
class_ids=class_ids,
nb_sample_classes=nb_sample_classes
),
foreground=foreground,
background=background,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class BlendAlphaBoundingBoxes(BlendAlphaMask):
"""Blend images from two branches based on areas enclosed in bounding boxes.
This class generates masks that are ``1.0`` within bounding boxes of given
labels. A mask pixel will be set to ``1.0`` if *at least* one bounding box
covers the area and has one of the requested labels.
This class is a thin wrapper around
:class:`~imgaug.augmenters.blend.BlendAlphaMask` together with
:class:`~imgaug.augmenters.blend.BoundingBoxesMaskGen`.
.. note::
Avoid using augmenters as children that affect pixel locations (e.g.
horizontal flips). See
:class:`~imgaug.augmenters.blend.BlendAlphaMask` for details.
.. note::
This class will produce an ``AssertionError`` if there are no
bounding boxes in a batch.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.blend.BlendAlphaMask`.
Parameters
----------
labels : None or str or list of str or imgaug.parameters.StochasticParameter
See :class:`~imgaug.augmenters.blend.BoundingBoxesMaskGen`.
foreground : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the foreground branch.
High alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the foreground branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
background : None or imgaug.augmenters.meta.Augmenter or iterable of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) that make up the background branch.
Low alpha values will show this branch's results.
* If ``None``, then the input images will be reused as the output
of the background branch.
* If ``Augmenter``, then that augmenter will be used as the branch.
* If iterable of ``Augmenter``, then that iterable will be
converted into a ``Sequential`` and used as the augmenter.
nb_sample_labels : None or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.blend.BoundingBoxesMaskGen`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BlendAlphaBoundingBoxes("person",
>>> foreground=iaa.Grayscale(1.0))
Create an augmenter that removes color within bounding boxes having the
label ``person``.
>>> aug = iaa.BlendAlphaBoundingBoxes(["person", "car"],
>>> foreground=iaa.AddToHue((-255, 255)))
Create an augmenter that randomizes the hue within bounding boxes that
have the label ``person`` or ``car``.
>>> aug = iaa.BlendAlphaBoundingBoxes(["person", "car"],
>>> foreground=iaa.AddToHue((-255, 255)),
>>> nb_sample_labels=1)
Create an augmenter that randomizes the hue within bounding boxes that
have either the label ``person`` or ``car``. Only one label is picked per
image. Note that the sampling happens with replacement, so if
``nb_sample_classes`` would be ``>1``, it could still lead to only one
*unique* label being sampled.
>>> aug = iaa.BlendAlphaBoundingBoxes(None,
>>> background=iaa.Multiply(0.0))
Create an augmenter that zeros all pixels (``Multiply(0.0)``)
that are *not* (``background`` branch) within bounding boxes of
*any* (``None``) label. In other words, all pixels outside of bounding
boxes become black.
Note that we don't use ``TotalDropout`` here, because by default it will
also remove all coordinate-based augmentables, which will break the
blending of such inputs.
"""
# Added in 0.4.0.
def __init__(self,
labels,
foreground=None, background=None,
nb_sample_labels=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(BlendAlphaBoundingBoxes, self).__init__(
BoundingBoxesMaskGen(
labels=labels,
nb_sample_labels=nb_sample_labels
),
foreground=foreground,
background=background,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
@six.add_metaclass(ABCMeta)
class IBatchwiseMaskGenerator(object):
"""Interface for classes generating masks for batches.
Child classes are supposed to receive a batch and generate an iterable
of masks, one per row (i.e. image), matching the row shape (i.e. image
shape). This is used in :class:`~imgaug.augmenters.blend.BlendAlphaMask`.
Added in 0.4.0.
"""
# Added in 0.4.0.
def draw_masks(self, batch, random_state=None):
"""Generate a mask with given shape.
Parameters
----------
batch : imgaug.augmentables.batches._BatchInAugmentation
Shape of the mask to sample.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
A seed or random number generator to use during the sampling
process. If ``None``, the global RNG will be used.
See also :func:`~imgaug.augmenters.meta.Augmenter.__init__`
for a similar parameter with more details.
Returns
-------
iterable of ndarray
Masks, one per row in the batch.
Each mask must be a ``float32`` array in interval ``[0.0, 1.0]``.
It must either have the same shape as the row (i.e. the image)
or shape ``(H, W)`` if all channels are supposed to have the
same mask.
"""
class StochasticParameterMaskGen(IBatchwiseMaskGenerator):
"""Mask generator that queries stochastic parameters for mask values.
This class receives batches for which to generate masks, iterates over
the batch rows (i.e. images) and generates one mask per row.
For a row with shape ``(H, W, C)`` (= image shape), it generates
either a ``(H, W)`` mask (if ``per_channel`` is false-like) or a
``(H, W, C)`` mask (if ``per_channel`` is true-like).
The ``per_channel`` is sampled per batch for each row/image.
Added in 0.4.0.
Parameters
----------
parameter : imgaug.parameters.StochasticParameter
Stochastic parameter to draw mask samples from.
Expected to return values in interval ``[0.0, 1.0]`` (not all
stochastic parameters do that) and must be able to handle sampling
shapes ``(H, W)`` and ``(H, W, C)`` (all stochastic parameters should
do that).
per_channel : bool or float or imgaug.parameters.StochasticParameter, optional
Whether to use the same mask for all channels (``False``)
or to sample a new mask for each channel (``True``).
If this value is a float ``p``, then for ``p`` percent of all rows
(i.e. images) `per_channel` will be treated as ``True``, otherwise
as ``False``.
"""
# Added in 0.4.0.
def __init__(self, parameter, per_channel):
super(StochasticParameterMaskGen, self).__init__()
self.parameter = parameter
self.per_channel = iap.handle_probability_param(per_channel,
"per_channel")
# Added in 0.4.0.
def draw_masks(self, batch, random_state=None):
"""
See :func:`~imgaug.augmenters.blend.IBatchwiseMaskGenerator.draw_masks`.
"""
shapes = batch.get_rowwise_shapes()
random_state = iarandom.RNG.create_if_not_rng_(random_state)
per_channel = self.per_channel.draw_samples((len(shapes),),
random_state=random_state)
return [self._draw_mask(shape, random_state, per_channel_i)
for shape, per_channel_i
in zip(shapes, per_channel)]
# Added in 0.4.0.
def _draw_mask(self, shape, random_state, per_channel):
if len(shape) == 2 or per_channel >= 0.5:
mask = self.parameter.draw_samples(shape,
random_state=random_state)
else:
# TODO When this was wrongly sampled directly as (H,W,C) no
# test for AlphaElementwise ended up failing. That should not
# happen.
# We are guarantueed here to have (H, W, C) as shape (H, W) is
# handled by the above block.
# As the mask is not channelwise, we will just return (H, W)
# instead of (H, W, C).
mask = self.parameter.draw_samples(shape[0:2],
random_state=random_state)
# mask has no elements if height or width in shape is 0
if mask.size > 0:
assert 0 <= mask.item(0) <= 1.0, (
"Expected 'parameter' samples to be in the interval "
"[0.0, 1.0]. Got min %.4f and max %.4f." % (
np.min(mask), np.max(mask),))
return mask
class SomeColorsMaskGen(IBatchwiseMaskGenerator):
"""Generator that produces masks based on some similar colors in images.
This class receives batches for which to generate masks, iterates over
the batch rows (i.e. images) and generates one mask per row.
The mask contains high alpha values for some colors, while other colors
get low mask values. Which colors are chosen is random. How wide or
narrow the selection is (e.g. very specific blue tone or all blue-ish
colors) is determined by the hyperparameters.
The color selection method performs roughly the following steps:
1. Split the full color range of the hue in ``HSV`` into ``nb_bins``
bins (i.e. ``256/nb_bins`` different possible hue tones).
2. Shift the bins by ``rotation_deg`` degrees. (This way, the ``0th``
bin does not always start at exactly ``0deg`` of hue.)
3. Sample ``alpha`` values for each bin.
4. Repeat the ``nb_bins`` bins until there are ``256`` bins.
5. Smoothen the alpha values of neighbouring bins using a gaussian
kernel. The kernel's ``sigma`` is derived from ``smoothness``.
6. Associate all hue values in the image with the corresponding bin's
alpha value. This results in the alpha mask.
.. note::
This mask generator will produce an ``AssertionError`` for batches
that contain no images.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspaces_`.
Parameters
----------
nb_bins : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of bins. For ``B`` bins, each bin denotes roughly ``360/B``
degrees of colors in the hue channel. Lower values lead to a coarser
selection of colors. Expected value range is ``[2, 256]``.
* If ``int``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the discrete interval ``[a..b]``.
* If ``list``: A random value will be picked per image from that
list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
smoothness : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Strength of the 1D gaussian kernel applied to the sampled binwise
alpha values. Larger values will lead to more similar grayscaling of
neighbouring colors. Expected value range is ``[0.0, 1.0]``.
* If ``number``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the interval ``[a, b]``.
* If ``list``: A random value will be picked per image from that
list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Parameter to sample binwise alpha blending factors from. Expected
value range is ``[0.0, 1.0]``. Note that the alpha values will be
smoothed between neighbouring bins. Hence, it is usually a good idea
to set this so that the probability distribution peaks are around
``0.0`` and ``1.0``, e.g. via a list ``[0.0, 1.0]`` or a ``Beta``
distribution.
It is not recommended to set this to a deterministic value, otherwise
all bins and hence all pixels in the generated mask will have the
same value.
* If ``number``: Exactly that value will be used for all bins.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per bin from the interval ``[a, b]``.
* If ``list``: A random value will be picked per bin from that list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N*B,)`` values -- one per image and bin.
rotation_deg : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Rotiational shift of each bin as a fraction of ``360`` degrees.
E.g. ``0.0`` will not shift any bins, while a value of ``0.5`` will
shift by around ``180`` degrees. This shift is mainly used so that
the ``0th`` bin does not always start at ``0deg``. Expected value
range is ``[-360, 360]``. This parameter can usually be kept at the
default value.
* If ``number``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the interval ``[a, b]``.
* If ``list``: A random value will be picked per image from that
list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
from_colorspace : str, optional
The source colorspace (of the input images).
See :func:`~imgaug.augmenters.color.change_colorspace_`.
"""
# Added in 0.4.0.
# TODO colorlib.CSPACE_RGB produces 'has no attribute' error?
def __init__(self, nb_bins=(5, 15), smoothness=(0.1, 0.3),
alpha=[0.0, 1.0], rotation_deg=(0, 360),
from_colorspace="RGB"):
# pylint: disable=dangerous-default-value
super(SomeColorsMaskGen, self).__init__()
self.nb_bins = iap.handle_discrete_param(
nb_bins, "nb_bins", value_range=(1, 256),
tuple_to_uniform=True, list_to_choice=True)
self.smoothness = iap.handle_continuous_param(
smoothness, "smoothness", value_range=(0.0, 1.0),
tuple_to_uniform=True, list_to_choice=True)
self.alpha = iap.handle_continuous_param(
alpha, "alpha", value_range=(0.0, 1.0),
tuple_to_uniform=True, list_to_choice=True)
self.rotation_deg = iap.handle_continuous_param(
rotation_deg, "rotation_deg", value_range=(-360, 360),
tuple_to_uniform=True, list_to_choice=True)
self.from_colorspace = from_colorspace
self.sigma_max = 10.0
# Added in 0.4.0.
def draw_masks(self, batch, random_state=None):
"""
See :func:`~imgaug.augmenters.blend.IBatchwiseMaskGenerator.draw_masks`.
"""
assert batch.images is not None, (
"Can only generate masks for batches that contain images, but "
"got a batch without images.")
random_state = iarandom.RNG.create_if_not_rng_(random_state)
samples = self._draw_samples(batch, random_state=random_state)
return [self._draw_mask(image, i, samples)
for i, image
in enumerate(batch.images)]
# Added in 0.4.0.
def _draw_mask(self, image, image_idx, samples):
return self.generate_mask(
image,
samples[0][image_idx],
samples[1][image_idx] * self.sigma_max,
samples[2][image_idx],
self.from_colorspace)
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
nb_rows = batch.nb_rows
nb_bins = self.nb_bins.draw_samples((nb_rows,),
random_state=random_state)
smoothness = self.smoothness.draw_samples((nb_rows,),
random_state=random_state)
alpha = self.alpha.draw_samples((np.sum(nb_bins),),
random_state=random_state)
rotation_deg = self.rotation_deg.draw_samples(
(nb_rows,), random_state=random_state)
nb_bins = np.clip(nb_bins, 1, 256)
smoothness = np.clip(smoothness, 0.0, 1.0)
alpha = np.clip(alpha, 0.0, 1.0)
rotation_bins = np.mod(
np.round(rotation_deg * (256/360)).astype(np.int32),
256)
binwise_alphas = _split_1d_array_to_list(alpha, nb_bins)
return binwise_alphas, smoothness, rotation_bins
@classmethod
def generate_mask(cls, image, binwise_alphas, sigma,
rotation_bins, from_colorspace):
"""Generate a colorwise alpha mask for a single image.
Added in 0.4.0.
Parameters
----------
image : ndarray
Image for which to generate the mask. Must have shape ``(H,W,3)``
in colorspace `from_colorspace`.
binwise_alphas : ndarray
Alpha values of shape ``(B,)`` with ``B`` in ``[1, 256]``
and values in interval ``[0.0, 1.0]``. Will be upscaled to
256 bins by simple repetition. Each bin represents ``1/256`` th
of the hue.
sigma : float
Sigma of the 1D gaussian kernel applied to the upscaled binwise
alpha value array.
rotation_bins : int
By how much to rotate the 256 bin alpha array. The rotation is
given in number of bins.
from_colorspace : str
Colorspace of the input image. One of
``imgaug.augmenters.color.CSPACE_*``.
Returns
-------
ndarray
``float32`` mask array of shape ``(H, W)`` with values in
``[0.0, 1.0]``
"""
# import has to be deferred, otherwise python 2.7 fails
from . import color as colorlib
image_hsv = colorlib.change_colorspace_(
np.copy(image),
to_colorspace=colorlib.CSPACE_HSV,
from_colorspace=from_colorspace)
if 0 in image_hsv.shape[0:2]:
return np.zeros(image_hsv.shape[0:2], dtype=np.float32)
binwise_alphas = cls._upscale_to_256_alpha_bins(binwise_alphas)
binwise_alphas = cls._rotate_alpha_bins(binwise_alphas, rotation_bins)
binwise_alphas_smooth = cls._smoothen_alphas(binwise_alphas, sigma)
mask = cls._generate_pixelwise_alpha_mask(image_hsv,
binwise_alphas_smooth)
return mask
# Added in 0.4.0.
@classmethod
def _upscale_to_256_alpha_bins(cls, alphas):
# repeat alphas bins so that B sampled bins become 256 bins
nb_bins = len(alphas)
nb_repeats_per_bin = int(np.ceil(256/nb_bins))
alphas = np.repeat(alphas, (nb_repeats_per_bin,))
alphas = alphas[0:256]
return alphas
# Added in 0.4.0.
@classmethod
def _rotate_alpha_bins(cls, alphas, rotation_bins):
# e.g. for offset 2: abcdef -> cdefab
# note: offset here is expected to be in [0, 256]
if rotation_bins > 0:
alphas = np.roll(alphas, -rotation_bins)
return alphas
# Added in 0.4.0.
@classmethod
def _smoothen_alphas(cls, alphas, sigma):
if sigma <= 0.0+1e-2:
return alphas
ksize = max(int(sigma * 2.5), 3)
ksize_y, ksize_x = (1, ksize)
if ksize_x % 2 == 0:
ksize_x += 1
# we fake here cv2.BORDER_WRAP, because GaussianBlur does not
# support that mode, i.e. we want:
# cdefgh|abcdefgh|abcdefg
alphas = np.concatenate([
alphas[-ksize_x:],
alphas,
alphas[:ksize_x],
])
alphas = cv2.GaussianBlur(
_normalize_cv2_input_arr_(alphas[np.newaxis, :]),
ksize=(ksize_x, ksize_y),
sigmaX=sigma, sigmaY=sigma,
borderType=cv2.BORDER_REPLICATE
)[0, :]
# revert fake BORDER_WRAP
alphas = alphas[ksize_x:-ksize_x]
return alphas
# Added in 0.4.0.
@classmethod
def _generate_pixelwise_alpha_mask(cls, image_hsv, hue_to_alpha):
hue = image_hsv[:, :, 0]
table = hue_to_alpha * 255
table = np.clip(np.round(table), 0, 255).astype(np.uint8)
mask = ia.apply_lut(hue, table)
return mask.astype(np.float32) / 255.0
# Added in 0.4.0.
class _LinearGradientMaskGen(IBatchwiseMaskGenerator):
# Added in 0.4.0.
def __init__(self, axis, min_value=0.0, max_value=1.0,
start_at=0.0, end_at=1.0):
self.axis = axis
self.min_value = iap.handle_continuous_param(
min_value, "min_value", value_range=(0.0, 1.0),
tuple_to_uniform=True, list_to_choice=True)
self.max_value = iap.handle_continuous_param(
max_value, "max_value", value_range=(0.0, 1.0),
tuple_to_uniform=True, list_to_choice=True)
self.start_at = iap.handle_continuous_param(
start_at, "start_at", value_range=(0.0, 1.0),
tuple_to_uniform=True, list_to_choice=True)
self.end_at = iap.handle_continuous_param(
end_at, "end_at", value_range=(0.0, 1.0),
tuple_to_uniform=True, list_to_choice=True)
def draw_masks(self, batch, random_state=None):
"""
See :func:`~imgaug.augmenters.blend.IBatchwiseMaskGenerator.draw_masks`.
Added in 0.4.0.
"""
random_state = iarandom.RNG.create_if_not_rng_(random_state)
shapes = batch.get_rowwise_shapes()
samples = self._draw_samples(len(shapes), random_state=random_state)
return [self._draw_mask(shape, i, samples)
for i, shape
in enumerate(shapes)]
# Added in 0.4.0.
def _draw_mask(self, shape, image_idx, samples):
return self.generate_mask(
shape,
samples[0][image_idx],
samples[1][image_idx],
samples[2][image_idx],
samples[3][image_idx])
# Added in 0.4.0.
def _draw_samples(self, nb_rows, random_state):
min_value = self.min_value.draw_samples((nb_rows,),
random_state=random_state)
max_value = self.max_value.draw_samples((nb_rows,),
random_state=random_state)
start_at = self.start_at.draw_samples(
(nb_rows,), random_state=random_state)
end_at = self.end_at.draw_samples(
(nb_rows,), random_state=random_state)
return min_value, max_value, start_at, end_at
@classmethod
@abstractmethod
def generate_mask(cls, shape, min_value, max_value, start_at, end_at):
"""Generate a horizontal gradient mask.
Added in 0.4.0.
Parameters
----------
shape : tuple of int
Shape of the image. The mask will have the same height and
width.
min_value : number
Minimum value of the gradient in interval ``[0.0, 1.0]``.
max_value : number
Maximum value of the gradient in interval ``[0.0, 1.0]``.
start_at : number
Position on the x-axis where the linear gradient starts, given as
a fraction of the axis size. Interval is ``[0.0, 1.0]``.
end_at : number
Position on the x-axis where the linear gradient ends, given as
a fraction of the axis size. Interval is ``[0.0, 1.0]``.
Returns
-------
ndarray
``float32`` mask array with same height and width as the image.
Values are in ``[0.0, 1.0]``.
"""
# Added in 0.4.0.
@classmethod
def _generate_mask(cls, shape, axis, min_value, max_value, start_at,
end_at):
height, width = shape[0:2]
axis_size = shape[axis]
min_value = min(max(min_value, 0.0), 1.0)
max_value = min(max(max_value, 0.0), 1.0)
start_at_px = min(max(int(start_at * axis_size), 0), axis_size)
end_at_px = min(max(int(end_at * axis_size), 0), axis_size)
inverted = False
if end_at_px < start_at_px:
inverted = True
start_at_px, end_at_px = end_at_px, start_at_px
before_grad = np.full((start_at_px,), min_value,
dtype=np.float32)
grad = np.linspace(start=min_value,
stop=max_value,
num=end_at_px - start_at_px,
dtype=np.float32)
after_grad = np.full((axis_size - end_at_px,), max_value,
dtype=np.float32)
mask = np.concatenate((
before_grad,
grad,
after_grad
), axis=0)
if inverted:
mask = 1.0 - mask
if axis == 0:
mask = mask[:, np.newaxis]
mask = np.tile(mask, (1, width))
else:
mask = mask[np.newaxis, :]
mask = np.tile(mask, (height, 1))
return mask
class HorizontalLinearGradientMaskGen(_LinearGradientMaskGen):
"""Generator that produces horizontal linear gradient masks.
This class receives batches and produces for each row (i.e. image)
a horizontal linear gradient that matches the row's shape (i.e. image
shape). The gradient increases linearly from a minimum value to a
maximum value along the x-axis. The start and end points (i.e. where the
minimum value starts to increase and where it reaches the maximum)
may be defines as fractions of the width. E.g. for width ``100`` and
``start=0.25``, ``end=0.75``, the gradient would have its minimum
in interval ``[0px, 25px]`` and its maximum in interval ``[75px, 100px]``.
Note that this has nothing to do with a *derivative* along the x-axis.
Added in 0.4.0.
Parameters
----------
min_value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Minimum value that the mask will have up to the start point of the
linear gradient.
Note that `min_value` is allowed to be larger than `max_value`,
in which case the gradient will start at the (higher) `min_value`
and decrease towards the (lower) `max_value`.
* If ``number``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the interval ``[a, b]``.
* If ``list``: A random value will be picked per image from that list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
max_value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Maximum value that the mask will have at the end of the
linear gradient.
Datatypes are analogous to `min_value`.
start_at : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Position on the x-axis where the linear gradient starts, given as a
fraction of the axis size. Interval is ``[0.0, 1.0]``, where ``0.0``
is at the left of the image.
If ``end_at < start_at`` the gradient will be inverted.
Datatypes are analogous to `min_value`.
end_at : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Position on the x-axis where the linear gradient ends, given as a
fraction of the axis size. Interval is ``[0.0, 1.0]``, where ``0.0``
is at the right of the image.
Datatypes are analogous to `min_value`.
"""
# Added in 0.4.0.
def __init__(self, min_value=(0.0, 0.2), max_value=(0.8, 1.0),
start_at=(0.0, 0.2), end_at=(0.8, 1.0)):
super(HorizontalLinearGradientMaskGen, self).__init__(
axis=1,
min_value=min_value,
max_value=max_value,
start_at=start_at,
end_at=end_at)
@classmethod
def generate_mask(cls, shape, min_value, max_value, start_at, end_at):
"""Generate a linear horizontal gradient mask.
Added in 0.4.0.
Parameters
----------
shape : tuple of int
Shape of the image. The mask will have the same height and
width.
min_value : number
Minimum value of the gradient in interval ``[0.0, 1.0]``.
max_value : number
Maximum value of the gradient in interval ``[0.0, 1.0]``.
start_at : number
Position on the x-axis where the linear gradient starts, given as
a fraction of the axis size. Interval is ``[0.0, 1.0]``.
end_at : number
Position on the x-axis where the linear gradient ends, given as
a fraction of the axis size. Interval is ``[0.0, 1.0]``.
Returns
-------
ndarray
``float32`` mask array with same height and width as the image.
Values are in ``[0.0, 1.0]``.
"""
return cls._generate_mask(
axis=1,
shape=shape,
min_value=min_value,
max_value=max_value,
start_at=start_at,
end_at=end_at)
class VerticalLinearGradientMaskGen(_LinearGradientMaskGen):
"""Generator that produces vertical linear gradient masks.
See :class:`~imgaug.augmenters.blend.HorizontalLinearGradientMaskGen`
for details.
Added in 0.4.0.
Parameters
----------
min_value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Minimum value that the mask will have up to the start point of the
linear gradient.
Note that `min_value` is allowed to be larger than `max_value`,
in which case the gradient will start at the (higher) `min_value`
and decrease towards the (lower) `max_value`.
* If ``number``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the interval ``[a, b]``.
* If ``list``: A random value will be picked per image from that list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
max_value : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Maximum value that the mask will have at the end of the
linear gradient.
Datatypes are analogous to `min_value`.
start_at : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Position on the y-axis where the linear gradient starts, given as a
fraction of the axis size. Interval is ``[0.0, 1.0]``, where ``0.0``
is at the top of the image.
If ``end_at < start_at`` the gradient will be inverted.
Datatypes are analogous to `min_value`.
end_at : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Position on the x-axis where the linear gradient ends, given as a
fraction of the axis size. Interval is ``[0.0, 1.0]``, where ``1.0``
is at the bottom of the image.
Datatypes are analogous to `min_value`.
"""
# Added in 0.4.0.
def __init__(self, min_value=(0.0, 0.2), max_value=(0.8, 1.0),
start_at=(0.0, 0.2), end_at=(0.8, 1.0)):
super(VerticalLinearGradientMaskGen, self).__init__(
axis=0,
min_value=min_value,
max_value=max_value,
start_at=start_at,
end_at=end_at)
@classmethod
def generate_mask(cls, shape, min_value, max_value, start_at, end_at):
"""Generate a linear horizontal gradient mask.
Added in 0.4.0.
Parameters
----------
shape : tuple of int
Shape of the image. The mask will have the same height and
width.
min_value : number
Minimum value of the gradient in interval ``[0.0, 1.0]``.
max_value : number
Maximum value of the gradient in interval ``[0.0, 1.0]``.
start_at : number
Position on the x-axis where the linear gradient starts, given as
a fraction of the axis size. Interval is ``[0.0, 1.0]``.
end_at : number
Position on the x-axis where the linear gradient ends, given as
a fraction of the axis size. Interval is ``[0.0, 1.0]``.
Returns
-------
ndarray
``float32`` mask array with same height and width as the image.
Values are in ``[0.0, 1.0]``.
"""
return cls._generate_mask(
axis=0,
shape=shape,
min_value=min_value,
max_value=max_value,
start_at=start_at,
end_at=end_at)
class RegularGridMaskGen(IBatchwiseMaskGenerator):
"""Generate masks following a regular grid pattern.
This mask generator splits each image into a grid-like pattern of
``H`` rows and ``W`` columns. Each cell is then filled with an alpha
value, sampled randomly per cell.
The difference to :class:`CheckerboardMaskGen` is that this mask generator
samples random alpha values per cell, while in the checkerboard the
alpha values follow a fixed pattern.
Added in 0.4.0.
Parameters
----------
nb_rows : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
Number of rows of the regular grid.
* If ``int``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the discrete interval ``[a..b]``.
* If ``list``: A random value will be picked per image from that
list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
nb_cols : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
Number of columns of the checkerboard. Analogous to `nb_rows`.
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Alpha value of each cell.
* If ``number``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the interval ``[a, b]``.
* If ``list``: A random value will be picked per image from that list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
"""
# Added in 0.4.0.
def __init__(self, nb_rows, nb_cols, alpha=[0.0, 1.0]):
# pylint: disable=dangerous-default-value
self.nb_rows = iap.handle_discrete_param(
nb_rows, "nb_rows", value_range=(1, None),
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False)
self.nb_cols = iap.handle_discrete_param(
nb_cols, "nb_cols", value_range=(1, None),
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False)
self.alpha = iap.handle_continuous_param(
alpha, "alpha", value_range=(0.0, 1.0),
tuple_to_uniform=True, list_to_choice=True)
def draw_masks(self, batch, random_state=None):
"""
See :func:`~imgaug.augmenters.blend.IBatchwiseMaskGenerator.draw_masks`.
Added in 0.4.0.
"""
random_state = iarandom.RNG.create_if_not_rng_(random_state)
shapes = batch.get_rowwise_shapes()
nb_rows, nb_cols, alpha = self._draw_samples(len(shapes),
random_state=random_state)
return [self.generate_mask(shape, nb_rows_i, nb_cols_i, alpha_i)
for shape, nb_rows_i, nb_cols_i, alpha_i
in zip(shapes, nb_rows, nb_cols, alpha)]
# Added in 0.4.0.
def _draw_samples(self, nb_images, random_state):
nb_rows = self.nb_rows.draw_samples((nb_images,),
random_state=random_state)
nb_cols = self.nb_cols.draw_samples((nb_images,),
random_state=random_state)
nb_alphas_per_img = nb_rows * nb_cols
alpha_raw = self.alpha.draw_samples(
(np.sum(nb_alphas_per_img),),
random_state=random_state)
alpha = _split_1d_array_to_list(alpha_raw, nb_alphas_per_img)
return nb_rows, nb_cols, alpha
@classmethod
def generate_mask(cls, shape, nb_rows, nb_cols, alphas):
"""Generate a mask following a checkerboard pattern.
Added in 0.4.0.
Parameters
----------
shape : tuple of int
Height and width of the output mask.
nb_rows : int
Number of rows of the checkerboard pattern.
nb_cols : int
Number of columns of the checkerboard pattern.
alphas : ndarray
1D or 2D array containing for each cell the alpha value, i.e.
``nb_rows*nb_cols`` values.
Returns
-------
ndarray
``float32`` mask array with same height and width as
``segmap.shape``. Values are in ``[0.0, 1.0]``.
"""
from . import size as sizelib
height, width = shape[0:2]
if 0 in (height, width):
return np.zeros((height, width), dtype=np.float32)
nb_rows = min(max(nb_rows, 1), height)
nb_cols = min(max(nb_cols, 1), width)
cell_height = int(height / nb_rows)
cell_width = int(width / nb_cols)
# If there are more alpha values than nb_rows*nb_cols we reduce the
# number of alpha values.
alphas = alphas.flat[0:nb_rows*nb_cols]
assert alphas.size == nb_rows*nb_cols, (
"Expected `alphas` to not contain less values than "
"`nb_rows * nb_cols` (both clipped to [1, height] and "
"[1, width] respectively). Got %d alpha values vs %d expected "
"values (nb_rows=%d, nb_cols=%d) for requested mask shape %s." % (
alphas.size, nb_rows * nb_cols, nb_rows, nb_cols,
(height, width)))
mask = alphas.astype(np.float32).reshape((nb_rows, nb_cols))
mask = np.repeat(mask, cell_height, axis=0)
mask = np.repeat(mask, cell_width, axis=1)
# if mask is too small, reflection pad it on all sides
missing_height = height - mask.shape[0]
missing_width = width - mask.shape[1]
top = int(np.floor(missing_height / 2))
bottom = int(np.ceil(missing_height / 2))
left = int(np.floor(missing_width / 2))
right = int(np.ceil(missing_width / 2))
mask = sizelib.pad(mask,
top=top, right=right, bottom=bottom, left=left,
mode="reflect")
return mask
class CheckerboardMaskGen(IBatchwiseMaskGenerator):
"""Generate masks following a checkerboard-like pattern.
This mask generator splits each image into a regular grid of
``H`` rows and ``W`` columns. Each cell is then filled with either
``1.0`` or ``0.0``. The cell at the top-left is always ``1.0``. Its right
and bottom neighbour cells are ``0.0``. The 4-neighbours of any cell always
have a value opposite to the cell's value (``0.0`` vs. ``1.0``).
Added in 0.4.0.
Parameters
----------
nb_rows : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of rows of the checkerboard.
* If ``int``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled
per image from the discrete interval ``[a..b]``.
* If ``list``: A random value will be picked per image from that
list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(N,)`` values -- one per image.
nb_cols : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of columns of the checkerboard. Analogous to `nb_rows`.
"""
def __init__(self, nb_rows, nb_cols):
self.grid = RegularGridMaskGen(nb_rows=nb_rows,
nb_cols=nb_cols,
alpha=1)
@property
def nb_rows(self):
"""Get the number of rows of the checkerboard grid.
Added in 0.4.0.
Returns
-------
int
The number of rows.
"""
return self.grid.nb_rows
@property
def nb_cols(self):
"""Get the number of columns of the checkerboard grid.
Added in 0.4.0.
Returns
-------
int
The number of columns.
"""
return self.grid.nb_cols
def draw_masks(self, batch, random_state=None):
"""
See :func:`~imgaug.augmenters.blend.IBatchwiseMaskGenerator.draw_masks`.
Added in 0.4.0.
"""
# pylint: disable=protected-access
random_state = iarandom.RNG.create_if_not_rng_(random_state)
shapes = batch.get_rowwise_shapes()
nb_rows, nb_cols, _alpha = self.grid._draw_samples(
len(shapes), random_state=random_state)
return [self.generate_mask(shape, nb_rows_i, nb_cols_i)
for shape, nb_rows_i, nb_cols_i
in zip(shapes, nb_rows, nb_cols)]
@classmethod
def generate_mask(cls, shape, nb_rows, nb_cols):
"""Generate a mask following a checkerboard pattern.
Added in 0.4.0.
Parameters
----------
shape : tuple of int
Height and width of the output mask.
nb_rows : int
Number of rows of the checkerboard pattern.
nb_cols : int
Number of columns of the checkerboard pattern.
Returns
-------
ndarray
``float32`` mask array with same height and width as
``segmap.shape``. Values are in ``[0.0, 1.0]``.
"""
height, width = shape[0:2]
if 0 in (height, width):
return np.zeros((height, width), dtype=np.float32)
nb_rows = min(max(nb_rows, 1), height)
nb_cols = min(max(nb_cols, 1), width)
alphas = np.full((nb_cols,), 1.0, dtype=np.float32)
alphas[::2] = 0.0
alphas = np.tile(alphas[np.newaxis, :], (nb_rows, 1))
alphas[::2, :] = 1.0 - alphas[::2, :]
return RegularGridMaskGen.generate_mask(shape, nb_rows, nb_cols, alphas)
class SegMapClassIdsMaskGen(IBatchwiseMaskGenerator):
"""Generator that produces masks highlighting segmentation map classes.
This class produces for each segmentation map in a batch a mask in which
the locations of a set of provided classes are highlighted (i.e. ``1.0``).
The classes may be provided as a fixed list of class ids or a stochastic
parameter from which class ids will be sampled.
The produced masks are initially of the same height and width as the
segmentation map arrays and later upscaled to the image height and width.
.. note::
Segmentation maps can have multiple channels. If that is the case
then for each position ``(x, y)`` it is sufficient that any class id
in any channel matches one of the desired class ids.
.. note::
This class will produce an ``AssertionError`` if there are no
segmentation maps in a batch.
Added in 0.4.0.
Parameters
----------
class_ids : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
Segmentation map classes to mark in the produced mask.
If `nb_sample_classes` is ``None`` then this is expected to be either
a single ``int`` (always mark this one class id) or a ``list`` of
``int`` s (always mark these class ids).
If `nb_sample_classes` is set, then this parameter will be treated
as a stochastic parameter with the following valid types:
* If ``int``: Exactly that class id will be used for all
segmentation maps.
* If ``tuple`` ``(a, b)``: ``N`` random values will be uniformly
sampled per segmentation map from the discrete interval
``[a..b]`` and used as the class ids.
* If ``list``: ``N`` random values will be picked per segmentation
map from that list and used as the class ids.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(sum(N),)`` values.
``N`` denotes the number of classes to sample per segmentation
map (derived from `nb_sample_classes`) and ``sum(N)`` denotes the
sum of ``N`` s over all segmentation maps.
nb_sample_classes : None or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of class ids to sample (with replacement) per segmentation map.
As sampling happens with replacement, fewer *unique* class ids may be
sampled.
* If ``None``: `class_ids` is expected to be a fixed value of
class ids to be used for all segmentation maps.
* If ``int``: Exactly that many class ids will be sampled for all
segmentation maps.
* If ``tuple`` ``(a, b)``: A random value will be uniformly
sampled per segmentation map from the discrete interval
``[a..b]``.
* If ``list`` or ``int``: A random value will be picked per
segmentation map from that list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(B,)`` values, where ``B`` is the number of
segmentation maps.
"""
# Added in 0.4.0.
def __init__(self, class_ids, nb_sample_classes=None):
if nb_sample_classes is None:
if ia.is_single_integer(class_ids):
class_ids = [class_ids]
assert isinstance(class_ids, list), (
"Expected `class_ids` to be a single integer or a list of "
"integers if `nb_sample_classes` is None. Got type `%s`. "
"Set `nb_sample_classes` to e.g. an integer to enable "
"stochastic parameters for `class_ids`." % (
type(class_ids).__name__,))
self.class_ids = class_ids
self.nb_sample_classes = None
else:
self.class_ids = iap.handle_discrete_param(
class_ids, "class_ids", value_range=(0, None),
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False)
self.nb_sample_classes = iap.handle_discrete_param(
nb_sample_classes, "nb_sample_classes", value_range=(0, None),
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False)
def draw_masks(self, batch, random_state=None):
"""
See :func:`~imgaug.augmenters.blend.IBatchwiseMaskGenerator.draw_masks`.
Added in 0.4.0.
"""
assert batch.segmentation_maps is not None, (
"Can only generate masks for batches that contain segmentation "
"maps, but got a batch without them.")
random_state = iarandom.RNG.create_if_not_rng_(random_state)
class_ids = self._draw_samples(batch.nb_rows,
random_state=random_state)
return [self.generate_mask(segmap, class_ids_i)
for segmap, class_ids_i
in zip(batch.segmentation_maps, class_ids)]
# Added in 0.4.0.
def _draw_samples(self, nb_rows, random_state):
nb_sample_classes = self.nb_sample_classes
if nb_sample_classes is None:
assert isinstance(self.class_ids, list), (
"Expected list got %s." % (type(self.class_ids).__name__,))
return [self.class_ids] * nb_rows
nb_sample_classes = nb_sample_classes.draw_samples(
(nb_rows,), random_state=random_state)
nb_sample_classes = np.clip(nb_sample_classes, 0, None)
class_ids_raw = self.class_ids.draw_samples(
(np.sum(nb_sample_classes),),
random_state=random_state)
class_ids = _split_1d_array_to_list(class_ids_raw, nb_sample_classes)
return class_ids
# TODO this could be simplified to something like:
# segmap.keep_only_classes(class_ids).draw_mask()
@classmethod
def generate_mask(cls, segmap, class_ids):
"""Generate a mask of where the segmentation map has the given classes.
Added in 0.4.0.
Parameters
----------
segmap : imgaug.augmentables.segmap.SegmentationMapsOnImage
The segmentation map for which to generate the mask.
class_ids : iterable of int
IDs of the classes to set to ``1.0``.
For an ``(x, y)`` position, it is enough that *any* channel
at the given location to have one of these class ids to be marked
as ``1.0``.
Returns
-------
ndarray
``float32`` mask array with same height and width as
``segmap.shape``. Values are in ``[0.0, 1.0]``.
"""
mask = np.zeros(segmap.arr.shape[0:2], dtype=bool)
for class_id in class_ids:
# note that segmap has shape (H,W,C), so we max() along C
mask_i = np.any(segmap.arr == class_id, axis=2)
mask = np.logical_or(mask, mask_i)
mask = mask.astype(np.float32)
mask = ia.imresize_single_image(mask, segmap.shape[0:2])
return mask
class BoundingBoxesMaskGen(IBatchwiseMaskGenerator):
"""Generator that produces masks highlighting bounding boxes.
This class produces for each row (i.e. image + bounding boxes) in a batch
a mask in which the inner areas of bounding box rectangles with given
labels are marked (i.e. set to ``1.0``). The labels may be provided as a
fixed list of strings or a stochastic parameter from which labels will be
sampled. If no labels are provided, all bounding boxes will be marked.
A pixel will be set to ``1.0`` if *at least* one bounding box at that
location has one of the requested labels, even if there is *also* one
bounding box at that location with a not requested label.
.. note::
This class will produce an ``AssertionError`` if there are no
bounding boxes in a batch.
Added in 0.4.0.
Parameters
----------
labels : None or str or list of str or imgaug.parameters.StochasticParameter
Labels of bounding boxes to select for.
If `nb_sample_labels` is ``None`` then this is expected to be either
also ``None`` (select all BBs) or a single ``str`` (select BBs with
this one label) or a ``list`` of ``str`` s (always select BBs with
these labels).
If `nb_sample_labels` is set, then this parameter will be treated
as a stochastic parameter with the following valid types:
* If ``None``: Ignore the sampling count and always use all
bounding boxes.
* If ``str``: Exactly that label will be used for all
images.
* If ``list`` of ``str``: ``N`` random values will be picked per
image from that list and used as the labels.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(sum(N),)`` values.
``N`` denotes the number of labels to sample per segmentation
map (derived from `nb_sample_labels`) and ``sum(N)`` denotes the
sum of ``N`` s over all images.
nb_sample_labels : None or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of labels to sample (with replacement) per image.
As sampling happens with replacement, fewer *unique* labels may be
sampled.
* If ``None``: `labels` is expected to also be ``None`` or a fixed
value of labels to be used for all images.
* If ``int``: Exactly that many labels will be sampled for all
images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly
sampled per image from the discrete interval ``[a..b]``.
* If ``list``: A random value will be picked per image from
that list.
* If ``StochasticParameter``: That parameter will be queried once
per batch for ``(B,)`` values, where ``B`` is the number of
images.
"""
# Added in 0.4.0.
def __init__(self, labels=None, nb_sample_labels=None):
if labels is None:
self.labels = None
self.nb_sample_labels = None
elif nb_sample_labels is None:
if ia.is_string(labels):
labels = [labels]
assert isinstance(labels, list), (
"Expected `labels` a single string or a list of "
"strings if `nb_sample_labels` is None. Got type `%s`. "
"Set `nb_sample_labels` to e.g. an integer to enable "
"stochastic parameters for `labels`." % (
type(labels).__name__,))
self.labels = labels
self.nb_sample_labels = None
else:
self.labels = iap.handle_categorical_string_param(labels, "labels")
self.nb_sample_labels = iap.handle_discrete_param(
nb_sample_labels, "nb_sample_labels", value_range=(0, None),
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False)
def draw_masks(self, batch, random_state=None):
"""
See :func:`~imgaug.augmenters.blend.IBatchwiseMaskGenerator.draw_masks`.
Added in 0.4.0.
"""
assert batch.bounding_boxes is not None, (
"Can only generate masks for batches that contain bounding boxes, "
"but got a batch without them.")
random_state = iarandom.RNG.create_if_not_rng_(random_state)
if self.labels is None:
return [self.generate_mask(bbsoi, None)
for bbsoi in batch.bounding_boxes]
labels = self._draw_samples(batch.nb_rows, random_state=random_state)
return [self.generate_mask(bbsoi, labels_i)
for bbsoi, labels_i
in zip(batch.bounding_boxes, labels)]
# Added in 0.4.0.
def _draw_samples(self, nb_rows, random_state):
nb_sample_labels = self.nb_sample_labels
if nb_sample_labels is None:
assert isinstance(self.labels, list), (
"Expected list got %s." % (type(self.labels).__name__,))
return [self.labels] * nb_rows
nb_sample_labels = nb_sample_labels.draw_samples(
(nb_rows,), random_state=random_state)
nb_sample_labels = np.clip(nb_sample_labels, 0, None)
labels_raw = self.labels.draw_samples(
(np.sum(nb_sample_labels),),
random_state=random_state)
labels = _split_1d_array_to_list(labels_raw, nb_sample_labels)
return labels
# TODO this could be simplified to something like
# bbsoi.only_labels(labels).draw_mask()
@classmethod
def generate_mask(cls, bbsoi, labels):
"""Generate a mask of the areas of bounding boxes with given labels.
Added in 0.4.0.
Parameters
----------
bbsoi : imgaug.augmentables.bbs.BoundingBoxesOnImage
The bounding boxes for which to generate the mask.
labels : None or iterable of str
Labels of the bounding boxes to set to ``1.0``.
For an ``(x, y)`` position, it is enough that *any* bounding box
at the given location has one of the labels.
If this is ``None``, all bounding boxes will be marked.
Returns
-------
ndarray
``float32`` mask array with same height and width as
``segmap.shape``. Values are in ``[0.0, 1.0]``.
"""
labels = set(labels) if labels is not None else None
height, width = bbsoi.shape[0:2]
mask = np.zeros((height, width), dtype=np.float32)
for bb in bbsoi:
if labels is None or bb.label in labels:
x1 = min(max(int(bb.x1), 0), width)
y1 = min(max(int(bb.y1), 0), height)
x2 = min(max(int(bb.x2), 0), width)
y2 = min(max(int(bb.y2), 0), height)
if x1 < x2 and y1 < y2:
mask[y1:y2, x1:x2] = 1.0
return mask
class InvertMaskGen(IBatchwiseMaskGenerator):
"""Generator that inverts the outputs of other mask generators.
This class receives batches and calls for each row (i.e. image)
a child mask generator to produce a mask. That mask is then inverted
for ``p%`` of all rows, i.e. converted to ``1.0 - mask``.
Added in 0.4.0.
Parameters
----------
p : bool or float or imgaug.parameters.StochasticParameter, optional
Probability of inverting each mask produced by the other mask
generator.
child : IBatchwiseMaskGenerator
The other mask generator to invert.
"""
# Added in 0.4.0.
def __init__(self, p, child):
self.p = iap.handle_probability_param(p, "p")
self.child = child
def draw_masks(self, batch, random_state=None):
"""
See :func:`~imgaug.augmenters.blend.IBatchwiseMaskGenerator.draw_masks`.
Added in 0.4.0.
"""
random_state = iarandom.RNG.create_if_not_rng_(random_state)
masks = self.child.draw_masks(batch, random_state=random_state)
p = self.p.draw_samples(len(masks), random_state=random_state)
for mask, p_i in zip(masks, p):
if p_i >= 0.5:
mask[...] = 1.0 - mask
return masks
@ia.deprecated(alt_func="Alpha",
comment="Alpha is deprecated. "
"Use BlendAlpha instead. "
"The order of parameters is the same. "
"Parameter 'first' was renamed to 'foreground'. "
"Parameter 'second' was renamed to 'background'.")
def Alpha(factor=0, first=None, second=None, per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
"""See :class:`BlendAlpha`.
Deprecated since 0.4.0.
"""
# pylint: disable=invalid-name
return BlendAlpha(
factor=factor,
foreground=first,
background=second,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
@ia.deprecated(alt_func="AlphaElementwise",
comment="AlphaElementwise is deprecated. "
"Use BlendAlphaElementwise instead. "
"The order of parameters is the same. "
"Parameter 'first' was renamed to 'foreground'. "
"Parameter 'second' was renamed to 'background'.")
def AlphaElementwise(factor=0, first=None, second=None, per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
"""See :class:`BlendAlphaElementwise`.
Deprecated since 0.4.0.
"""
# pylint: disable=invalid-name
return BlendAlphaElementwise(
factor=factor,
foreground=first,
background=second,
per_channel=per_channel,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
@ia.deprecated(alt_func="BlendAlphaSimplexNoise",
comment="SimplexNoiseAlpha is deprecated. "
"Use BlendAlphaSimplexNoise instead. "
"The order of parameters is the same. "
"Parameter 'first' was renamed to 'foreground'. "
"Parameter 'second' was renamed to 'background'.")
def SimplexNoiseAlpha(first=None, second=None, per_channel=False,
size_px_max=(2, 16), upscale_method=None,
iterations=(1, 3), aggregation_method="max",
sigmoid=True, sigmoid_thresh=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
"""See :class:`BlendAlphaSimplexNoise`.
Deprecated since 0.4.0.
"""
# pylint: disable=invalid-name
return BlendAlphaSimplexNoise(
foreground=first,
background=second,
per_channel=per_channel,
size_px_max=size_px_max,
upscale_method=upscale_method,
iterations=iterations,
aggregation_method=aggregation_method,
sigmoid=sigmoid,
sigmoid_thresh=sigmoid_thresh,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
@ia.deprecated(alt_func="BlendAlphaFrequencyNoise",
comment="FrequencyNoiseAlpha is deprecated. "
"Use BlendAlphaFrequencyNoise instead. "
"The order of parameters is the same. "
"Parameter 'first' was renamed to 'foreground'. "
"Parameter 'second' was renamed to 'background'.")
def FrequencyNoiseAlpha(exponent=(-4, 4), first=None, second=None,
per_channel=False, size_px_max=(4, 16),
upscale_method=None,
iterations=(1, 3), aggregation_method=["avg", "max"],
sigmoid=0.5, sigmoid_thresh=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
"""See :class:`BlendAlphaFrequencyNoise`.
Deprecated since 0.4.0.
"""
# pylint: disable=invalid-name, dangerous-default-value
return BlendAlphaFrequencyNoise(
exponent=exponent,
foreground=first,
background=second,
per_channel=per_channel,
size_px_max=size_px_max,
upscale_method=upscale_method,
iterations=iterations,
aggregation_method=aggregation_method,
sigmoid=sigmoid,
sigmoid_thresh=sigmoid_thresh,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
================================================
FILE: imgaug/augmenters/blur.py
================================================
"""
Augmenters that blur images.
List of augmenters:
* :class:`GaussianBlur`
* :class:`AverageBlur`
* :class:`MedianBlur`
* :class:`BilateralBlur`
* :class:`MotionBlur`
* :class:`MeanShiftBlur`
"""
from __future__ import print_function, division, absolute_import
import numpy as np
from scipy import ndimage
import cv2
import six.moves as sm
import imgaug as ia
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from . import convolutional as iaa_convolutional
from .. import parameters as iap
from .. import dtypes as iadt
# TODO add border mode, cval
def blur_gaussian_(image, sigma, ksize=None, backend="auto", eps=1e-3):
"""Blur an image using gaussian blurring in-place.
This operation *may* change the input image in-place.
**Supported dtypes**:
if (backend="auto"):
* ``uint8``: yes; fully tested (1)
* ``uint16``: yes; tested (1)
* ``uint32``: yes; tested (2)
* ``uint64``: yes; tested (2)
* ``int8``: yes; tested (1)
* ``int16``: yes; tested (1)
* ``int32``: yes; tested (1)
* ``int64``: yes; tested (2)
* ``float16``: yes; tested (1)
* ``float32``: yes; tested (1)
* ``float64``: yes; tested (1)
* ``float128``: no
* ``bool``: yes; tested (1)
- (1) Handled by ``cv2``. See ``backend="cv2"``.
- (2) Handled by ``scipy``. See ``backend="scipy"``.
if (backend="cv2"):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (2)
* ``uint64``: no (3)
* ``int8``: yes; tested (4)
* ``int16``: yes; tested
* ``int32``: yes; tested (5)
* ``int64``: no (6)
* ``float16``: yes; tested (7)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (8)
* ``bool``: yes; tested (1)
- (1) Mapped internally to ``float32``. Otherwise causes
``TypeError: src data type = 0 is not supported``.
- (2) Causes ``TypeError: src data type = 6 is not supported``.
- (3) Causes ``cv2.error: OpenCV(3.4.5) (...)/filter.cpp:2957:
error: (-213:The function/feature is not implemented)
Unsupported combination of source format (=4), and buffer
format (=5) in function 'getLinearRowFilter'``.
- (4) Mapped internally to ``int16``. Otherwise causes
``cv2.error: OpenCV(3.4.5) (...)/filter.cpp:2957: error:
(-213:The function/feature is not implemented) Unsupported
combination of source format (=1), and buffer format (=5)
in function 'getLinearRowFilter'``.
- (5) Mapped internally to ``float64``. Otherwise causes
``cv2.error: OpenCV(3.4.5) (...)/filter.cpp:2957: error:
(-213:The function/feature is not implemented) Unsupported
combination of source format (=4), and buffer format (=5)
in function 'getLinearRowFilter'``.
- (6) Causes ``cv2.error: OpenCV(3.4.5) (...)/filter.cpp:2957:
error: (-213:The function/feature is not implemented)
Unsupported combination of source format (=4), and buffer
format (=5) in function 'getLinearRowFilter'``.
- (7) Mapped internally to ``float32``. Otherwise causes
``TypeError: src data type = 23 is not supported``.
- (8) Causes ``TypeError: src data type = 13 is not supported``.
if (backend="scipy"):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested (1)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (2)
* ``bool``: yes; tested (3)
- (1) Mapped internally to ``float32``. Otherwise causes
``RuntimeError: array type dtype('float16') not supported``.
- (2) Causes ``RuntimeError: array type dtype('float128') not
supported``.
- (3) Mapped internally to ``float32``. Otherwise too inaccurate.
Parameters
----------
image : numpy.ndarray
The image to blur. Expected to be of shape ``(H, W)`` or ``(H, W, C)``.
sigma : number
Standard deviation of the gaussian blur. Larger numbers result in
more large-scale blurring, which is overall slower than small-scale
blurring.
ksize : None or int, optional
Size in height/width of the gaussian kernel. This argument is only
understood by the ``cv2`` backend. If it is set to ``None``, an
appropriate value for `ksize` will automatically be derived from
`sigma`. The value is chosen tighter for larger sigmas to avoid as
much as possible very large kernel sizes and therey improve
performance.
backend : {'auto', 'cv2', 'scipy'}, optional
Backend library to use. If ``auto``, then the likely best library
will be automatically picked per image. That is usually equivalent
to ``cv2`` (OpenCV) and it will fall back to ``scipy`` for datatypes
not supported by OpenCV.
eps : number, optional
A threshold used to decide whether `sigma` can be considered zero.
Returns
-------
numpy.ndarray
The blurred image. Same shape and dtype as the input.
(Input image *might* have been altered in-place.)
"""
if image.size == 0:
return image
if sigma < eps:
return image
iadt.gate_dtypes_strs(
{image.dtype},
allowed="bool uint8 uint16 uint32 "
"int8 int16 int32 int64 "
"uint64 "
"float16 float32 float64",
disallowed="float128",
augmenter=None
)
dts_not_supported_by_cv2 = iadt._convert_dtype_strs_to_types(
"uint32 uint64 int64 float128"
)
backend_to_use = backend
if backend == "auto":
backend_to_use = (
"cv2"
if image.dtype not in dts_not_supported_by_cv2
else "scipy")
elif backend == "cv2":
assert image.dtype not in dts_not_supported_by_cv2, (
"Requested 'cv2' backend, but provided %s input image, which "
"cannot be handled by that backend. Choose a different "
"backend or set backend to 'auto' or use a different "
"datatype." % (
image.dtype.name,))
elif backend == "scipy":
# can handle all dtypes that were allowed in gate_dtypes()
pass
if backend_to_use == "scipy":
image = _blur_gaussian_scipy_(image, sigma, ksize)
else:
image = _blur_gaussian_cv2(image, sigma, ksize)
return image
# Added in 0.5.0.
def _blur_gaussian_scipy_(image, sigma, ksize):
dtype = image.dtype
if dtype.kind == "b":
# We convert bool to float32 here, because gaussian_filter()
# seems to only return True when the underlying value is
# approximately 1.0, not when it is above 0.5. So we do that
# here manually. cv2 does not support bool for gaussian blur.
image = image.astype(np.float32, copy=False)
elif dtype == iadt._FLOAT16_DTYPE:
image = image.astype(np.float32, copy=False)
# gaussian_filter() has no ksize argument
# TODO it does have a truncate argument that truncates at x
# standard deviations -- maybe can be used similarly to ksize
if ksize is not None:
ia.warn(
"Requested 'scipy' backend or picked it automatically by "
"backend='auto' n blur_gaussian_(), but also provided "
"'ksize' argument, which is not understood by that "
"backend and will be ignored.")
# Note that while gaussian_filter can be applied to all channels
# at the same time, that should not be done here, because then
# the blurring would also happen across channels (e.g. red values
# might be mixed with blue values in RGB)
if image.ndim == 2:
image[:, :] = ndimage.gaussian_filter(image[:, :], sigma,
mode="mirror")
else:
nb_channels = image.shape[2]
for channel in sm.xrange(nb_channels):
image[:, :, channel] = ndimage.gaussian_filter(
image[:, :, channel], sigma, mode="mirror")
if dtype.kind == "b":
image = image > 0.5
elif dtype != image.dtype:
image = iadt.restore_dtypes_(image, dtype)
return image
# Added in 0.5.0.
def _blur_gaussian_cv2(image, sigma, ksize):
dtype = image.dtype
if dtype.kind == "b":
image = image.astype(np.float32, copy=False)
elif dtype == iadt._FLOAT16_DTYPE:
image = image.astype(np.float32, copy=False)
elif dtype == iadt._INT8_DTYPE:
image = image.astype(np.int16, copy=False)
elif dtype == iadt._INT32_DTYPE:
image = image.astype(np.float64, copy=False)
# ksize here is derived from the equation to compute sigma based
# on ksize, see
# https://docs.opencv.org/3.1.0/d4/d86/group__imgproc__filter.html
# -> cv::getGaussianKernel()
# example values:
# sig = 0.1 -> ksize = -1.666
# sig = 0.5 -> ksize = 0.9999
# sig = 1.0 -> ksize = 1.0
# sig = 2.0 -> ksize = 11.0
# sig = 3.0 -> ksize = 17.666
# ksize = ((sig - 0.8)/0.3 + 1)/0.5 + 1
if ksize is None:
ksize = _compute_gaussian_blur_ksize(sigma)
else:
assert ia.is_single_integer(ksize), (
"Expected 'ksize' argument to be a number, "
"got %s." % (type(ksize),))
ksize = ksize + 1 if ksize % 2 == 0 else ksize
image_warped = image
if ksize > 0:
# works with >512 channels
# normalization not required here
# dst seems to not help here
image_warped = cv2.GaussianBlur(
image,
(ksize, ksize),
sigmaX=sigma,
sigmaY=sigma,
borderType=cv2.BORDER_REFLECT_101
)
if image_warped.ndim == 2 and image.ndim == 3:
image_warped = image_warped[..., np.newaxis]
if dtype.kind == "b":
image_warped = image_warped > 0.5
elif dtype != image.dtype:
image_warped = iadt.restore_dtypes_(image_warped, dtype)
return image_warped
def _compute_gaussian_blur_ksize(sigma):
if sigma < 3.0:
ksize = 3.3 * sigma # 99% of weight
elif sigma < 5.0:
ksize = 2.9 * sigma # 97% of weight
else:
ksize = 2.6 * sigma # 95% of weight
# we use 5x5 here as the minimum size as that simplifies
# comparisons with gaussian_filter() in the tests
# TODO reduce this to 3x3
ksize = int(max(ksize, 5))
if ksize % 2 == 0:
ksize += 1
return ksize
def blur_avg_(image, k):
"""Blur an image in-place by computing averages over local neighbourhoods.
This operation *may* change the input image in-place.
The padding behaviour around the image borders is cv2's
``BORDER_REFLECT_101``.
Added in 0.5.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested (3)
* ``int16``: yes; tested
* ``int32``: no (4)
* ``int64``: no (5)
* ``float16``: yes; tested (6)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no
* ``bool``: yes; tested (7)
- (1) rejected by ``cv2.blur()``
- (2) loss of resolution in ``cv2.blur()`` (result is ``int32``)
- (3) ``int8`` is mapped internally to ``int16``, ``int8`` itself
leads to cv2 error "Unsupported combination of source format
(=1), and buffer format (=4) in function 'getRowSumFilter'" in
``cv2``
- (4) results too inaccurate
- (5) loss of resolution in ``cv2.blur()`` (result is ``int32``)
- (6) ``float16`` is mapped internally to ``float32``
- (7) ``bool`` is mapped internally to ``float32``
Parameters
----------
image : numpy.ndarray
The image to blur. Expected to be of shape ``(H, W)`` or ``(H, W, C)``.
k : int or tuple of int
Kernel size to use. A single ``int`` will lead to an ``k x k``
kernel. Otherwise a ``tuple`` of two ``int`` ``(height, width)``
is expected.
Returns
-------
numpy.ndarray
The blurred image. Same shape and dtype as the input.
(Input image *might* have been altered in-place.)
"""
if isinstance(k, tuple):
k_height, k_width = k
else:
k_height, k_width = k, k
shape = image.shape
if 0 in shape:
return image
if k_height <= 0 or k_width <= 0 or (k_height, k_width) == (1, 1):
return image
iadt.gate_dtypes_strs(
{image.dtype},
allowed="bool uint8 uint16 int8 int16 float16 float32 float64",
disallowed="uint32 uint64 int32 int64 float128"
)
input_dtype = image.dtype
if image.dtype in {iadt._BOOL_DTYPE, iadt._FLOAT16_DTYPE}:
image = image.astype(np.float32, copy=False)
elif image.dtype == iadt._INT8_DTYPE:
image = image.astype(np.int16, copy=False)
input_ndim = len(shape)
if input_ndim == 2 or shape[-1] <= 512:
image = _normalize_cv2_input_arr_(image)
image_aug = cv2.blur(
image,
(k_width, k_height),
dst=image
)
# cv2.blur() removes channel axis for single-channel images
if input_ndim == 3 and image_aug.ndim == 2:
image_aug = image_aug[..., np.newaxis]
else:
# TODO this is quite inefficient
# handling more than 512 channels in cv2.blur()
channels = [
cv2.blur(
_normalize_cv2_input_arr_(image[..., c]),
(k_width, k_height)
)
for c in sm.xrange(shape[-1])
]
image_aug = np.stack(channels, axis=-1)
if input_dtype.kind == "b":
image_aug = image_aug > 0.5
elif input_dtype in {iadt._INT8_DTYPE, iadt._FLOAT16_DTYPE}:
image_aug = iadt.restore_dtypes_(image_aug, input_dtype)
return image_aug
def blur_mean_shift_(image, spatial_window_radius, color_window_radius):
"""Apply a pyramidic mean shift filter to the input image in-place.
This produces an output image that has similarity with one modified by
a bilateral filter. That is different from mean shift *segmentation*,
which averages the colors in segments found by mean shift clustering.
This function is a thin wrapper around ``cv2.pyrMeanShiftFiltering``.
.. note::
This function does *not* change the image's colorspace to ``RGB``
before applying the mean shift filter. A non-``RGB`` colorspace will
hence influence the results.
.. note::
This function is quite slow.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no (1)
* ``uint32``: no (1)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (1)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) Not supported by ``cv2.pyrMeanShiftFiltering``.
Parameters
----------
image : ndarray
``(H,W)`` or ``(H,W,1)`` or ``(H,W,3)`` image to blur.
Images with no or one channel will be temporarily tiled to have
three channels.
spatial_window_radius : number
Spatial radius for pixels that are assumed to be similar.
color_window_radius : number
Color radius for pixels that are assumed to be similar.
Returns
-------
ndarray
Blurred input image. Same shape and dtype as the input.
(Input image *might* have been altered in-place.)
"""
if 0 in image.shape[0:2]:
return image
# opencv method only supports uint8
iadt.allow_only_uint8({image.dtype})
shape_is_hw = (image.ndim == 2)
shape_is_hw1 = (image.ndim == 3 and image.shape[-1] == 1)
shape_is_hw3 = (image.ndim == 3 and image.shape[-1] == 3)
assert shape_is_hw or shape_is_hw1 or shape_is_hw3, (
"Expected (H,W) or (H,W,1) or (H,W,3) image, "
"got shape %s." % (image.shape,))
# opencv method only supports (H,W,3), so we have to tile here for (H,W)
# and (H,W,1)
if shape_is_hw:
image = np.tile(image[..., np.newaxis], (1, 1, 3))
elif shape_is_hw1:
image = np.tile(image, (1, 1, 3))
spatial_window_radius = max(spatial_window_radius, 0)
color_window_radius = max(color_window_radius, 0)
image = _normalize_cv2_input_arr_(image)
image = cv2.pyrMeanShiftFiltering(
image,
sp=spatial_window_radius,
sr=color_window_radius,
dst=image)
if shape_is_hw:
image = image[..., 0]
elif shape_is_hw1:
image = image[..., 0:1]
return image
# TODO offer different values for sigma on x/y-axis, supported by cv2 but not
# by scipy
# TODO add channelwise flag - channelwise=False would be supported by scipy
class GaussianBlur(meta.Augmenter):
"""Augmenter to blur images using gaussian kernels.
**Supported dtypes**:
See ``~imgaug.augmenters.blur.blur_gaussian_(backend="auto")``.
Parameters
----------
sigma : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Standard deviation of the gaussian kernel.
Values in the range ``0.0`` (no blur) to ``3.0`` (strong blur) are
common.
* If a single ``float``, that value will always be used as the
standard deviation.
* If a tuple ``(a, b)``, then a random value from the interval
``[a, b]`` will be picked per image.
* If a list, then a random value will be sampled per image from
that list.
* If a ``StochasticParameter``, then ``N`` samples will be drawn
from that parameter per ``N`` input images.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.GaussianBlur(sigma=1.5)
Blur all images using a gaussian kernel with a standard deviation of
``1.5``.
>>> aug = iaa.GaussianBlur(sigma=(0.0, 3.0))
Blur images using a gaussian kernel with a random standard deviation
sampled uniformly (per image) from the interval ``[0.0, 3.0]``.
"""
def __init__(self, sigma=(0.0, 3.0),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(GaussianBlur, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.sigma = iap.handle_continuous_param(
sigma, "sigma", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True)
# epsilon value to estimate whether sigma is sufficently above 0 to
# apply the blur
self.eps = 1e-3
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
samples = self.sigma.draw_samples((nb_images,),
random_state=random_state)
for image, sig in zip(images, samples):
image[...] = blur_gaussian_(image, sigma=sig, eps=self.eps)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.sigma]
class AverageBlur(meta.Augmenter):
"""Blur an image by computing simple means over neighbourhoods.
The padding behaviour around the image borders is cv2's
``BORDER_REFLECT_101``.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested (3)
* ``int16``: yes; tested
* ``int32``: no (4)
* ``int64``: no (5)
* ``float16``: yes; tested (6)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no
* ``bool``: yes; tested (7)
- (1) rejected by ``cv2.blur()``
- (2) loss of resolution in ``cv2.blur()`` (result is ``int32``)
- (3) ``int8`` is mapped internally to ``int16``, ``int8`` itself
leads to cv2 error "Unsupported combination of source format
(=1), and buffer format (=4) in function 'getRowSumFilter'" in
``cv2``
- (4) results too inaccurate
- (5) loss of resolution in ``cv2.blur()`` (result is ``int32``)
- (6) ``float16`` is mapped internally to ``float32``
- (7) ``bool`` is mapped internally to ``float32``
Parameters
----------
k : int or tuple of int or tuple of tuple of int or imgaug.parameters.StochasticParameter or tuple of StochasticParameter, optional
Kernel size to use.
* If a single ``int``, then that value will be used for the height
and width of the kernel.
* If a tuple of two ``int`` s ``(a, b)``, then the kernel size will
be sampled from the interval ``[a..b]``.
* If a tuple of two tuples of ``int`` s ``((a, b), (c, d))``,
then per image a random kernel height will be sampled from the
interval ``[a..b]`` and a random kernel width will be sampled
from the interval ``[c..d]``.
* If a ``StochasticParameter``, then ``N`` samples will be drawn
from that parameter per ``N`` input images, each representing
the kernel size for the n-th image.
* If a tuple ``(a, b)``, where either ``a`` or ``b`` is a tuple,
then ``a`` and ``b`` will be treated according to the rules
above. This leads to different values for height and width of
the kernel.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AverageBlur(k=5)
Blur all images using a kernel size of ``5x5``.
>>> aug = iaa.AverageBlur(k=(2, 5))
Blur images using a varying kernel size, which is sampled (per image)
uniformly from the interval ``[2..5]``.
>>> aug = iaa.AverageBlur(k=((5, 7), (1, 3)))
Blur images using a varying kernel size, which's height is sampled
(per image) uniformly from the interval ``[5..7]`` and which's width is
sampled (per image) uniformly from ``[1..3]``.
"""
def __init__(self, k=(1, 7),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AverageBlur, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO replace this by iap.handle_discrete_kernel_size()
self.mode = "single"
if ia.is_single_number(k):
self.k = iap.Deterministic(int(k))
elif ia.is_iterable(k):
assert len(k) == 2, (
"Expected iterable 'k' to contain exactly 2 entries, "
"got %d." % (len(k),))
if all([ia.is_single_number(ki) for ki in k]):
self.k = iap.DiscreteUniform(int(k[0]), int(k[1]))
elif all([isinstance(ki, iap.StochasticParameter) for ki in k]):
self.mode = "two"
self.k = (k[0], k[1])
else:
k_tuple = [None, None]
if ia.is_single_number(k[0]):
k_tuple[0] = iap.Deterministic(int(k[0]))
elif (ia.is_iterable(k[0])
and all([ia.is_single_number(ki) for ki in k[0]])):
k_tuple[0] = iap.DiscreteUniform(int(k[0][0]),
int(k[0][1]))
else:
raise Exception(
"k[0] expected to be int or tuple of two ints, "
"got %s" % (type(k[0]),))
if ia.is_single_number(k[1]):
k_tuple[1] = iap.Deterministic(int(k[1]))
elif (ia.is_iterable(k[1])
and all([ia.is_single_number(ki) for ki in k[1]])):
k_tuple[1] = iap.DiscreteUniform(int(k[1][0]),
int(k[1][1]))
else:
raise Exception(
"k[1] expected to be int or tuple of two ints, "
"got %s" % (type(k[1]),))
self.mode = "two"
self.k = k_tuple
elif isinstance(k, iap.StochasticParameter):
self.k = k
else:
raise Exception(
"Expected int, tuple/list with 2 entries or "
"StochasticParameter. Got %s." % (type(k),))
self.k = iap._wrap_leafs_of_param_in_prefetchers(
self.k, iap._NB_PREFETCH
)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
if self.mode == "single":
samples = self.k.draw_samples((nb_images,),
random_state=random_state)
samples = (samples, samples)
else:
rss = random_state.duplicate(2)
samples = (
self.k[0].draw_samples((nb_images,), random_state=rss[0]),
self.k[1].draw_samples((nb_images,), random_state=rss[1]),
)
gen = enumerate(zip(images, samples[0], samples[1]))
for i, (image, ksize_h, ksize_w) in gen:
batch.images[i] = blur_avg_(image, (ksize_h, ksize_w))
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.k]
class MedianBlur(meta.Augmenter):
"""Blur an image by computing median values over neighbourhoods.
Median blurring can be used to remove small dirt from images.
At larger kernel sizes, its effects have some similarity with Superpixels.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: ?
* ``uint32``: ?
* ``uint64``: ?
* ``int8``: ?
* ``int16``: ?
* ``int32``: ?
* ``int64``: ?
* ``float16``: ?
* ``float32``: ?
* ``float64``: ?
* ``float128``: ?
* ``bool``: ?
Parameters
----------
k : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Kernel size.
* If a single ``int``, then that value will be used for the
height and width of the kernel. Must be an odd value.
* If a tuple of two ints ``(a, b)``, then the kernel size will be
an odd value sampled from the interval ``[a..b]``. ``a`` and
``b`` must both be odd values.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then ``N`` samples will be drawn
from that parameter per ``N`` input images, each representing
the kernel size for the nth image. Expected to be discrete. If
a sampled value is not odd, then that value will be increased
by ``1``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MedianBlur(k=5)
Blur all images using a kernel size of ``5x5``.
>>> aug = iaa.MedianBlur(k=(3, 7))
Blur images using varying kernel sizes, which are sampled uniformly from
the interval ``[3..7]``. Only odd values will be sampled, i.e. ``3``
or ``5`` or ``7``.
"""
def __init__(self, k=(1, 7),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MedianBlur, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO replace this by iap.handle_discrete_kernel_size()
self.k = iap.handle_discrete_param(
k, "k", value_range=(1, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
if ia.is_single_integer(k):
assert k % 2 != 0, (
"Expected k to be odd, got %d. Add or subtract 1." % (
int(k),))
elif ia.is_iterable(k):
assert all([ki % 2 != 0 for ki in k]), (
"Expected all values in iterable k to be odd, but at least "
"one was not. Add or subtract 1 to/from that value.")
self.k = iap._wrap_leafs_of_param_in_prefetchers(
self.k, iap._NB_PREFETCH
)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
samples = self.k.draw_samples((nb_images,), random_state=random_state)
for i, (image, ksize) in enumerate(zip(images, samples)):
has_zero_sized_axes = (image.size == 0)
if ksize > 1 and not has_zero_sized_axes:
ksize = ksize + 1 if ksize % 2 == 0 else ksize
if image.ndim == 2 or image.shape[-1] <= 512:
image_aug = cv2.medianBlur(
_normalize_cv2_input_arr_(image), ksize)
# cv2.medianBlur() removes channel axis for single-channel
# images
if image_aug.ndim == 2:
image_aug = image_aug[..., np.newaxis]
else:
# TODO this is quite inefficient
# handling more than 512 channels in cv2.medainBlur()
channels = [
cv2.medianBlur(
_normalize_cv2_input_arr_(image[..., c]), ksize)
for c in sm.xrange(image.shape[-1])
]
image_aug = np.stack(channels, axis=-1)
batch.images[i] = image_aug
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.k]
# TODO tests
class BilateralBlur(meta.Augmenter):
"""Blur/Denoise an image using a bilateral filter.
Bilateral filters blur homogenous and textured areas, while trying to
preserve edges.
See
http://docs.opencv.org/2.4/modules/imgproc/doc/filtering.html#bilateralfilter
for more information regarding the parameters.
**Supported dtypes**:
* ``uint8``: yes; not tested
* ``uint16``: ?
* ``uint32``: ?
* ``uint64``: ?
* ``int8``: ?
* ``int16``: ?
* ``int32``: ?
* ``int64``: ?
* ``float16``: ?
* ``float32``: ?
* ``float64``: ?
* ``float128``: ?
* ``bool``: ?
Parameters
----------
d : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Diameter of each pixel neighborhood with value range ``[1 .. inf)``.
High values for `d` lead to significantly worse performance. Values
equal or less than ``10`` seem to be good. Use ``<5`` for real-time
applications.
* If a single ``int``, then that value will be used for the
diameter.
* If a tuple of two ``int`` s ``(a, b)``, then the diameter will
be a value sampled from the interval ``[a..b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then ``N`` samples will be drawn
from that parameter per ``N`` input images, each representing
the diameter for the n-th image. Expected to be discrete.
sigma_color : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Filter sigma in the color space with value range ``[1, inf)``. A
large value of the parameter means that farther colors within the
pixel neighborhood (see `sigma_space`) will be mixed together,
resulting in larger areas of semi-equal color.
* If a single ``int``, then that value will be used for the
diameter.
* If a tuple of two ``int`` s ``(a, b)``, then the diameter will
be a value sampled from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then ``N`` samples will be drawn
from that parameter per ``N`` input images, each representing
the diameter for the n-th image. Expected to be discrete.
sigma_space : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Filter sigma in the coordinate space with value range ``[1, inf)``. A
large value of the parameter means that farther pixels will influence
each other as long as their colors are close enough (see
`sigma_color`).
* If a single ``int``, then that value will be used for the
diameter.
* If a tuple of two ``int`` s ``(a, b)``, then the diameter will
be a value sampled from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then ``N`` samples will be drawn
from that parameter per ``N`` input images, each representing
the diameter for the n-th image. Expected to be discrete.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.BilateralBlur(
>>> d=(3, 10), sigma_color=(10, 250), sigma_space=(10, 250))
Blur all images using a bilateral filter with a `max distance` sampled
uniformly from the interval ``[3, 10]`` and wide ranges for `sigma_color`
and `sigma_space`.
"""
def __init__(self, d=(1, 9), sigma_color=(10, 250), sigma_space=(10, 250),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=invalid-name
super(BilateralBlur, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.d = iap.handle_discrete_param(
d, "d", value_range=(1, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
self.sigma_color = iap.handle_continuous_param(
sigma_color, "sigma_color", value_range=(1, None),
tuple_to_uniform=True, list_to_choice=True)
self.sigma_space = iap.handle_continuous_param(
sigma_space, "sigma_space", value_range=(1, None),
tuple_to_uniform=True, list_to_choice=True)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
# pylint: disable=invalid-name
if batch.images is None:
return batch
images = batch.images
# Make sure that all images have 3 channels
assert all([image.shape[2] == 3 for image in images]), (
"BilateralBlur can currently only be applied to images with 3 "
"channels. Got channels: %s" % (
[image.shape[2] for image in images],))
nb_images = len(images)
rss = random_state.duplicate(3)
samples_d = self.d.draw_samples((nb_images,), random_state=rss[0])
samples_sigma_color = self.sigma_color.draw_samples(
(nb_images,), random_state=rss[1])
samples_sigma_space = self.sigma_space.draw_samples(
(nb_images,), random_state=rss[2])
gen = enumerate(zip(images, samples_d, samples_sigma_color,
samples_sigma_space))
for i, (image, di, sigma_color_i, sigma_space_i) in gen:
has_zero_sized_axes = (image.size == 0)
if di != 1 and not has_zero_sized_axes:
batch.images[i] = cv2.bilateralFilter(
_normalize_cv2_input_arr_(image),
di, sigma_color_i, sigma_space_i)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.d, self.sigma_color, self.sigma_space]
# TODO add k sizing via float/percentage
class MotionBlur(iaa_convolutional.Convolve):
"""Blur images in a way that fakes camera or object movements.
**Supported dtypes**:
See :class:`~imgaug.augmenters.convolutional.Convolve`.
Parameters
----------
k : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Kernel size to use.
* If a single ``int``, then that value will be used for the height
and width of the kernel.
* If a tuple of two ``int`` s ``(a, b)``, then the kernel size
will be sampled from the interval ``[a..b]``.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then ``N`` samples will be drawn
from that parameter per ``N`` input images, each representing
the kernel size for the n-th image.
angle : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Angle of the motion blur in degrees (clockwise, relative to top center
direction).
* If a number, exactly that value will be used.
* If a tuple ``(a, b)``, a random value from the interval
``[a, b]`` will be uniformly sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
direction : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Forward/backward direction of the motion blur. Lower values towards
``-1.0`` will point the motion blur towards the back (with angle
provided via `angle`). Higher values towards ``1.0`` will point the
motion blur forward. A value of ``0.0`` leads to a uniformly (but
still angled) motion blur.
* If a number, exactly that value will be used.
* If a tuple ``(a, b)``, a random value from the interval
``[a, b]`` will be uniformly sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
Interpolation order to use when rotating the kernel according to
`angle`.
See :func:`~imgaug.augmenters.geometric.Affine.__init__`.
Recommended to be ``0`` or ``1``, with ``0`` being faster, but less
continuous/smooth as `angle` is changed, particularly around multiple
of ``45`` degrees.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MotionBlur(k=15)
Apply motion blur with a kernel size of ``15x15`` pixels to images.
>>> aug = iaa.MotionBlur(k=15, angle=[-45, 45])
Apply motion blur with a kernel size of ``15x15`` pixels and a blur angle
of either ``-45`` or ``45`` degrees (randomly picked per image).
"""
def __init__(self, k=(3, 7), angle=(0, 360), direction=(-1.0, 1.0), order=1,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# TODO allow (1, None) and set to identity matrix if k == 1
k_param = iap.handle_discrete_param(
k, "k", value_range=(3, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
angle_param = iap.handle_continuous_param(
angle, "angle", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
direction_param = iap.handle_continuous_param(
direction, "direction", value_range=(-1.0-1e-6, 1.0+1e-6),
tuple_to_uniform=True, list_to_choice=True)
matrix_gen = _MotionBlurMatrixGenerator(k_param, angle_param,
direction_param, order)
super(MotionBlur, self).__init__(
matrix_gen,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
class _MotionBlurMatrixGenerator(object):
# Added in 0.4.0.
def __init__(self, k, angle, direction, order):
self.k = k
self.angle = angle
self.direction = direction
self.order = order
# Added in 0.4.0.
def __call__(self, _image, nb_channels, random_state):
# avoid cyclic import between blur and geometric
from . import geometric as iaa_geometric
# force discrete for k_sample via int() in case of stochastic
# parameter
k_sample = int(
self.k.draw_sample(random_state=random_state))
angle_sample = self.angle.draw_sample(
random_state=random_state)
direction_sample = self.direction.draw_sample(
random_state=random_state)
k_sample = k_sample if k_sample % 2 != 0 else k_sample + 1
direction_sample = np.clip(direction_sample, -1.0, 1.0)
direction_sample = (direction_sample + 1.0) / 2.0
matrix = np.zeros((k_sample, k_sample), dtype=np.float32)
matrix[:, k_sample//2] = np.linspace(
float(direction_sample),
1.0 - float(direction_sample),
num=k_sample)
rot = iaa_geometric.Affine(rotate=angle_sample, order=self.order)
matrix = (
rot.augment_image(
(matrix * 255).astype(np.uint8)
).astype(np.float32) / 255.0
)
return [matrix/np.sum(matrix)] * nb_channels
# TODO add a per_channel flag?
# TODO make spatial_radius a fraction of the input image size?
class MeanShiftBlur(meta.Augmenter):
"""Apply a pyramidic mean shift filter to each image.
See also :func:`blur_mean_shift_` for details.
This augmenter expects input images of shape ``(H,W)`` or ``(H,W,1)``
or ``(H,W,3)``.
.. note::
This augmenter is quite slow.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.blur.blur_mean_shift_`.
Parameters
----------
spatial_radius : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Spatial radius for pixels that are assumed to be similar.
* If ``number``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly
sampled per image from the interval ``[a, b)``.
* If ``list``: A random value will be sampled from that ``list``
per image.
* If ``StochasticParameter``: The parameter will be queried once
per batch for ``(N,)`` values with ``N`` denoting the number of
images.
color_radius : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Color radius for pixels that are assumed to be similar.
* If ``number``: Exactly that value will be used for all images.
* If ``tuple`` ``(a, b)``: A random value will be uniformly
sampled per image from the interval ``[a, b)``.
* If ``list``: A random value will be sampled from that ``list``
per image.
* If ``StochasticParameter``: The parameter will be queried once
per batch for ``(N,)`` values with ``N`` denoting the number of
images.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MeanShiftBlur()
Create a mean shift blur augmenter.
"""
# Added in 0.4.0.
def __init__(self, spatial_radius=(5.0, 40.0), color_radius=(5.0, 40.0),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MeanShiftBlur, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.spatial_window_radius = iap.handle_continuous_param(
spatial_radius, "spatial_radius",
value_range=(0.01, None), tuple_to_uniform=True,
list_to_choice=True)
self.color_window_radius = iap.handle_continuous_param(
color_radius, "color_radius",
value_range=(0.01, None), tuple_to_uniform=True,
list_to_choice=True)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is not None:
samples = self._draw_samples(batch, random_state)
for i, image in enumerate(batch.images):
batch.images[i] = blur_mean_shift_(
image,
spatial_window_radius=samples[0][i],
color_window_radius=samples[1][i]
)
return batch
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
nb_rows = batch.nb_rows
return (
self.spatial_window_radius.draw_samples((nb_rows,),
random_state=random_state),
self.color_window_radius.draw_samples((nb_rows,),
random_state=random_state)
)
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.spatial_window_radius, self.color_window_radius]
================================================
FILE: imgaug/augmenters/collections.py
================================================
"""Augmenters that are collections of other augmenters.
List of augmenters:
* :class:`RandAugment`
Added in 0.4.0.
"""
from __future__ import print_function, division, absolute_import
import numpy as np
from .. import parameters as iap
from .. import random as iarandom
from . import meta
from . import arithmetic
from . import flip
from . import pillike
from . import size as sizelib
class RandAugment(meta.Sequential):
"""Apply RandAugment to inputs as described in the corresponding paper.
See paper::
Cubuk et al.
RandAugment: Practical automated data augmentation with a reduced
search space
.. note::
The paper contains essentially no hyperparameters for the individual
augmentation techniques. The hyperparameters used here come mostly
from the official code repository, which however seems to only contain
code for CIFAR10 and SVHN, not for ImageNet. So some guesswork was
involved and a few of the hyperparameters were also taken from
https://github.com/ildoonet/pytorch-randaugment/blob/master/RandAugment/augmentations.py .
This implementation deviates from the code repository for all PIL
enhance operations. In the repository these use a factor of
``0.1 + M*1.8/M_max``, which would lead to a factor of ``0.1`` for the
weakest ``M`` of ``M=0``. For e.g. ``Brightness`` that would result in
a basically black image. This definition is fine for AutoAugment (from
where the code and hyperparameters are copied), which optimizes
each transformation's ``M`` individually, but not for RandAugment,
which uses a single fixed ``M``. We hence redefine these
hyperparameters to ``1.0 + S * M * 0.9/M_max``, where ``S`` is
randomly either ``1`` or ``-1``.
We also note that it is not entirely clear which transformations
were used in the ImageNet experiments. The paper lists some
transformations in Figure 2, but names others in the text too (e.g.
crops, flips, cutout). While Figure 2 lists the Identity function,
this transformation seems to not appear in the repository (and in fact,
the function ``randaugment(N, M)`` doesn't seem to exist in the
repository either). So we also make a best guess here about what
transformations might have been used.
.. warning::
This augmenter only works with image data, not e.g. bounding boxes.
The used PIL-based affine transformations are not yet able to
process non-image data. (This augmenter uses PIL-based affine
transformations to ensure that outputs are as similar as possible
to the paper's implementation.)
Added in 0.4.0.
**Supported dtypes**:
minimum of (
:class:`~imgaug.augmenters.flip.Fliplr`,
:class:`~imgaug.augmenters.size.KeepSizeByResize`,
:class:`~imgaug.augmenters.size.Crop`,
:class:`~imgaug.augmenters.meta.Sequential`,
:class:`~imgaug.augmenters.meta.SomeOf`,
:class:`~imgaug.augmenters.meta.Identity`,
:class:`~imgaug.augmenters.pillike.Autocontrast`,
:class:`~imgaug.augmenters.pillike.Equalize`,
:class:`~imgaug.augmenters.arithmetic.Invert`,
:class:`~imgaug.augmenters.pillike.Affine`,
:class:`~imgaug.augmenters.pillike.Posterize`,
:class:`~imgaug.augmenters.pillike.Solarize`,
:class:`~imgaug.augmenters.pillike.EnhanceColor`,
:class:`~imgaug.augmenters.pillike.EnhanceContrast`,
:class:`~imgaug.augmenters.pillike.EnhanceBrightness`,
:class:`~imgaug.augmenters.pillike.EnhanceSharpness`,
:class:`~imgaug.augmenters.arithmetic.Cutout`,
:class:`~imgaug.augmenters.pillike.FilterBlur`,
:class:`~imgaug.augmenters.pillike.FilterSmooth`
)
Parameters
----------
n : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or None, optional
Parameter ``N`` in the paper, i.e. number of transformations to apply.
The paper suggests ``N=2`` for ImageNet.
See also parameter ``n`` in :class:`~imgaug.augmenters.meta.SomeOf`
for more details.
Note that horizontal flips (p=50%) and crops are always applied. This
parameter only determines how many of the other transformations
are applied per image.
m : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or None, optional
Parameter ``M`` in the paper, i.e. magnitude/severity/strength of the
applied transformations in interval ``[0 .. 30]`` with ``M=0`` being
the weakest. The paper suggests for ImageNet ``M=9`` in case of
ResNet-50 and ``M=28`` in case of EfficientNet-B7.
This implementation uses a default value of ``(6, 12)``, i.e. the
value is uniformly sampled per image from the interval ``[6 .. 12]``.
This ensures greater diversity of transformations than using a single
fixed value.
* If ``int``: That value will always be used.
* If ``tuple`` ``(a, b)``: A random value will be uniformly sampled per
image from the discrete interval ``[a .. b]``.
* If ``list``: A random value will be picked from the list per image.
* If ``StochasticParameter``: For ``B`` images in a batch, ``B`` values
will be sampled per augmenter (provided the augmenter is dependent
on the magnitude).
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
The constant value to use when filling in newly created pixels.
See parameter `fillcolor` in
:class:`~imgaug.augmenters.pillike.Affine` for details.
The paper's repository uses an RGB value of ``125, 122, 113``.
This implementation uses a single intensity value of ``128``, which
should work better for cases where input images don't have exactly
``3`` channels or come from a different dataset than used by the
paper.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.RandAugment(n=2, m=9)
Create a RandAugment augmenter similar to the suggested hyperparameters
in the paper.
>>> aug = iaa.RandAugment(m=30)
Create a RandAugment augmenter with maximum magnitude/strength.
>>> aug = iaa.RandAugment(m=(0, 9))
Create a RandAugment augmenter that applies its transformations with a
random magnitude between ``0`` (very weak) and ``9`` (recommended for
ImageNet and ResNet-50). ``m`` is sampled per transformation.
>>> aug = iaa.RandAugment(n=(0, 3))
Create a RandAugment augmenter that applies ``0`` to ``3`` of its
child transformations to images. Horizontal flips (p=50%) and crops are
always applied.
"""
_M_MAX = 30
# according to paper:
# N=2, M=9 is optimal for ImageNet with ResNet-50
# N=2, M=28 is optimal for ImageNet with EfficientNet-B7
# for cval they use [125, 122, 113]
# Added in 0.4.0.
def __init__(self, n=2, m=(6, 12), cval=128,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=invalid-name
seed = seed if random_state == "deprecated" else random_state
rng = iarandom.RNG.create_if_not_rng_(seed)
# we don't limit the value range to 10 here, because the paper
# gives several examples of using more than 10 for M
m = iap.handle_discrete_param(
m, "m", value_range=(0, None),
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False)
self._m = m
self._cval = cval
# The paper says in Appendix A.2.3 "ImageNet", that they actually
# always execute Horizontal Flips and Crops first and only then a
# random selection of the other transformations.
# Hence, we split here into two groups.
# It's not really clear what crop parameters they use, so we
# choose [0..M] here.
initial_augs = self._create_initial_augmenters_list(m)
main_augs = self._create_main_augmenters_list(m, cval)
# assign random state to all child augmenters
for lst in [initial_augs, main_augs]:
for augmenter in lst:
augmenter.random_state = rng
super(RandAugment, self).__init__(
[
meta.Sequential(initial_augs,
seed=rng.derive_rng_()),
meta.SomeOf(n, main_augs, random_order=True,
seed=rng.derive_rng_())
],
seed=rng, name=name,
random_state=random_state, deterministic=deterministic
)
# Added in 0.4.0.
@classmethod
def _create_initial_augmenters_list(cls, m):
# pylint: disable=invalid-name
return [
flip.Fliplr(0.5),
sizelib.KeepSizeByResize(
# assuming that the paper implementation crops M pixels from
# 224px ImageNet images, we crop here a fraction of
# M*(M_max/224)
sizelib.Crop(
percent=iap.Divide(
iap.Uniform(0, m),
224,
elementwise=True),
sample_independently=True,
keep_size=False),
interpolation="linear"
)
]
# Added in 0.4.0.
@classmethod
def _create_main_augmenters_list(cls, m, cval):
# pylint: disable=invalid-name
m_max = cls._M_MAX
def _float_parameter(level, maxval):
maxval_norm = maxval / m_max
return iap.Multiply(level, maxval_norm, elementwise=True)
def _int_parameter(level, maxval):
# paper applies just int(), so we don't round here
return iap.Discretize(_float_parameter(level, maxval),
round=False)
# In the paper's code they use the definition from AutoAugment,
# which is 0.1 + M*1.8/10. But that results in 0.1 for M=0, i.e. for
# Brightness an almost black image, while M=5 would result in an
# unaltered image. For AutoAugment that may be fine, as M is optimized
# for each operation individually, but here we have only one fixed M
# for all operations. Hence, we rather set this to 1.0 +/- M*0.9/10,
# so that M=10 would result in 0.1 or 1.9.
def _enhance_parameter(level):
fparam = _float_parameter(level, 0.9)
return iap.Clip(
iap.Add(1.0, iap.RandomSign(fparam), elementwise=True),
0.1, 1.9
)
def _subtract(a, b):
return iap.Subtract(a, b, elementwise=True)
def _affine(*args, **kwargs):
kwargs["fillcolor"] = cval
if "center" not in kwargs:
kwargs["center"] = (0.0, 0.0)
return pillike.Affine(*args, **kwargs)
_rnd_s = iap.RandomSign
shear_max = np.rad2deg(0.3)
# we don't add vertical flips here, paper is not really clear about
# whether they used them or not
return [
meta.Identity(),
pillike.Autocontrast(cutoff=0),
pillike.Equalize(),
arithmetic.Invert(p=1.0),
# they use Image.rotate() for the rotation, which uses
# the image center as the rotation center
_affine(rotate=_rnd_s(_float_parameter(m, 30)),
center=(0.5, 0.5)),
# paper uses 4 - int_parameter(M, 4)
pillike.Posterize(
nb_bits=_subtract(
8,
iap.Clip(_int_parameter(m, 6), 0, 6)
)
),
# paper uses 256 - int_parameter(M, 256)
pillike.Solarize(
p=1.0,
threshold=iap.Clip(
_subtract(256, _int_parameter(m, 256)),
0, 256
)
),
pillike.EnhanceColor(_enhance_parameter(m)),
pillike.EnhanceContrast(_enhance_parameter(m)),
pillike.EnhanceBrightness(_enhance_parameter(m)),
pillike.EnhanceSharpness(_enhance_parameter(m)),
_affine(shear={"x": _rnd_s(_float_parameter(m, shear_max))}),
_affine(shear={"y": _rnd_s(_float_parameter(m, shear_max))}),
_affine(translate_percent={"x": _rnd_s(_float_parameter(m, 0.33))}),
_affine(translate_percent={"y": _rnd_s(_float_parameter(m, 0.33))}),
# paper code uses 20px on CIFAR (i.e. size 20/32), no information
# on ImageNet values so we just use the same values
arithmetic.Cutout(1,
size=iap.Clip(
_float_parameter(m, 20 / 32), 0, 20 / 32),
squared=True,
fill_mode="constant",
cval=cval),
pillike.FilterBlur(),
pillike.FilterSmooth()
]
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
someof = self[1]
return [someof.n, self._m, self._cval]
================================================
FILE: imgaug/augmenters/color.py
================================================
"""
Augmenters that affect image colors or image colorspaces.
List of augmenters:
* :class:`InColorspace` (deprecated)
* :class:`WithColorspace`
* :class:`WithBrightnessChannels`
* :class:`MultiplyAndAddToBrightness`
* :class:`MultiplyBrightness`
* :class:`AddToBrightness`
* :class:`WithHueAndSaturation`
* :class:`MultiplyHueAndSaturation`
* :class:`MultiplyHue`
* :class:`MultiplySaturation`
* :class:`RemoveSaturation`
* :class:`AddToHueAndSaturation`
* :class:`AddToHue`
* :class:`AddToSaturation`
* :class:`ChangeColorspace`
* :class:`Grayscale`
* :class:`ChangeColorTemperature`
* :class:`KMeansColorQuantization`
* :class:`UniformColorQuantization`
* :class:`Posterize`
"""
from __future__ import print_function, division, absolute_import
from abc import ABCMeta, abstractmethod
import numpy as np
import cv2
import six
import six.moves as sm
import imgaug as ia
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from . import blend
from . import arithmetic
from .. import parameters as iap
from .. import dtypes as iadt
from .. import random as iarandom
# pylint: disable=invalid-name
CSPACE_RGB = "RGB"
CSPACE_BGR = "BGR"
CSPACE_GRAY = "GRAY"
CSPACE_YCrCb = "YCrCb"
CSPACE_HSV = "HSV"
CSPACE_HLS = "HLS"
CSPACE_Lab = "Lab" # aka CIELAB
# TODO add Luv to various color/contrast augmenters as random default choice?
CSPACE_Luv = "Luv" # aka CIE 1976, aka CIELUV
CSPACE_YUV = "YUV" # aka CIE 1960
CSPACE_CIE = "CIE" # aka CIE 1931, aka XYZ in OpenCV
CSPACE_ALL = {CSPACE_RGB, CSPACE_BGR, CSPACE_GRAY, CSPACE_YCrCb,
CSPACE_HSV, CSPACE_HLS, CSPACE_Lab, CSPACE_Luv,
CSPACE_YUV, CSPACE_CIE}
# pylint: enable=invalid-name
def _get_opencv_attr(attr_names):
for attr_name in attr_names:
if hasattr(cv2, attr_name):
return getattr(cv2, attr_name)
ia.warn("Could not find any of the following attributes in cv2: %s. "
"This can cause issues with colorspace transformations." % (
attr_names))
return None
_CSPACE_OPENCV_CONV_VARS = {
# RGB
(CSPACE_RGB, CSPACE_BGR): cv2.COLOR_RGB2BGR,
(CSPACE_RGB, CSPACE_GRAY): cv2.COLOR_RGB2GRAY,
(CSPACE_RGB, CSPACE_YCrCb): _get_opencv_attr(["COLOR_RGB2YCR_CB"]),
(CSPACE_RGB, CSPACE_HSV): cv2.COLOR_RGB2HSV,
(CSPACE_RGB, CSPACE_HLS): cv2.COLOR_RGB2HLS,
(CSPACE_RGB, CSPACE_Lab): _get_opencv_attr(["COLOR_RGB2LAB",
"COLOR_RGB2Lab"]),
(CSPACE_RGB, CSPACE_Luv): cv2.COLOR_RGB2LUV,
(CSPACE_RGB, CSPACE_YUV): cv2.COLOR_RGB2YUV,
(CSPACE_RGB, CSPACE_CIE): cv2.COLOR_RGB2XYZ,
# BGR
(CSPACE_BGR, CSPACE_RGB): cv2.COLOR_BGR2RGB,
(CSPACE_BGR, CSPACE_GRAY): cv2.COLOR_BGR2GRAY,
(CSPACE_BGR, CSPACE_YCrCb): _get_opencv_attr(["COLOR_BGR2YCR_CB"]),
(CSPACE_BGR, CSPACE_HSV): cv2.COLOR_BGR2HSV,
(CSPACE_BGR, CSPACE_HLS): cv2.COLOR_BGR2HLS,
(CSPACE_BGR, CSPACE_Lab): _get_opencv_attr(["COLOR_BGR2LAB",
"COLOR_BGR2Lab"]),
(CSPACE_BGR, CSPACE_Luv): cv2.COLOR_BGR2LUV,
(CSPACE_BGR, CSPACE_YUV): cv2.COLOR_BGR2YUV,
(CSPACE_BGR, CSPACE_CIE): cv2.COLOR_BGR2XYZ,
# GRAY
# YCrCb
(CSPACE_YCrCb, CSPACE_RGB): _get_opencv_attr(["COLOR_YCrCb2RGB",
"COLOR_YCR_CB2RGB"]),
(CSPACE_YCrCb, CSPACE_BGR): _get_opencv_attr(["COLOR_YCrCb2BGR",
"COLOR_YCR_CB2BGR"]),
# HSV
(CSPACE_HSV, CSPACE_RGB): cv2.COLOR_HSV2RGB,
(CSPACE_HSV, CSPACE_BGR): cv2.COLOR_HSV2BGR,
# HLS
(CSPACE_HLS, CSPACE_RGB): cv2.COLOR_HLS2RGB,
(CSPACE_HLS, CSPACE_BGR): cv2.COLOR_HLS2BGR,
# Lab
(CSPACE_Lab, CSPACE_RGB): _get_opencv_attr(["COLOR_Lab2RGB",
"COLOR_LAB2RGB"]),
(CSPACE_Lab, CSPACE_BGR): _get_opencv_attr(["COLOR_Lab2BGR",
"COLOR_LAB2BGR"]),
# Luv
(CSPACE_Luv, CSPACE_RGB): _get_opencv_attr(["COLOR_Luv2RGB",
"COLOR_LUV2RGB"]),
(CSPACE_Luv, CSPACE_BGR): _get_opencv_attr(["COLOR_Luv2BGR",
"COLOR_LUV2BGR"]),
# YUV
(CSPACE_YUV, CSPACE_RGB): cv2.COLOR_YUV2RGB,
(CSPACE_YUV, CSPACE_BGR): cv2.COLOR_YUV2BGR,
# CIE
(CSPACE_CIE, CSPACE_RGB): cv2.COLOR_XYZ2RGB,
(CSPACE_CIE, CSPACE_BGR): cv2.COLOR_XYZ2BGR,
}
# This defines which colorspace pairs will be converted in-place in
# change_colorspace_(). Currently, all colorspaces seem to work fine with
# in-place transformations, which is why they are all set to True.
_CHANGE_COLORSPACE_INPLACE = {
# RGB
(CSPACE_RGB, CSPACE_BGR): True,
(CSPACE_RGB, CSPACE_GRAY): True,
(CSPACE_RGB, CSPACE_YCrCb): True,
(CSPACE_RGB, CSPACE_HSV): True,
(CSPACE_RGB, CSPACE_HLS): True,
(CSPACE_RGB, CSPACE_Lab): True,
(CSPACE_RGB, CSPACE_Luv): True,
(CSPACE_RGB, CSPACE_YUV): True,
(CSPACE_RGB, CSPACE_CIE): True,
# BGR
(CSPACE_BGR, CSPACE_RGB): True,
(CSPACE_BGR, CSPACE_GRAY): True,
(CSPACE_BGR, CSPACE_YCrCb): True,
(CSPACE_BGR, CSPACE_HSV): True,
(CSPACE_BGR, CSPACE_HLS): True,
(CSPACE_BGR, CSPACE_Lab): True,
(CSPACE_BGR, CSPACE_Luv): True,
(CSPACE_BGR, CSPACE_YUV): True,
(CSPACE_BGR, CSPACE_CIE): True,
# GRAY
# YCrCb
(CSPACE_YCrCb, CSPACE_RGB): True,
(CSPACE_YCrCb, CSPACE_BGR): True,
# HSV
(CSPACE_HSV, CSPACE_RGB): True,
(CSPACE_HSV, CSPACE_BGR): True,
# HLS
(CSPACE_HLS, CSPACE_RGB): True,
(CSPACE_HLS, CSPACE_BGR): True,
# Lab
(CSPACE_Lab, CSPACE_RGB): True,
(CSPACE_Lab, CSPACE_BGR): True,
# Luv
(CSPACE_Luv, CSPACE_RGB): True,
(CSPACE_Luv, CSPACE_BGR): True,
# YUV
(CSPACE_YUV, CSPACE_RGB): True,
(CSPACE_YUV, CSPACE_BGR): True,
# CIE
(CSPACE_CIE, CSPACE_RGB): True,
(CSPACE_CIE, CSPACE_BGR): True,
}
def change_colorspace_(image, to_colorspace, from_colorspace=CSPACE_RGB):
"""Change the colorspace of an image inplace.
.. note::
All outputs of this function are `uint8`. For some colorspaces this
may not be optimal.
.. note::
Output grayscale images will still have three channels.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to convert from one colorspace into another.
Usually expected to have shape ``(H,W,3)``.
to_colorspace : str
The target colorspace. See the ``CSPACE`` constants,
e.g. ``imgaug.augmenters.color.CSPACE_RGB``.
from_colorspace : str, optional
The source colorspace. Analogous to `to_colorspace`. Defaults
to ``RGB``.
Returns
-------
ndarray
Image with target colorspace. *Can* be the same array instance as was
originally provided (i.e. changed inplace). Grayscale images will
still have three channels.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> import numpy as np
>>> # fake RGB image
>>> image_rgb = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
>>> image_bgr = iaa.change_colorspace_(np.copy(image_rgb), iaa.CSPACE_BGR)
"""
# some colorspaces here should use image/255.0 according to
# the docs, but at least for conversion to grayscale that
# results in errors, ie uint8 is expected
# this was once used to accomodate for image .flags -- still necessary?
def _get_dst(image_, from_to_cspace):
if _CHANGE_COLORSPACE_INPLACE[from_to_cspace]:
return image_
return None
# cv2 does not support height/width 0
# we don't check here if the channel axis is zero-sized as for colorspace
# transformations it should never be 0
if 0 in image.shape[0:2]:
return image
iadt.allow_only_uint8({image.dtype})
for arg_name in ["to_colorspace", "from_colorspace"]:
assert locals()[arg_name] in CSPACE_ALL, (
"Expected `%s` to be one of: %s. Got: %s." % (
arg_name, CSPACE_ALL, locals()[arg_name]))
assert from_colorspace != CSPACE_GRAY, (
"Cannot convert from grayscale to another colorspace as colors "
"cannot be recovered.")
assert image.ndim == 3, (
"Expected image shape to be three-dimensional, i.e. (H,W,C), "
"got %d dimensions with shape %s." % (image.ndim, image.shape))
assert image.shape[2] == 3, (
"Expected number of channels to be three, "
"got %d channels (shape %s)." % (image.shape[2], image.shape,))
if from_colorspace == to_colorspace:
return image
from_to_direct = (from_colorspace, to_colorspace)
from_to_indirect = [
(from_colorspace, CSPACE_RGB),
(CSPACE_RGB, to_colorspace)
]
image = _normalize_cv2_input_arr_(image)
image_aug = image
if from_to_direct in _CSPACE_OPENCV_CONV_VARS:
from2to_var = _CSPACE_OPENCV_CONV_VARS[from_to_direct]
dst = _get_dst(image_aug, from_to_direct)
image_aug = cv2.cvtColor(image_aug, from2to_var, dst=dst)
else:
from2rgb_var = _CSPACE_OPENCV_CONV_VARS[from_to_indirect[0]]
rgb2to_var = _CSPACE_OPENCV_CONV_VARS[from_to_indirect[1]]
dst1 = _get_dst(image_aug, from_to_indirect[0])
dst2 = _get_dst(image_aug, from_to_indirect[1])
image_aug = cv2.cvtColor(image_aug, from2rgb_var, dst=dst1)
image_aug = cv2.cvtColor(image_aug, rgb2to_var, dst=dst2)
# for grayscale: covnert from (H, W) to (H, W, 3)
if len(image_aug.shape) == 2:
image_aug = image_aug[:, :, np.newaxis]
image_aug = np.tile(image_aug, (1, 1, 3))
return image_aug
def change_colorspaces_(images, to_colorspaces, from_colorspaces=CSPACE_RGB):
"""Change the colorspaces of a batch of images inplace.
.. note::
All outputs of this function are `uint8`. For some colorspaces this
may not be optimal.
.. note::
Output grayscale images will still have three channels.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspace_`.
Parameters
----------
images : ndarray or list of ndarray
The images to convert from one colorspace into another.
Either a list of ``(H,W,3)`` arrays or a single ``(N,H,W,3)`` array.
to_colorspaces : str or iterable of str
The target colorspaces. Either a single string (all images will be
converted to the same colorspace) or an iterable of strings (one per
image). See the ``CSPACE`` constants, e.g.
``imgaug.augmenters.color.CSPACE_RGB``.
from_colorspaces : str or list of str, optional
The source colorspace. Analogous to `to_colorspace`. Defaults
to ``RGB``.
Returns
-------
ndarray or list of ndarray
Images with target colorspaces. *Can* contain the same array instances
as were originally provided (i.e. changed inplace). Grayscale images
will still have three channels.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> import numpy as np
>>> # fake RGB image
>>> image_rgb = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
>>> images_rgb = [image_rgb, image_rgb, image_rgb]
>>> images_rgb_copy = [np.copy(image_rgb) for image_rgb in images_rgb]
>>> images_bgr = iaa.change_colorspaces_(images_rgb_copy, iaa.CSPACE_BGR)
Create three example ``RGB`` images and convert them to ``BGR`` colorspace.
>>> images_rgb_copy = [np.copy(image_rgb) for image_rgb in images_rgb]
>>> images_various = iaa.change_colorspaces_(
>>> images_rgb_copy, [iaa.CSPACE_BGR, iaa.CSPACE_HSV, iaa.CSPACE_GRAY])
Chnage the colorspace of the first image to ``BGR``, the one of the second
image to ``HSV`` and the one of the third image to ``grayscale`` (note
that in the latter case the image will still have shape ``(H,W,3)``,
not ``(H,W,1)``).
"""
def _validate(arg, arg_name):
if ia.is_string(arg):
arg = [arg] * len(images)
else:
assert ia.is_iterable(arg), (
"Expected `%s` to be either an iterable of strings or a single "
"string. Got type: %s." % (arg_name, type(arg).__name__)
)
assert len(arg) == len(images), (
"If `%s` is provided as a list it must have the same length "
"as `images`. Got length %d, expected %d." % (
arg_name, len(arg), len(images)))
return arg
to_colorspaces = _validate(to_colorspaces, "to_colorspaces")
from_colorspaces = _validate(from_colorspaces, "from_colorspaces")
gen = zip(images, to_colorspaces, from_colorspaces)
for i, (image, to_colorspace, from_colorspace) in enumerate(gen):
images[i] = change_colorspace_(image, to_colorspace, from_colorspace)
return images
# Added in 0.4.0.
class _KelvinToRGBTableSingleton(object):
_INSTANCE = None
# Added in 0.4.0.
@classmethod
def get_instance(cls):
if cls._INSTANCE is None:
cls._INSTANCE = _KelvinToRGBTable()
return cls._INSTANCE
# Added in 0.4.0.
class _KelvinToRGBTable(object):
# Added in 0.4.0.
def __init__(self):
self.table = self.create_table()
def transform_kelvins_to_rgb_multipliers(self, kelvins):
"""Transform kelvin values to corresponding multipliers for RGB images.
A single returned multiplier denotes the channelwise multipliers
in the range ``[0.0, 1.0]`` to apply to an image to change its kelvin
value to the desired one.
Added in 0.4.0.
Parameters
----------
kelvins : iterable of number
Imagewise temperatures in kelvin.
Returns
-------
ndarray
``float32 (N, 3) ndarrays``, one per kelvin.
"""
kelvins = np.clip(kelvins, 1000, 40000)
tbl_indices = kelvins / 100 - (1000//100)
tbl_indices_floored = np.floor(tbl_indices)
tbl_indices_ceiled = np.ceil(tbl_indices)
interpolation_factors = tbl_indices - tbl_indices_floored
tbl_indices_floored_int = tbl_indices_floored.astype(np.int32)
tbl_indices_ceiled_int = tbl_indices_ceiled.astype(np.int32)
multipliers_floored = self.table[tbl_indices_floored_int, :]
multipliers_ceiled = self.table[tbl_indices_ceiled_int, :]
multipliers = (
multipliers_floored
+ interpolation_factors[:, np.newaxis]
* (multipliers_ceiled - multipliers_floored)
)
return multipliers
# Added in 0.4.0.
@classmethod
def create_table(cls):
table = np.float32([
[255, 56, 0], # K=1000
[255, 71, 0], # K=1100
[255, 83, 0], # K=1200
[255, 93, 0], # K=1300
[255, 101, 0], # K=1400
[255, 109, 0], # K=1500
[255, 115, 0], # K=1600
[255, 121, 0], # K=1700
[255, 126, 0], # K=1800
[255, 131, 0], # K=1900
[255, 137, 18], # K=2000
[255, 142, 33], # K=2100
[255, 147, 44], # K=2200
[255, 152, 54], # K=2300
[255, 157, 63], # K=2400
[255, 161, 72], # K=2500
[255, 165, 79], # K=2600
[255, 169, 87], # K=2700
[255, 173, 94], # K=2800
[255, 177, 101], # K=2900
[255, 180, 107], # K=3000
[255, 184, 114], # K=3100
[255, 187, 120], # K=3200
[255, 190, 126], # K=3300
[255, 193, 132], # K=3400
[255, 196, 137], # K=3500
[255, 199, 143], # K=3600
[255, 201, 148], # K=3700
[255, 204, 153], # K=3800
[255, 206, 159], # K=3900
[255, 209, 163], # K=4000
[255, 211, 168], # K=4100
[255, 213, 173], # K=4200
[255, 215, 177], # K=4300
[255, 217, 182], # K=4400
[255, 219, 186], # K=4500
[255, 221, 190], # K=4600
[255, 223, 194], # K=4700
[255, 225, 198], # K=4800
[255, 227, 202], # K=4900
[255, 228, 206], # K=5000
[255, 230, 210], # K=5100
[255, 232, 213], # K=5200
[255, 233, 217], # K=5300
[255, 235, 220], # K=5400
[255, 236, 224], # K=5500
[255, 238, 227], # K=5600
[255, 239, 230], # K=5700
[255, 240, 233], # K=5800
[255, 242, 236], # K=5900
[255, 243, 239], # K=6000
[255, 244, 242], # K=6100
[255, 245, 245], # K=6200
[255, 246, 248], # K=6300
[255, 248, 251], # K=6400
[255, 249, 253], # K=6500
[254, 249, 255], # K=6600
[252, 247, 255], # K=6700
[249, 246, 255], # K=6800
[247, 245, 255], # K=6900
[245, 243, 255], # K=7000
[243, 242, 255], # K=7100
[240, 241, 255], # K=7200
[239, 240, 255], # K=7300
[237, 239, 255], # K=7400
[235, 238, 255], # K=7500
[233, 237, 255], # K=7600
[231, 236, 255], # K=7700
[230, 235, 255], # K=7800
[228, 234, 255], # K=7900
[227, 233, 255], # K=8000
[225, 232, 255], # K=8100
[224, 231, 255], # K=8200
[222, 230, 255], # K=8300
[221, 230, 255], # K=8400
[220, 229, 255], # K=8500
[218, 228, 255], # K=8600
[217, 227, 255], # K=8700
[216, 227, 255], # K=8800
[215, 226, 255], # K=8900
[214, 225, 255], # K=9000
[212, 225, 255], # K=9100
[211, 224, 255], # K=9200
[210, 223, 255], # K=9300
[209, 223, 255], # K=9400
[208, 222, 255], # K=9500
[207, 221, 255], # K=9600
[207, 221, 255], # K=9700
[206, 220, 255], # K=9800
[205, 220, 255], # K=9900
[204, 219, 255], # K=10000
[203, 219, 255], # K=10100
[202, 218, 255], # K=10200
[201, 218, 255], # K=10300
[201, 217, 255], # K=10400
[200, 217, 255], # K=10500
[199, 216, 255], # K=10600
[199, 216, 255], # K=10700
[198, 216, 255], # K=10800
[197, 215, 255], # K=10900
[196, 215, 255], # K=11000
[196, 214, 255], # K=11100
[195, 214, 255], # K=11200
[195, 214, 255], # K=11300
[194, 213, 255], # K=11400
[193, 213, 255], # K=11500
[193, 212, 255], # K=11600
[192, 212, 255], # K=11700
[192, 212, 255], # K=11800
[191, 211, 255], # K=11900
[191, 211, 255], # K=12000
[190, 211, 255], # K=12100
[190, 210, 255], # K=12200
[189, 210, 255], # K=12300
[189, 210, 255], # K=12400
[188, 210, 255], # K=12500
[188, 209, 255], # K=12600
[187, 209, 255], # K=12700
[187, 209, 255], # K=12800
[186, 208, 255], # K=12900
[186, 208, 255], # K=13000
[185, 208, 255], # K=13100
[185, 208, 255], # K=13200
[185, 207, 255], # K=13300
[184, 207, 255], # K=13400
[184, 207, 255], # K=13500
[183, 207, 255], # K=13600
[183, 206, 255], # K=13700
[183, 206, 255], # K=13800
[182, 206, 255], # K=13900
[182, 206, 255], # K=14000
[182, 205, 255], # K=14100
[181, 205, 255], # K=14200
[181, 205, 255], # K=14300
[181, 205, 255], # K=14400
[180, 205, 255], # K=14500
[180, 204, 255], # K=14600
[180, 204, 255], # K=14700
[179, 204, 255], # K=14800
[179, 204, 255], # K=14900
[179, 204, 255], # K=15000
[178, 203, 255], # K=15100
[178, 203, 255], # K=15200
[178, 203, 255], # K=15300
[178, 203, 255], # K=15400
[177, 203, 255], # K=15500
[177, 202, 255], # K=15600
[177, 202, 255], # K=15700
[177, 202, 255], # K=15800
[176, 202, 255], # K=15900
[176, 202, 255], # K=16000
[176, 202, 255], # K=16100
[175, 201, 255], # K=16200
[175, 201, 255], # K=16300
[175, 201, 255], # K=16400
[175, 201, 255], # K=16500
[175, 201, 255], # K=16600
[174, 201, 255], # K=16700
[174, 201, 255], # K=16800
[174, 200, 255], # K=16900
[174, 200, 255], # K=17000
[173, 200, 255], # K=17100
[173, 200, 255], # K=17200
[173, 200, 255], # K=17300
[173, 200, 255], # K=17400
[173, 200, 255], # K=17500
[172, 199, 255], # K=17600
[172, 199, 255], # K=17700
[172, 199, 255], # K=17800
[172, 199, 255], # K=17900
[172, 199, 255], # K=18000
[171, 199, 255], # K=18100
[171, 199, 255], # K=18200
[171, 199, 255], # K=18300
[171, 198, 255], # K=18400
[171, 198, 255], # K=18500
[170, 198, 255], # K=18600
[170, 198, 255], # K=18700
[170, 198, 255], # K=18800
[170, 198, 255], # K=18900
[170, 198, 255], # K=19000
[170, 198, 255], # K=19100
[169, 198, 255], # K=19200
[169, 197, 255], # K=19300
[169, 197, 255], # K=19400
[169, 197, 255], # K=19500
[169, 197, 255], # K=19600
[169, 197, 255], # K=19700
[169, 197, 255], # K=19800
[168, 197, 255], # K=19900
[168, 197, 255], # K=20000
[168, 197, 255], # K=20100
[168, 197, 255], # K=20200
[168, 196, 255], # K=20300
[168, 196, 255], # K=20400
[168, 196, 255], # K=20500
[167, 196, 255], # K=20600
[167, 196, 255], # K=20700
[167, 196, 255], # K=20800
[167, 196, 255], # K=20900
[167, 196, 255], # K=21000
[167, 196, 255], # K=21100
[167, 196, 255], # K=21200
[166, 196, 255], # K=21300
[166, 195, 255], # K=21400
[166, 195, 255], # K=21500
[166, 195, 255], # K=21600
[166, 195, 255], # K=21700
[166, 195, 255], # K=21800
[166, 195, 255], # K=21900
[166, 195, 255], # K=22000
[165, 195, 255], # K=22100
[165, 195, 255], # K=22200
[165, 195, 255], # K=22300
[165, 195, 255], # K=22400
[165, 195, 255], # K=22500
[165, 195, 255], # K=22600
[165, 194, 255], # K=22700
[165, 194, 255], # K=22800
[165, 194, 255], # K=22900
[164, 194, 255], # K=23000
[164, 194, 255], # K=23100
[164, 194, 255], # K=23200
[164, 194, 255], # K=23300
[164, 194, 255], # K=23400
[164, 194, 255], # K=23500
[164, 194, 255], # K=23600
[164, 194, 255], # K=23700
[164, 194, 255], # K=23800
[164, 194, 255], # K=23900
[163, 194, 255], # K=24000
[163, 194, 255], # K=24100
[163, 193, 255], # K=24200
[163, 193, 255], # K=24300
[163, 193, 255], # K=24400
[163, 193, 255], # K=24500
[163, 193, 255], # K=24600
[163, 193, 255], # K=24700
[163, 193, 255], # K=24800
[163, 193, 255], # K=24900
[163, 193, 255], # K=25000
[162, 193, 255], # K=25100
[162, 193, 255], # K=25200
[162, 193, 255], # K=25300
[162, 193, 255], # K=25400
[162, 193, 255], # K=25500
[162, 193, 255], # K=25600
[162, 193, 255], # K=25700
[162, 193, 255], # K=25800
[162, 192, 255], # K=25900
[162, 192, 255], # K=26000
[162, 192, 255], # K=26100
[162, 192, 255], # K=26200
[162, 192, 255], # K=26300
[161, 192, 255], # K=26400
[161, 192, 255], # K=26500
[161, 192, 255], # K=26600
[161, 192, 255], # K=26700
[161, 192, 255], # K=26800
[161, 192, 255], # K=26900
[161, 192, 255], # K=27000
[161, 192, 255], # K=27100
[161, 192, 255], # K=27200
[161, 192, 255], # K=27300
[161, 192, 255], # K=27400
[161, 192, 255], # K=27500
[161, 192, 255], # K=27600
[161, 192, 255], # K=27700
[160, 192, 255], # K=27800
[160, 192, 255], # K=27900
[160, 191, 255], # K=28000
[160, 191, 255], # K=28100
[160, 191, 255], # K=28200
[160, 191, 255], # K=28300
[160, 191, 255], # K=28400
[160, 191, 255], # K=28500
[160, 191, 255], # K=28600
[160, 191, 255], # K=28700
[160, 191, 255], # K=28800
[160, 191, 255], # K=28900
[160, 191, 255], # K=29000
[160, 191, 255], # K=29100
[160, 191, 255], # K=29200
[159, 191, 255], # K=29300
[159, 191, 255], # K=29400
[159, 191, 255], # K=29500
[159, 191, 255], # K=29600
[159, 191, 255], # K=29700
[159, 191, 255], # K=29800
[159, 191, 255], # K=29900
[159, 191, 255], # K=30000
[159, 191, 255], # K=30100
[159, 191, 255], # K=30200
[159, 191, 255], # K=30300
[159, 190, 255], # K=30400
[159, 190, 255], # K=30500
[159, 190, 255], # K=30600
[159, 190, 255], # K=30700
[159, 190, 255], # K=30800
[159, 190, 255], # K=30900
[159, 190, 255], # K=31000
[158, 190, 255], # K=31100
[158, 190, 255], # K=31200
[158, 190, 255], # K=31300
[158, 190, 255], # K=31400
[158, 190, 255], # K=31500
[158, 190, 255], # K=31600
[158, 190, 255], # K=31700
[158, 190, 255], # K=31800
[158, 190, 255], # K=31900
[158, 190, 255], # K=32000
[158, 190, 255], # K=32100
[158, 190, 255], # K=32200
[158, 190, 255], # K=32300
[158, 190, 255], # K=32400
[158, 190, 255], # K=32500
[158, 190, 255], # K=32600
[158, 190, 255], # K=32700
[158, 190, 255], # K=32800
[158, 190, 255], # K=32900
[158, 190, 255], # K=33000
[158, 190, 255], # K=33100
[157, 190, 255], # K=33200
[157, 190, 255], # K=33300
[157, 189, 255], # K=33400
[157, 189, 255], # K=33500
[157, 189, 255], # K=33600
[157, 189, 255], # K=33700
[157, 189, 255], # K=33800
[157, 189, 255], # K=33900
[157, 189, 255], # K=34000
[157, 189, 255], # K=34100
[157, 189, 255], # K=34200
[157, 189, 255], # K=34300
[157, 189, 255], # K=34400
[157, 189, 255], # K=34500
[157, 189, 255], # K=34600
[157, 189, 255], # K=34700
[157, 189, 255], # K=34800
[157, 189, 255], # K=34900
[157, 189, 255], # K=35000
[157, 189, 255], # K=35100
[157, 189, 255], # K=35200
[157, 189, 255], # K=35300
[157, 189, 255], # K=35400
[157, 189, 255], # K=35500
[156, 189, 255], # K=35600
[156, 189, 255], # K=35700
[156, 189, 255], # K=35800
[156, 189, 255], # K=35900
[156, 189, 255], # K=36000
[156, 189, 255], # K=36100
[156, 189, 255], # K=36200
[156, 189, 255], # K=36300
[156, 189, 255], # K=36400
[156, 189, 255], # K=36500
[156, 189, 255], # K=36600
[156, 189, 255], # K=36700
[156, 189, 255], # K=36800
[156, 189, 255], # K=36900
[156, 189, 255], # K=37000
[156, 189, 255], # K=37100
[156, 188, 255], # K=37200
[156, 188, 255], # K=37300
[156, 188, 255], # K=37400
[156, 188, 255], # K=37500
[156, 188, 255], # K=37600
[156, 188, 255], # K=37700
[156, 188, 255], # K=37800
[156, 188, 255], # K=37900
[156, 188, 255], # K=38000
[156, 188, 255], # K=38100
[156, 188, 255], # K=38200
[156, 188, 255], # K=38300
[155, 188, 255], # K=38400
[155, 188, 255], # K=38500
[155, 188, 255], # K=38600
[155, 188, 255], # K=38700
[155, 188, 255], # K=38800
[155, 188, 255], # K=38900
[155, 188, 255], # K=39000
[155, 188, 255], # K=39100
[155, 188, 255], # K=39200
[155, 188, 255], # K=39300
[155, 188, 255], # K=39400
[155, 188, 255], # K=39500
[155, 188, 255], # K=39600
[155, 188, 255], # K=39700
[155, 188, 255], # K=39800
[155, 188, 255], # K=39900
[155, 188, 255], # K=40000
]) / 255.0
_KelvinToRGBTable._TABLE = table
return table
def change_color_temperatures_(images, kelvins, from_colorspaces=CSPACE_RGB):
"""Change in-place the temperature of images to given values in Kelvin.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.change_colorspace_`.
Parameters
----------
images : ndarray or list of ndarray
The images which's color temperature is supposed to be changed.
Either a list of ``(H,W,3)`` arrays or a single ``(N,H,W,3)`` array.
kelvins : iterable of number
Temperatures in Kelvin. One per image. Expected value range is in
the interval ``(1000, 4000)``.
from_colorspaces : str or list of str, optional
The source colorspace.
See :func:`~imgaug.augmenters.color.change_colorspaces_`.
Defaults to ``RGB``.
Returns
-------
ndarray or list of ndarray
Images with target color temperatures.
The input array(s) might have been changed in-place.
"""
# we return here early, because we validate below the first kelvin value
if len(images) == 0:
return images
# TODO this is very similar to the validation in change_colorspaces_().
# Make DRY.
def _validate(arg, arg_name, datatype):
if ia.is_iterable(arg) and not ia.is_string(arg):
assert len(arg) == len(images), (
"If `%s` is provided as an iterable it must have the same "
"length as `images`. Got length %d, expected %d." % (
arg_name, len(arg), len(images)))
elif datatype == "str":
assert ia.is_string(arg), (
"Expected `%s` to be either an iterable of strings or a single "
"string. Got type %s." % (arg_name, type(arg).__name__))
arg = [arg] * len(images)
else:
assert ia.is_single_number(arg), (
"Expected `%s` to be either an iterable of numbers or a single "
"number. Got type %s." % (arg_name, type(arg).__name__))
arg = np.tile(np.float32([arg]), (len(images),))
return arg
kelvins = _validate(kelvins, "kelvins", "number")
from_colorspaces = _validate(from_colorspaces, "from_colorspaces", "str")
# list `kelvins` inputs are not yet converted to ndarray by _validate()
kelvins = np.array(kelvins, dtype=np.float32)
# Validate only one kelvin value for performance reasons.
# If values are outside that range, the kelvin table simply clips them.
# If there are no images (and hence no kelvin values), we already returned
# above.
assert 1000 <= kelvins[0] <= 40000, (
"Expected Kelvin values in the interval [1000, 40000]. "
"Got interval [%.8f, %.8f]." % (np.min(kelvins), np.max(kelvins)))
table = _KelvinToRGBTableSingleton.get_instance()
rgb_multipliers = table.transform_kelvins_to_rgb_multipliers(kelvins)
rgb_multipliers_nhwc = rgb_multipliers.reshape((-1, 1, 1, 3))
gen = enumerate(zip(images, rgb_multipliers_nhwc, from_colorspaces))
for i, (image, rgb_multiplier_hwc, from_colorspace) in gen:
image_rgb = change_colorspace_(image,
to_colorspace=CSPACE_RGB,
from_colorspace=from_colorspace)
# we always have uint8 at this point as only that is accepted by
# convert_colorspace
# all multipliers are in the range [0.0, 1.0], hence we can afford to
# not clip here
image_temp_adj = np.round(
image_rgb.astype(np.float32) * rgb_multiplier_hwc
).astype(np.uint8)
image_orig_cspace = change_colorspace_(image_temp_adj,
to_colorspace=from_colorspace,
from_colorspace=CSPACE_RGB)
images[i] = image_orig_cspace
return images
def change_color_temperature(image, kelvin, from_colorspace=CSPACE_RGB):
"""Change the temperature of an image to a given value in Kelvin.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.change_color_temperatures_`.
Parameters
----------
image : ndarray
The image which's color temperature is supposed to be changed.
Expected to be of shape ``(H,W,3)`` array.
kelvin : number
The temperature in Kelvin. Expected value range is in
the interval ``(1000, 4000)``.
from_colorspace : str, optional
The source colorspace.
See :func:`~imgaug.augmenters.color.change_colorspaces_`.
Defaults to ``RGB``.
Returns
-------
ndarray
Image with target color temperature.
"""
return change_color_temperatures_(image[np.newaxis, ...],
[kelvin],
from_colorspaces=[from_colorspace])[0]
@ia.deprecated(alt_func="WithColorspace")
def InColorspace(to_colorspace, from_colorspace="RGB", children=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
"""Convert images to another colorspace."""
# pylint: disable=invalid-name
return WithColorspace(to_colorspace, from_colorspace, children,
seed=seed, name=name,
random_state=random_state,
deterministic=deterministic)
# TODO add tests
class WithColorspace(meta.Augmenter):
"""
Apply child augmenters within a specific colorspace.
This augumenter takes a source colorspace A and a target colorspace B
as well as children C. It changes images from A to B, then applies the
child augmenters C and finally changes the colorspace back from B to A.
See also ChangeColorspace() for more.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspaces_`.
Parameters
----------
to_colorspace : str
See :func:`~imgaug.augmenters.color.change_colorspace_`.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
One or more augmenters to apply to converted images.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.WithColorspace(
>>> to_colorspace=iaa.CSPACE_HSV,
>>> from_colorspace=iaa.CSPACE_RGB,
>>> children=iaa.WithChannels(
>>> 0,
>>> iaa.Add((0, 50))
>>> )
>>> )
Convert to ``HSV`` colorspace, add a value between ``0`` and ``50``
(uniformly sampled per image) to the Hue channel, then convert back to the
input colorspace (``RGB``).
"""
def __init__(self, to_colorspace, from_colorspace=CSPACE_RGB, children=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(WithColorspace, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.to_colorspace = to_colorspace
self.from_colorspace = from_colorspace
self.children = meta.handle_children_list(children, self.name, "then")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
with batch.propagation_hooks_ctx(self, hooks, parents):
# TODO this did not fail in the tests when there was only one
# `if` with all three steps in it
if batch.images is not None:
batch.images = change_colorspaces_(
batch.images,
to_colorspaces=self.to_colorspace,
from_colorspaces=self.from_colorspace)
batch = self.children.augment_batch_(
batch,
parents=parents + [self],
hooks=hooks
)
if batch.images is not None:
batch.images = change_colorspaces_(
batch.images,
to_colorspaces=self.from_colorspace,
from_colorspaces=self.to_colorspace)
return batch
def _to_deterministic(self):
aug = self.copy()
aug.children = aug.children.to_deterministic()
aug.deterministic = True
aug.random_state = self.random_state.derive_rng_()
return aug
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.channels]
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [self.children]
def __str__(self):
return (
"WithColorspace(from_colorspace=%s, "
"to_colorspace=%s, name=%s, children=[%s], deterministic=%s)" % (
self.from_colorspace, self.to_colorspace, self.name,
self.children, self.deterministic)
)
class WithBrightnessChannels(meta.Augmenter):
"""Augmenter to apply child augmenters to brightness-related image channels.
This augmenter first converts an image to a random colorspace containing a
brightness-related channel (e.g. ``V`` in ``HSV``), then extracts that
channel and applies its child augmenters to this one channel. Afterwards,
it reintegrates the augmented channel into the full image and converts
back to the input colorspace.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspaces_`.
Parameters
----------
children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
One or more augmenters to apply to the brightness channels.
They receive images with a single channel and have to modify these.
to_colorspace : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
Colorspace in which to extract the brightness-related channels.
Currently, ``imgaug.augmenters.color.CSPACE_YCrCb``, ``CSPACE_HSV``,
``CSPACE_HLS``, ``CSPACE_Lab``, ``CSPACE_Luv``, ``CSPACE_YUV``,
``CSPACE_CIE`` are supported.
* If ``imgaug.ALL``: Will pick imagewise a random colorspace from
all supported colorspaces.
* If ``str``: Will always use this colorspace.
* If ``list`` or ``str``: Will pick imagewise a random colorspace
from this list.
* If :class:`~imgaug.parameters.StochasticParameter`:
A parameter that will be queried once per batch to generate
all target colorspaces. Expected to return strings matching the
``CSPACE_*`` constants.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.WithBrightnessChannels(iaa.Add((-50, 50)))
Add ``-50`` to ``50`` to the brightness-related channels of each image.
>>> aug = iaa.WithBrightnessChannels(
>>> iaa.Add((-50, 50)), to_colorspace=[iaa.CSPACE_Lab, iaa.CSPACE_HSV])
Add ``-50`` to ``50`` to the brightness-related channels of each image,
but pick those brightness-related channels only from ``Lab`` (``L``) and
``HSV`` (``V``) colorspaces.
>>> aug = iaa.WithBrightnessChannels(
>>> iaa.Add((-50, 50)), from_colorspace=iaa.CSPACE_BGR)
Add ``-50`` to ``50`` to the brightness-related channels of each image,
where the images are provided in ``BGR`` colorspace instead of the
standard ``RGB``.
"""
# Usually one would think that CSPACE_CIE (=XYZ) would also work, as
# wikipedia says that Y denotes luminance, but this resulted in strong
# color changes (tried also the other channels).
_CSPACE_TO_CHANNEL_ID = {
CSPACE_YCrCb: 0,
CSPACE_HSV: 2,
CSPACE_HLS: 1,
CSPACE_Lab: 0,
CSPACE_Luv: 0,
CSPACE_YUV: 0
}
_VALID_COLORSPACES = set(_CSPACE_TO_CHANNEL_ID.keys())
# Added in 0.4.0.
def __init__(self, children=None,
to_colorspace=[
CSPACE_YCrCb,
CSPACE_HSV,
CSPACE_HLS,
CSPACE_Lab,
CSPACE_Luv,
CSPACE_YUV],
from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
super(WithBrightnessChannels, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.children = meta.handle_children_list(children, self.name, "then")
self.to_colorspace = iap.handle_categorical_string_param(
to_colorspace, "to_colorspace",
valid_values=self._VALID_COLORSPACES)
self.from_colorspace = from_colorspace
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
with batch.propagation_hooks_ctx(self, hooks, parents):
images_cvt = None
to_colorspaces = None
if batch.images is not None:
to_colorspaces = self.to_colorspace.draw_samples(
(len(batch.images),), random_state)
images_cvt = change_colorspaces_(
batch.images,
from_colorspaces=self.from_colorspace,
to_colorspaces=to_colorspaces,)
brightness_channels = self._extract_brightness_channels(
images_cvt, to_colorspaces)
batch.images = brightness_channels
batch = self.children.augment_batch_(
batch, parents=parents + [self], hooks=hooks)
if batch.images is not None:
batch.images = self._invert_extract_brightness_channels(
batch.images, images_cvt, to_colorspaces)
batch.images = change_colorspaces_(
batch.images,
from_colorspaces=to_colorspaces,
to_colorspaces=self.from_colorspace)
return batch
# Added in 0.4.0.
def _extract_brightness_channels(self, images, colorspaces):
result = []
for image, colorspace in zip(images, colorspaces):
channel_id = self._CSPACE_TO_CHANNEL_ID[colorspace]
# Note that augmenters expect (H,W,C) and not (H,W), so cannot
# just use image[:, :, channel_id] here.
channel = image[:, :, channel_id:channel_id+1]
result.append(channel)
return result
# Added in 0.4.0.
def _invert_extract_brightness_channels(self, channels, images,
colorspaces):
for channel, image, colorspace in zip(channels, images, colorspaces):
channel_id = self._CSPACE_TO_CHANNEL_ID[colorspace]
image[:, :, channel_id:channel_id+1] = channel
return images
# Added in 0.4.0.
def _to_deterministic(self):
aug = self.copy()
aug.children = aug.children.to_deterministic()
aug.deterministic = True
aug.random_state = self.random_state.derive_rng_()
return aug
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.to_colorspace, self.from_colorspace]
# Added in 0.4.0.
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [self.children]
# Added in 0.4.0.
def __str__(self):
return (
"WithBrightnessChannels("
"to_colorspace=%s, "
"from_colorspace=%s, "
"name=%s, "
"children=%s, "
"deterministic=%s)" % (
self.to_colorspace,
self.from_colorspace,
self.name,
self.children,
self.deterministic)
)
class MultiplyAndAddToBrightness(WithBrightnessChannels):
"""Multiply and add to the brightness channels of input images.
This is a wrapper around :class:`WithBrightnessChannels` and hence
performs internally the same projection to random colorspaces.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.WithBrightnessChannels`.
Parameters
----------
mul : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.airthmetic.Multiply`.
add : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.airthmetic.Add`.
to_colorspace : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.color.WithBrightnessChannels`.
from_colorspace : str, optional
See :class:`~imgaug.augmenters.color.WithBrightnessChannels`.
random_order : bool, optional
Whether to apply the add and multiply operations in random
order (``True``). If ``False``, this augmenter will always first
multiply and then add.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MultiplyAndAddToBrightness(mul=(0.5, 1.5), add=(-30, 30))
Convert each image to a colorspace with a brightness-related channel,
extract that channel, multiply it by a factor between ``0.5`` and ``1.5``,
add a value between ``-30`` and ``30`` and convert back to the original
colorspace.
"""
# Added in 0.4.0.
def __init__(self, mul=(0.7, 1.3), add=(-30, 30),
to_colorspace=[
CSPACE_YCrCb,
CSPACE_HSV,
CSPACE_HLS,
CSPACE_Lab,
CSPACE_Luv,
CSPACE_YUV],
from_colorspace="RGB",
random_order=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
mul = (
meta.Identity()
if ia.is_single_number(mul) and np.isclose(mul, 1.0)
else arithmetic.Multiply(mul))
add = meta.Identity() if add == 0 else arithmetic.Add(add)
super(MultiplyAndAddToBrightness, self).__init__(
children=meta.Sequential(
[mul, add],
random_order=random_order
),
to_colorspace=to_colorspace,
from_colorspace=from_colorspace,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def __str__(self):
return (
"MultiplyAndAddToBrightness("
"mul=%s, "
"add=%s, "
"to_colorspace=%s, "
"from_colorspace=%s, "
"random_order=%s, "
"name=%s, "
"deterministic=%s)" % (
str(self.children[0]),
str(self.children[1]),
self.to_colorspace,
self.from_colorspace,
self.children.random_order,
self.name,
self.deterministic)
)
class MultiplyBrightness(MultiplyAndAddToBrightness):
"""Multiply the brightness channels of input images.
This is a wrapper around :class:`WithBrightnessChannels` and hence
performs internally the same projection to random colorspaces.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.MultiplyAndAddToBrightness`.
Parameters
----------
mul : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.airthmetic.Multiply`.
to_colorspace : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.color.WithBrightnessChannels`.
from_colorspace : str, optional
See :class:`~imgaug.augmenters.color.WithBrightnessChannels`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MultiplyBrightness((0.5, 1.5))
Convert each image to a colorspace with a brightness-related channel,
extract that channel, multiply it by a factor between ``0.5`` and ``1.5``,
and convert back to the original colorspace.
"""
# Added in 0.4.0.
def __init__(self, mul=(0.7, 1.3),
to_colorspace=[
CSPACE_YCrCb,
CSPACE_HSV,
CSPACE_HLS,
CSPACE_Lab,
CSPACE_Luv,
CSPACE_YUV],
from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
super(MultiplyBrightness, self).__init__(
mul=mul,
add=0,
to_colorspace=to_colorspace,
from_colorspace=from_colorspace,
random_order=False,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class AddToBrightness(MultiplyAndAddToBrightness):
"""Add to the brightness channels of input images.
This is a wrapper around :class:`WithBrightnessChannels` and hence
performs internally the same projection to random colorspaces.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.MultiplyAndAddToBrightness`.
Parameters
----------
add : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.airthmetic.Add`.
to_colorspace : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.color.WithBrightnessChannels`.
from_colorspace : str, optional
See :class:`~imgaug.augmenters.color.WithBrightnessChannels`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AddToBrightness((-30, 30))
Convert each image to a colorspace with a brightness-related channel,
extract that channel, add between ``-30`` and ``30`` and convert back
to the original colorspace.
"""
# Added in 0.4.0.
def __init__(self, add=(-30, 30),
to_colorspace=[
CSPACE_YCrCb,
CSPACE_HSV,
CSPACE_HLS,
CSPACE_Lab,
CSPACE_Luv,
CSPACE_YUV],
from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
super(AddToBrightness, self).__init__(
mul=1.0,
add=add,
to_colorspace=to_colorspace,
from_colorspace=from_colorspace,
random_order=False,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO Merge this into WithColorspace? A bit problematic due to int16
# conversion that would make WithColorspace less flexible.
# TODO add option to choose overflow behaviour for hue and saturation channels,
# e.g. clip, modulo or wrap
class WithHueAndSaturation(meta.Augmenter):
"""
Apply child augmenters to hue and saturation channels.
This augumenter takes an image in a source colorspace, converts
it to HSV, extracts the H (hue) and S (saturation) channels,
applies the provided child augmenters to these channels
and finally converts back to the original colorspace.
The image array generated by this augmenter and provided to its children
is in ``int16`` (**sic!** only augmenters that can handle ``int16`` arrays
can be children!). The hue channel is mapped to the value
range ``[0, 255]``. Before converting back to the source colorspace, the
saturation channel's values are clipped to ``[0, 255]``. A modulo operation
is applied to the hue channel's values, followed by a mapping from
``[0, 255]`` to ``[0, 180]`` (and finally the colorspace conversion).
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspaces_`.
Parameters
----------
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
One or more augmenters to apply to converted images.
They receive ``int16`` images with two channels (hue, saturation)
and have to modify these.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.WithHueAndSaturation(
>>> iaa.WithChannels(0, iaa.Add((0, 50)))
>>> )
Create an augmenter that will add a random value between ``0`` and ``50``
(uniformly sampled per image) hue channel in HSV colorspace. It
automatically accounts for the hue being in angular representation, i.e.
if the angle goes beyond 360 degrees, it will start again at 0 degrees.
The colorspace is finally converted back to ``RGB`` (default setting).
>>> import imgaug.augmenters as iaa
>>> aug = iaa.WithHueAndSaturation([
>>> iaa.WithChannels(0, iaa.Add((-30, 10))),
>>> iaa.WithChannels(1, [
>>> iaa.Multiply((0.5, 1.5)),
>>> iaa.LinearContrast((0.75, 1.25))
>>> ])
>>> ])
Create an augmenter that adds a random value sampled uniformly
from the range ``[-30, 10]`` to the hue and multiplies the saturation
by a random factor sampled uniformly from ``[0.5, 1.5]``. It also
modifies the contrast of the saturation channel. After these steps,
the ``HSV`` image is converted back to ``RGB``.
"""
def __init__(self, children=None, from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(WithHueAndSaturation, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.children = meta.handle_children_list(children, self.name, "then")
self.from_colorspace = from_colorspace
# this dtype needs to be able to go beyond [0, 255] to e.g. accomodate
# for Add or Multiply
self._internal_dtype = np.int16
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
with batch.propagation_hooks_ctx(self, hooks, parents):
images_hs, images_hsv = self._images_to_hsv_(batch.images)
batch.images = images_hs
batch = self.children.augment_batch_(
batch, parents=parents + [self], hooks=hooks)
batch.images = self._hs_to_images_(batch.images, images_hsv)
return batch
# Added in 0.4.0.
def _images_to_hsv_(self, images):
if images is None:
return None, None
# RGB (or other source colorspace) -> HSV
images_hsv = change_colorspaces_(
images, CSPACE_HSV, self.from_colorspace)
# HSV -> HS
images_hs = []
for image_hsv in images_hsv:
image_hsv = image_hsv.astype(np.int16)
# project hue from [0,180] to [0,255] so that child augmenters
# can assume the same value range for all channels
hue = (
(image_hsv[:, :, 0].astype(np.float32) / 180.0) * 255.0
).astype(self._internal_dtype)
saturation = image_hsv[:, :, 1]
images_hs.append(np.stack([hue, saturation], axis=-1))
if ia.is_np_array(images_hsv):
images_hs = np.stack(images_hs, axis=0)
return images_hs, images_hsv
# Added in 0.4.0.
def _hs_to_images_(self, images_hs, images_hsv):
if images_hs is None:
return None
# postprocess augmented HS int16 data
# hue: modulo to [0, 255] then project to [0, 360/2]
# saturation: clip to [0, 255]
# + convert to uint8
# + re-attach V channel to HS
hue_and_sat_proj = []
for i, hs_aug in enumerate(images_hs):
hue_aug = hs_aug[:, :, 0]
sat_aug = hs_aug[:, :, 1]
hue_aug = (
(np.mod(hue_aug, 255).astype(np.float32) / 255.0)
* (360/2)
).astype(np.uint8)
sat_aug = iadt.clip_(sat_aug, 0, 255).astype(np.uint8)
hue_and_sat_proj.append(
np.stack([hue_aug, sat_aug, images_hsv[i][:, :, 2]],
axis=-1)
)
if ia.is_np_array(images_hs):
hue_and_sat_proj = np.uint8(hue_and_sat_proj)
# HSV -> RGB (or whatever the source colorspace was)
images_rgb = change_colorspaces_(
hue_and_sat_proj,
to_colorspaces=self.from_colorspace,
from_colorspaces=CSPACE_HSV)
return images_rgb
def _to_deterministic(self):
aug = self.copy()
aug.children = aug.children.to_deterministic()
aug.deterministic = True
aug.random_state = self.random_state.derive_rng_()
return aug
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.from_colorspace]
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [self.children]
def __str__(self):
return (
"WithHueAndSaturation(from_colorspace=%s, "
"name=%s, children=[%s], deterministic=%s)" % (
self.from_colorspace, self.name,
self.children, self.deterministic)
)
class MultiplyHueAndSaturation(WithHueAndSaturation):
"""
Multipy hue and saturation by random values.
The augmenter first transforms images to HSV colorspace, then multiplies
the pixel values in the H and S channels and afterwards converts back to
RGB.
This augmenter is a wrapper around ``WithHueAndSaturation``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.WithHueAndSaturation`.
Parameters
----------
mul : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier with which to multiply all hue *and* saturation values of
all pixels.
It is expected to be in the range ``-10.0`` to ``+10.0``.
Note that values of ``0.0`` or lower will remove all saturation.
* If this is ``None``, `mul_hue` and/or `mul_saturation`
may be set to values other than ``None``.
* If a number, then that multiplier will be used for all images.
* If a tuple ``(a, b)``, then a value from the continuous
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
mul_hue : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier with which to multiply all hue values.
This is expected to be in the range ``-10.0`` to ``+10.0`` and will
automatically be projected to an angular representation using
``(hue/255) * (360/2)`` (OpenCV's hue representation is in the
range ``[0, 180]`` instead of ``[0, 360]``).
Only this or `mul` may be set, not both.
* If this and `mul_saturation` are both ``None``, `mul` may
be set to a non-``None`` value.
* If a number, then that multiplier will be used for all images.
* If a tuple ``(a, b)``, then a value from the continuous
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
mul_saturation : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier with which to multiply all saturation values.
It is expected to be in the range ``0.0`` to ``+10.0``.
Only this or `mul` may be set, not both.
* If this and `mul_hue` are both ``None``, `mul` may
be set to a non-``None`` value.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the continuous
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
per_channel : bool or float, optional
Whether to sample per image only one value from `mul` and use it for
both hue and saturation (``False``) or to sample independently one
value for hue and one for saturation (``True``).
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``, otherwise as ``False``.
This parameter has no effect if `mul_hue` and/or `mul_saturation`
are used instead of `mul`.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MultiplyHueAndSaturation((0.5, 1.5), per_channel=True)
Multiply hue and saturation by random values between ``0.5`` and ``1.5``
(independently per channel and the same value for all pixels within
that channel). The hue will be automatically projected to an angular
representation.
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MultiplyHueAndSaturation(mul_hue=(0.5, 1.5))
Multiply only the hue by random values between ``0.5`` and ``1.5``.
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MultiplyHueAndSaturation(mul_saturation=(0.5, 1.5))
Multiply only the saturation by random values between ``0.5`` and ``1.5``.
"""
def __init__(self, mul=None, mul_hue=None, mul_saturation=None,
per_channel=False, from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
if mul is None and mul_hue is None and mul_saturation is None:
mul_hue = (0.5, 1.5)
mul_saturation = (0.0, 1.7)
if mul is not None:
assert mul_hue is None, (
"`mul_hue` may not be set if `mul` is set. "
"It is set to: %s (type: %s)." % (
str(mul_hue), type(mul_hue)))
assert mul_saturation is None, (
"`mul_saturation` may not be set if `mul` is set. "
"It is set to: %s (type: %s)." % (
str(mul_saturation), type(mul_saturation)))
mul = iap.handle_continuous_param(
mul, "mul", value_range=(-10.0, 10.0), tuple_to_uniform=True,
list_to_choice=True)
else:
if mul_hue is not None:
mul_hue = iap.handle_continuous_param(
mul_hue, "mul_hue", value_range=(-10.0, 10.0),
tuple_to_uniform=True, list_to_choice=True)
if mul_saturation is not None:
mul_saturation = iap.handle_continuous_param(
mul_saturation, "mul_saturation", value_range=(0.0, 10.0),
tuple_to_uniform=True, list_to_choice=True)
if random_state != "deprecated":
seed = random_state
random_state = "deprecated"
if seed is None:
rss = [None] * 5
else:
rss = iarandom.RNG.create_if_not_rng_(seed).derive_rngs_(5)
children = []
if mul is not None:
children.append(
arithmetic.Multiply(
mul,
per_channel=per_channel,
seed=rss[0],
name="%s-Multiply" % (name,),
random_state=random_state,
deterministic=deterministic
)
)
else:
if mul_hue is not None:
children.append(
meta.WithChannels(
0,
arithmetic.Multiply(
mul_hue,
seed=rss[0],
name="%s-MultiplyHue" % (name,),
random_state=random_state,
deterministic=deterministic
),
seed=rss[1],
name="%s-WithChannelsHue" % (name,),
random_state=random_state,
deterministic=deterministic
)
)
if mul_saturation is not None:
children.append(
meta.WithChannels(
1,
arithmetic.Multiply(
mul_saturation,
seed=rss[2],
name="%s-MultiplySaturation" % (name,),
random_state=random_state,
deterministic=deterministic
),
seed=rss[3],
name="%s-WithChannelsSaturation" % (name,),
random_state=random_state,
deterministic=deterministic
)
)
super(MultiplyHueAndSaturation, self).__init__(
children,
from_colorspace=from_colorspace,
seed=rss[4],
name=name,
random_state=random_state, deterministic=deterministic
)
class MultiplyHue(MultiplyHueAndSaturation):
"""
Multiply the hue of images by random values.
The augmenter first transforms images to HSV colorspace, then multiplies
the pixel values in the H channel and afterwards converts back to
RGB.
This augmenter is a shortcut for ``MultiplyHueAndSaturation(mul_hue=...)``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.MultiplyHueAndSaturation`.
Parameters
----------
mul : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier with which to multiply all hue values.
This is expected to be in the range ``-10.0`` to ``+10.0`` and will
automatically be projected to an angular representation using
``(hue/255) * (360/2)`` (OpenCV's hue representation is in the
range ``[0, 180]`` instead of ``[0, 360]``).
Only this or `mul` may be set, not both.
* If a number, then that multiplier will be used for all images.
* If a tuple ``(a, b)``, then a value from the continuous
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MultiplyHue((0.5, 1.5))
Multiply the hue channel of images using random values between ``0.5``
and ``1.5``.
"""
def __init__(self, mul=(-3.0, 3.0), from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MultiplyHue, self).__init__(
mul_hue=mul,
from_colorspace=from_colorspace,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class MultiplySaturation(MultiplyHueAndSaturation):
"""
Multiply the saturation of images by random values.
The augmenter first transforms images to HSV colorspace, then multiplies
the pixel values in the H channel and afterwards converts back to
RGB.
This augmenter is a shortcut for
``MultiplyHueAndSaturation(mul_saturation=...)``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.MultiplyHueAndSaturation`.
Parameters
----------
mul : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier with which to multiply all saturation values.
It is expected to be in the range ``0.0`` to ``+10.0``.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the continuous
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MultiplySaturation((0.5, 1.5))
Multiply the saturation channel of images using random values between
``0.5`` and ``1.5``.
"""
def __init__(self, mul=(0.0, 3.0), from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MultiplySaturation, self).__init__(
mul_saturation=mul,
from_colorspace=from_colorspace,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class RemoveSaturation(MultiplySaturation):
"""Decrease the saturation of images by varying degrees.
This creates images looking similar to :class:`Grayscale`.
This augmenter is the same as ``MultiplySaturation((0.0, 1.0))``.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.MultiplySaturation`.
Parameters
----------
mul : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
*Inverse* multiplier to use for the saturation values.
High values denote stronger color removal. E.g. ``1.0`` will remove
all saturation, ``0.0`` will remove nothing.
Expected value range is ``[0.0, 1.0]``.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the continuous
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.RemoveSaturation((0.0, 1.0))
Create an augmenter that decreases saturation by varying degrees.
>>> aug = iaa.RemoveSaturation(1.0)
Create an augmenter that removes all saturation from input images.
This is similar to :class:`Grayscale`.
>>> aug = iaa.RemoveSaturation(from_colorspace=iaa.CSPACE_BGR)
Create an augmenter that decreases saturation of images in ``BGR``
colorspace by varying degrees.
"""
# Added in 0.4.0.
def __init__(self, mul=1, from_colorspace=CSPACE_RGB,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
mul = iap.Subtract(
1.0,
iap.handle_continuous_param(mul, "mul",
value_range=(0.0, 1.0),
tuple_to_uniform=True,
list_to_choice=True),
elementwise=True
)
super(RemoveSaturation, self).__init__(
mul, from_colorspace=from_colorspace,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO removed deterministic and random_state here as parameters, because this
# function creates multiple child augmenters. not sure if this is sensible
# (give them all the same random state instead?)
# TODO this is for now deactivated, because HSV images returned by opencv have
# value range 0-180 for the hue channel
# and are supposed to be angular representations, i.e. if values go below
# 0 or above 180 they are supposed to overflow
# to 180 and 0
# pylint: disable=pointless-string-statement
"""
def AddToHueAndSaturation(value=0, per_channel=False, from_colorspace="RGB",
channels=[0, 1], name=None): # pylint: disable=locally-disabled, dangerous-default-value, line-too-long
""
Augmenter that transforms images into HSV space, selects the H and S
channels and then adds a given range of values to these.
Parameters
----------
value : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.arithmetic.Add.__init__()`.
per_channel : bool or float, optional
See :func:`~imgaug.augmenters.arithmetic.Add.__init__()`.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
channels : int or list of int or None, optional
See :func:`~imgaug.augmenters.meta.WithChannels.__init__()`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
Examples
--------
>>> aug = AddToHueAndSaturation((-20, 20), per_channel=True)
Adds random values between -20 and 20 to the hue and saturation
(independently per channel and the same value for all pixels within
that channel).
""
if name is None:
name = "Unnamed%s" % (ia.caller_name(),)
return WithColorspace(
to_colorspace="HSV",
from_colorspace=from_colorspace,
children=meta.WithChannels(
channels=channels,
children=arithmetic.Add(value=value, per_channel=per_channel)
),
name=name
)
"""
# pylint: enable=pointless-string-statement
class AddToHueAndSaturation(meta.Augmenter):
"""
Increases or decreases hue and saturation by random values.
The augmenter first transforms images to HSV colorspace, then adds random
values to the H and S channels and afterwards converts back to RGB.
This augmenter is faster than using ``WithHueAndSaturation`` in combination
with ``Add``.
TODO add float support
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspace_`.
Parameters
----------
value : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Value to add to the hue *and* saturation of all pixels.
It is expected to be in the range ``-255`` to ``+255``.
* If this is ``None``, `value_hue` and/or `value_saturation`
may be set to values other than ``None``.
* If an integer, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the discrete
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
value_hue : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Value to add to the hue of all pixels.
This is expected to be in the range ``-255`` to ``+255`` and will
automatically be projected to an angular representation using
``(hue/255) * (360/2)`` (OpenCV's hue representation is in the
range ``[0, 180]`` instead of ``[0, 360]``).
Only this or `value` may be set, not both.
* If this and `value_saturation` are both ``None``, `value` may
be set to a non-``None`` value.
* If an integer, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the discrete
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
value_saturation : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Value to add to the saturation of all pixels.
It is expected to be in the range ``-255`` to ``+255``.
Only this or `value` may be set, not both.
* If this and `value_hue` are both ``None``, `value` may
be set to a non-``None`` value.
* If an integer, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the discrete
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
per_channel : bool or float, optional
Whether to sample per image only one value from `value` and use it for
both hue and saturation (``False``) or to sample independently one
value for hue and one for saturation (``True``).
If this value is a float ``p``, then for ``p`` percent of all images
`per_channel` will be treated as ``True``, otherwise as ``False``.
This parameter has no effect is `value_hue` and/or `value_saturation`
are used instead of `value`.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AddToHueAndSaturation((-50, 50), per_channel=True)
Add random values between ``-50`` and ``50`` to the hue and saturation
(independently per channel and the same value for all pixels within
that channel).
"""
_LUT_CACHE = None
def __init__(self, value=None, value_hue=None, value_saturation=None,
per_channel=False, from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AddToHueAndSaturation, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
if value is None and value_hue is None and value_saturation is None:
value_hue = (-40, 40)
value_saturation = (-40, 40)
self.value = self._handle_value_arg(value, value_hue, value_saturation)
self.value_hue = self._handle_value_hue_arg(value_hue)
self.value_saturation = self._handle_value_saturation_arg(
value_saturation)
self.per_channel = iap.handle_probability_param(per_channel,
"per_channel")
self.from_colorspace = from_colorspace
self.backend = "cv2"
# precompute tables for cv2.LUT
if self.backend == "cv2" and AddToHueAndSaturation._LUT_CACHE is None:
AddToHueAndSaturation._LUT_CACHE = self._generate_lut_table()
def _draw_samples(self, augmentables, random_state):
nb_images = len(augmentables)
rss = random_state.duplicate(2)
if self.value is not None:
per_channel = self.per_channel.draw_samples(
(nb_images,), random_state=rss[0])
per_channel = (per_channel > 0.5)
samples = self.value.draw_samples(
(nb_images, 2), random_state=rss[1]).astype(np.int32)
assert -255 <= samples[0, 0] <= 255, (
"Expected values sampled from `value` in "
"AddToHueAndSaturation to be in range [-255, 255], "
"but got %.8f." % (samples[0, 0]))
samples_hue = samples[:, 0]
samples_saturation = np.copy(samples[:, 0])
samples_saturation[per_channel] = samples[per_channel, 1]
else:
if self.value_hue is not None:
samples_hue = self.value_hue.draw_samples(
(nb_images,), random_state=rss[0]).astype(np.int32)
else:
samples_hue = np.zeros((nb_images,), dtype=np.int32)
if self.value_saturation is not None:
samples_saturation = self.value_saturation.draw_samples(
(nb_images,), random_state=rss[1]).astype(np.int32)
else:
samples_saturation = np.zeros((nb_images,), dtype=np.int32)
# project hue to angular representation
# OpenCV uses range [0, 180] for the hue
samples_hue = (
(samples_hue.astype(np.float32) / 255.0) * (360/2)
).astype(np.int32)
return samples_hue, samples_saturation
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
input_dtypes = iadt.copy_dtypes_for_restore(images, force_list=True)
# surprisingly, placing this here seems to be slightly slower than
# placing it inside the loop
# if isinstance(images_hsv, list):
# images_hsv = [img.astype(np.int32) for img in images_hsv]
# else:
# images_hsv = images_hsv.astype(np.int32)
images_hsv = change_colorspaces_(
images, CSPACE_HSV, self.from_colorspace)
samples = self._draw_samples(images, random_state)
hues = samples[0]
saturations = samples[1]
# this is needed if no cache for LUT is used:
# value_range = np.arange(0, 256, dtype=np.int16)
gen = enumerate(zip(images_hsv, hues, saturations))
for i, (image_hsv, hue_i, saturation_i) in gen:
if image_hsv.size == 0:
continue
if self.backend == "cv2":
image_hsv = self._transform_image_cv2(
image_hsv, hue_i, saturation_i)
else:
image_hsv = self._transform_image_numpy(
image_hsv, hue_i, saturation_i)
image_hsv = image_hsv.astype(input_dtypes[i])
image_rgb = change_colorspace_(
image_hsv,
to_colorspace=self.from_colorspace,
from_colorspace=CSPACE_HSV)
batch.images[i] = image_rgb
return batch
@classmethod
def _transform_image_cv2(cls, image_hsv, hue, saturation):
# this has roughly the same speed as the numpy backend
# for 64x64 and is about 25% faster for 224x224
# code without using cache:
# table_hue = np.mod(value_range + sample_hue, 180)
# table_saturation = np.clip(value_range + sample_saturation, 0, 255)
# table_hue = table_hue.astype(np.uint8, copy=False)
# table_saturation = table_saturation.astype(np.uint8, copy=False)
# image_hsv[..., 0] = cv2.LUT(image_hsv[..., 0], table_hue)
# image_hsv[..., 1] = cv2.LUT(image_hsv[..., 1], table_saturation)
# code with using cache (at best maybe 10% faster for 64x64):
table_hue = cls._LUT_CACHE[0]
table_saturation = cls._LUT_CACHE[1]
tables = [
table_hue[255+int(hue)],
table_saturation[255+int(saturation)]
]
image_hsv[..., [0, 1]] = ia.apply_lut(image_hsv[..., [0, 1]],
tables)
return image_hsv
@classmethod
def _transform_image_numpy(cls, image_hsv, hue, saturation):
# int16 seems to be slightly faster than int32
image_hsv = image_hsv.astype(np.int16)
# np.mod() works also as required here for negative values
image_hsv[..., 0] = np.mod(image_hsv[..., 0] + hue, 180)
image_hsv[..., 1] = np.clip(
image_hsv[..., 1] + saturation, 0, 255)
return image_hsv
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.value, self.value_hue, self.value_saturation,
self.per_channel, self.from_colorspace]
@classmethod
def _handle_value_arg(cls, value, value_hue, value_saturation):
if value is not None:
assert value_hue is None, (
"`value_hue` may not be set if `value` is set. "
"It is set to: %s (type: %s)." % (
str(value_hue), type(value_hue)))
assert value_saturation is None, (
"`value_saturation` may not be set if `value` is set. "
"It is set to: %s (type: %s)." % (
str(value_saturation), type(value_saturation)))
return iap.handle_discrete_param(
value, "value", value_range=(-255, 255), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
return None
@classmethod
def _handle_value_hue_arg(cls, value_hue):
if value_hue is not None:
# we don't have to verify here that value is None, as the
# exclusivity was already ensured in _handle_value_arg()
return iap.handle_discrete_param(
value_hue, "value_hue", value_range=(-255, 255),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
return None
@classmethod
def _handle_value_saturation_arg(cls, value_saturation):
if value_saturation is not None:
# we don't have to verify here that value is None, as the
# exclusivity was already ensured in _handle_value_arg()
return iap.handle_discrete_param(
value_saturation, "value_saturation", value_range=(-255, 255),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
return None
@classmethod
def _generate_lut_table(cls):
# TODO Changing the dtype here to int8 makes gen test for this method
# fail, but all other tests still succeed. How can this be?
# The dtype was verified to remain int8, having min & max at
# -128 & 127.
dtype = np.uint8
table = (np.zeros((256*2, 256), dtype=dtype),
np.zeros((256*2, 256), dtype=dtype))
value_range = np.arange(0, 256, dtype=np.int16)
# this could be done slightly faster by vectorizing the loop
for i in sm.xrange(-255, 255+1):
table_hue = np.mod(value_range + i, 180)
table_saturation = np.clip(value_range + i, 0, 255)
table[0][255+i, :] = table_hue
table[1][255+i, :] = table_saturation
return table
class AddToHue(AddToHueAndSaturation):
"""
Add random values to the hue of images.
The augmenter first transforms images to HSV colorspace, then adds random
values to the H channel and afterwards converts back to RGB.
If you want to change both the hue and the saturation, it is recommended
to use ``AddToHueAndSaturation`` as otherwise the image will be
converted twice to HSV and back to RGB.
This augmenter is a shortcut for ``AddToHueAndSaturation(value_hue=...)``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.AddToHueAndSaturation`.
Parameters
----------
value : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Value to add to the hue of all pixels.
This is expected to be in the range ``-255`` to ``+255`` and will
automatically be projected to an angular representation using
``(hue/255) * (360/2)`` (OpenCV's hue representation is in the
range ``[0, 180]`` instead of ``[0, 360]``).
* If an integer, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the discrete
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AddToHue((-50, 50))
Sample random values from the discrete uniform range ``[-50..50]``,
convert them to angular representation and add them to the hue, i.e.
to the ``H`` channel in ``HSV`` colorspace.
"""
def __init__(self, value=(-255, 255), from_colorspace=CSPACE_RGB,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AddToHue, self).__init__(
value_hue=value,
from_colorspace=from_colorspace,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class AddToSaturation(AddToHueAndSaturation):
"""
Add random values to the saturation of images.
The augmenter first transforms images to HSV colorspace, then adds random
values to the S channel and afterwards converts back to RGB.
If you want to change both the hue and the saturation, it is recommended
to use ``AddToHueAndSaturation`` as otherwise the image will be
converted twice to HSV and back to RGB.
This augmenter is a shortcut for
``AddToHueAndSaturation(value_saturation=...)``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.AddToHueAndSaturation`.
Parameters
----------
value : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Value to add to the saturation of all pixels.
It is expected to be in the range ``-255`` to ``+255``.
* If an integer, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the discrete
range ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, then a value will be sampled from that
parameter per image.
from_colorspace : str, optional
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AddToSaturation((-50, 50))
Sample random values from the discrete uniform range ``[-50..50]``,
and add them to the saturation, i.e. to the ``S`` channel in ``HSV``
colorspace.
"""
def __init__(self, value=(-75, 75), from_colorspace="RGB",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AddToSaturation, self).__init__(
value_saturation=value,
from_colorspace=from_colorspace,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO tests
# TODO rename to ChangeColorspace3D and then introduce ChangeColorspace, which
# does not enforce 3d images?
class ChangeColorspace(meta.Augmenter):
"""
Augmenter to change the colorspace of images.
.. note::
This augmenter is not tested. Some colorspaces might work, others
might not.
..note::
This augmenter tries to project the colorspace value range on
0-255. It outputs dtype=uint8 images.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspace_`.
Parameters
----------
to_colorspace : str or list of str or imgaug.parameters.StochasticParameter
The target colorspace.
Allowed strings are: ``RGB``, ``BGR``, ``GRAY``, ``CIE``, ``YCrCb``,
``HSV``, ``HLS``, ``Lab``, ``Luv``.
These are also accessible via
``imgaug.augmenters.color.CSPACE_``,
e.g. ``imgaug.augmenters.CSPACE_YCrCb``.
* If a string, it must be among the allowed colorspaces.
* If a list, it is expected to be a list of strings, each one
being an allowed colorspace. A random element from the list
will be chosen per image.
* If a StochasticParameter, it is expected to return string. A new
sample will be drawn per image.
from_colorspace : str, optional
The source colorspace (of the input images).
See `to_colorspace`. Only a single string is allowed.
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
The alpha value of the new colorspace when overlayed over the
old one. A value close to 1.0 means that mostly the new
colorspace is visible. A value close to 0.0 means, that mostly the
old image is visible.
* If an int or float, exactly that value will be used.
* If a tuple ``(a, b)``, a random value from the range
``a <= x <= b`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, a value will be sampled from the
parameter per image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
"""
# TODO mark these as deprecated
RGB = CSPACE_RGB
BGR = CSPACE_BGR
GRAY = CSPACE_GRAY
CIE = CSPACE_CIE
YCrCb = CSPACE_YCrCb
HSV = CSPACE_HSV
HLS = CSPACE_HLS
Lab = CSPACE_Lab
Luv = CSPACE_Luv
COLORSPACES = {RGB, BGR, GRAY, CIE, YCrCb, HSV, HLS, Lab, Luv}
# TODO access cv2 COLOR_ variables directly instead of indirectly via
# dictionary mapping
CV_VARS = {
# RGB
"RGB2BGR": cv2.COLOR_RGB2BGR,
"RGB2GRAY": cv2.COLOR_RGB2GRAY,
"RGB2CIE": cv2.COLOR_RGB2XYZ,
"RGB2YCrCb": cv2.COLOR_RGB2YCR_CB,
"RGB2HSV": cv2.COLOR_RGB2HSV,
"RGB2HLS": cv2.COLOR_RGB2HLS,
"RGB2Lab": cv2.COLOR_RGB2LAB,
"RGB2Luv": cv2.COLOR_RGB2LUV,
# BGR
"BGR2RGB": cv2.COLOR_BGR2RGB,
"BGR2GRAY": cv2.COLOR_BGR2GRAY,
"BGR2CIE": cv2.COLOR_BGR2XYZ,
"BGR2YCrCb": cv2.COLOR_BGR2YCR_CB,
"BGR2HSV": cv2.COLOR_BGR2HSV,
"BGR2HLS": cv2.COLOR_BGR2HLS,
"BGR2Lab": cv2.COLOR_BGR2LAB,
"BGR2Luv": cv2.COLOR_BGR2LUV,
# HSV
"HSV2RGB": cv2.COLOR_HSV2RGB,
"HSV2BGR": cv2.COLOR_HSV2BGR,
# HLS
"HLS2RGB": cv2.COLOR_HLS2RGB,
"HLS2BGR": cv2.COLOR_HLS2BGR,
# Lab
"Lab2RGB": (
cv2.COLOR_Lab2RGB
if hasattr(cv2, "COLOR_Lab2RGB") else cv2.COLOR_LAB2RGB),
"Lab2BGR": (
cv2.COLOR_Lab2BGR
if hasattr(cv2, "COLOR_Lab2BGR") else cv2.COLOR_LAB2BGR)
}
def __init__(self, to_colorspace, from_colorspace=CSPACE_RGB, alpha=1.0,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ChangeColorspace, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO somehow merge this with Alpha augmenter?
self.alpha = iap.handle_continuous_param(
alpha, "alpha", value_range=(0, 1.0), tuple_to_uniform=True,
list_to_choice=True)
if ia.is_string(to_colorspace):
assert to_colorspace in CSPACE_ALL, (
"Expected 'to_colorspace' to be one of %s. Got %s." % (
CSPACE_ALL, to_colorspace))
self.to_colorspace = iap.Deterministic(to_colorspace)
elif ia.is_iterable(to_colorspace):
all_strings = all(
[ia.is_string(colorspace) for colorspace in to_colorspace])
assert all_strings, (
"Expected list of 'to_colorspace' to only contain strings. "
"Got types %s." % (
", ".join([str(type(v)) for v in to_colorspace])))
all_valid = all(
[(colorspace in CSPACE_ALL)
for colorspace in to_colorspace])
assert all_valid, (
"Expected list of 'to_colorspace' to only contain strings "
"that are in %s. Got strings %s." % (
CSPACE_ALL, to_colorspace))
self.to_colorspace = iap.Choice(to_colorspace)
elif isinstance(to_colorspace, iap.StochasticParameter):
self.to_colorspace = to_colorspace
else:
raise Exception("Expected to_colorspace to be string, list of "
"strings or StochasticParameter, got %s." % (
type(to_colorspace),))
self.to_colorspace = iap._wrap_leafs_of_param_in_prefetchers(
self.to_colorspace, iap._NB_PREFETCH_STRINGS
)
assert ia.is_string(from_colorspace), (
"Expected from_colorspace to be a single string, "
"got type %s." % (type(from_colorspace),))
assert from_colorspace in CSPACE_ALL, (
"Expected from_colorspace to be one of: %s. Got: %s." % (
", ".join(CSPACE_ALL), from_colorspace))
assert from_colorspace != CSPACE_GRAY, (
"Cannot convert from grayscale images to other colorspaces.")
self.from_colorspace = from_colorspace
# epsilon value to check if alpha is close to 1.0 or 0.0
self.eps = 0.001
def _draw_samples(self, n_augmentables, random_state):
rss = random_state.duplicate(2)
alphas = self.alpha.draw_samples(
(n_augmentables,), random_state=rss[0])
to_colorspaces = self.to_colorspace.draw_samples(
(n_augmentables,), random_state=rss[1])
return alphas, to_colorspaces
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
alphas, to_colorspaces = self._draw_samples(nb_images, random_state)
for i, image in enumerate(images):
alpha = alphas[i]
to_colorspace = to_colorspaces[i]
assert to_colorspace in CSPACE_ALL, (
"Expected 'to_colorspace' to be one of %s. Got %s." % (
CSPACE_ALL, to_colorspace))
if alpha <= self.eps or self.from_colorspace == to_colorspace:
pass # no change necessary
else:
image_aug = change_colorspace_(image, to_colorspace,
self.from_colorspace)
batch.images[i] = blend.blend_alpha(image_aug, image, alpha,
self.eps)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.to_colorspace, self.alpha]
# TODO This should rather do the blending in RGB or BGR space.
# Currently, if the input image is in e.g. HSV space, it will blend in
# that space.
# TODO rename to Grayscale3D and add Grayscale that keeps the image at 1D?
class Grayscale(ChangeColorspace):
"""Augmenter to convert images to their grayscale versions.
.. note::
Number of output channels is still ``3``, i.e. this augmenter just
"removes" color.
TODO check dtype support
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_colorspace_`.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
The alpha value of the grayscale image when overlayed over the
old image. A value close to 1.0 means, that mostly the new grayscale
image is visible. A value close to 0.0 means, that mostly the
old image is visible.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value from the range
``a <= x <= b`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, a value will be sampled from the
parameter per image.
from_colorspace : str, optional
The source colorspace (of the input images).
See :func:`~imgaug.augmenters.color.change_colorspace_`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Grayscale(alpha=1.0)
Creates an augmenter that turns images to their grayscale versions.
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Grayscale(alpha=(0.0, 1.0))
Creates an augmenter that turns images to their grayscale versions with
an alpha value in the range ``0 <= alpha <= 1``. An alpha value of 0.5 would
mean, that the output image is 50 percent of the input image and 50
percent of the grayscale image (i.e. 50 percent of color removed).
"""
def __init__(self, alpha=1, from_colorspace=CSPACE_RGB,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Grayscale, self).__init__(
to_colorspace=CSPACE_GRAY,
alpha=alpha,
from_colorspace=from_colorspace,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ChangeColorTemperature(meta.Augmenter):
"""Change the temperature to a provided Kelvin value.
Low Kelvin values around ``1000`` to ``4000`` will result in red, yellow
or orange images. Kelvin values around ``10000`` to ``40000`` will result
in progressively darker blue tones.
Color temperatures taken from
``_
Basic method to change color temperatures taken from
``_
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.change_color_temperatures_`.
Parameters
----------
kelvin : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Temperature in Kelvin. The temperatures of images will be modified to
this value. Must be in the interval ``[1000, 40000]``.
* If a number, exactly that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the
interval ``[a, b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.ChangeColorTemperature((1100, 10000))
Create an augmenter that changes the color temperature of images to
a random value between ``1100`` and ``10000`` Kelvin.
"""
# Added in 0.4.0.
def __init__(self, kelvin=(1000, 11000), from_colorspace=CSPACE_RGB,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ChangeColorTemperature, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.kelvin = iap.handle_continuous_param(
kelvin, "kelvin", value_range=(1000, 40000), tuple_to_uniform=True,
list_to_choice=True)
self.from_colorspace = from_colorspace
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is not None:
nb_rows = batch.nb_rows
kelvins = self.kelvin.draw_samples((nb_rows,),
random_state=random_state)
batch.images = change_color_temperatures_(
batch.images, kelvins, from_colorspaces=self.from_colorspace)
return batch
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.kelvin, self.from_colorspace]
@six.add_metaclass(ABCMeta)
class _AbstractColorQuantization(meta.Augmenter):
def __init__(self,
counts=(2, 16), # number of bits or colors
counts_value_range=(2, None),
from_colorspace=CSPACE_RGB,
to_colorspace=[CSPACE_RGB, CSPACE_Lab],
max_size=128,
interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
super(_AbstractColorQuantization, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.counts_value_range = counts_value_range
self.counts = iap.handle_discrete_param(
counts, "counts", value_range=counts_value_range,
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
self.from_colorspace = from_colorspace
self.to_colorspace = to_colorspace
self.max_size = max_size
self.interpolation = interpolation
def _draw_samples(self, n_augmentables, random_state):
counts = self.counts.draw_samples((n_augmentables,), random_state)
counts = np.round(counts).astype(np.int32)
# Note that we can get values outside of the value range for counts
# here if a StochasticParameter was provided, e.g.
# Deterministic(1) is currently not verified.
counts = np.clip(counts,
self.counts_value_range[0],
self.counts_value_range[1])
return counts
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
rss = random_state.duplicate(1 + len(images))
counts = self._draw_samples(len(images), rss[-1])
for i, image in enumerate(images):
batch.images[i] = self._augment_single_image(image, counts[i],
rss[i])
return batch
def _augment_single_image(self, image, counts, random_state):
# pylint: disable=protected-access, invalid-name
assert image.shape[-1] in [1, 3, 4], (
"Expected image with 1, 3 or 4 channels, "
"got %d (shape: %s)." % (image.shape[-1], image.shape))
orig_shape = image.shape
image = self._ensure_max_size(
image, self.max_size, self.interpolation)
if image.shape[-1] == 1:
# 2D image
image_aug = self._quantize(image, counts)
else:
# 3D image with 3 or 4 channels
alpha_channel = None
if image.shape[-1] == 4:
alpha_channel = image[:, :, 3:4]
image = image[:, :, 0:3]
if self.to_colorspace is None:
cs = meta.Identity()
cs_inv = meta.Identity()
else:
# TODO quite hacky to recover the sampled to_colorspace here
# by accessing _draw_samples(). Would be better to have
# an inverse augmentation method in ChangeColorspace.
# We use random_state.copy() in this method, but that is not
# expected to cause unchanged an random_state, because
# _augment_batch_() uses an un-copied one for _draw_samples()
cs = ChangeColorspace(
from_colorspace=self.from_colorspace,
to_colorspace=self.to_colorspace,
random_state=random_state.copy(),)
_, to_colorspaces = cs._draw_samples(
1, random_state.copy())
cs_inv = ChangeColorspace(
from_colorspace=to_colorspaces[0],
to_colorspace=self.from_colorspace,
random_state=random_state.copy())
image_tf = cs.augment_image(image)
image_tf_aug = self._quantize(image_tf, counts)
image_aug = cs_inv.augment_image(image_tf_aug)
if alpha_channel is not None:
image_aug = np.concatenate([image_aug, alpha_channel], axis=2)
if orig_shape != image_aug.shape:
image_aug = ia.imresize_single_image(
image_aug,
orig_shape[0:2],
interpolation=self.interpolation)
return image_aug
@abstractmethod
def _quantize(self, image, counts):
"""Apply the augmenter-specific quantization function to an image."""
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.counts,
self.from_colorspace,
self.to_colorspace,
self.max_size,
self.interpolation]
# TODO this is the same function as in Superpixels._ensure_max_size
# make DRY
@classmethod
def _ensure_max_size(cls, image, max_size, interpolation):
if max_size is not None:
size = max(image.shape[0], image.shape[1])
if size > max_size:
resize_factor = max_size / size
new_height = int(image.shape[0] * resize_factor)
new_width = int(image.shape[1] * resize_factor)
image = ia.imresize_single_image(
image,
(new_height, new_width),
interpolation=interpolation)
return image
class KMeansColorQuantization(_AbstractColorQuantization):
"""
Quantize colors using k-Means clustering.
This "collects" the colors from the input image, groups them into
``k`` clusters using k-Means clustering and replaces the colors in the
input image using the cluster centroids.
This is slower than ``UniformColorQuantization``, but adapts dynamically
to the color range in the input image.
.. note::
This augmenter expects input images to be either grayscale
or to have 3 or 4 channels and use colorspace `from_colorspace`. If
images have 4 channels, it is assumed that the 4th channel is an alpha
channel and it will not be quantized.
**Supported dtypes**:
if (image size <= max_size):
minimum of (
:class:`~imgaug.augmenters.color.ChangeColorspace`,
:func:`~imgaug.augmenters.color.quantize_kmeans`
)
if (image size > max_size):
minimum of (
:class:`~imgaug.augmenters.color.ChangeColorspace`,
:func:`~imgaug.augmenters.color.quantize_kmeans`,
:func:`~imgaug.imgaug.imresize_single_image`
)
Parameters
----------
n_colors : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Target number of colors in the generated output image.
This corresponds to the number of clusters in k-Means, i.e. ``k``.
Sampled values below ``2`` will always be clipped to ``2``.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
to_colorspace : None or str or list of str or imgaug.parameters.StochasticParameter
The colorspace in which to perform the quantization.
See :func:`~imgaug.augmenters.color.change_colorspace_` for valid values.
This will be ignored for grayscale input images.
* If ``None`` the colorspace of input images will not be changed.
* If a string, it must be among the allowed colorspaces.
* If a list, it is expected to be a list of strings, each one
being an allowed colorspace. A random element from the list
will be chosen per image.
* If a StochasticParameter, it is expected to return string. A new
sample will be drawn per image.
from_colorspace : str, optional
The colorspace of the input images.
See `to_colorspace`. Only a single string is allowed.
max_size : int or None, optional
Maximum image size at which to perform the augmentation.
If the width or height of an image exceeds this value, it will be
downscaled before running the augmentation so that the longest side
matches `max_size`.
This is done to speed up the augmentation. The final output image has
the same size as the input image. Use ``None`` to apply no downscaling.
interpolation : int or str, optional
Interpolation method to use during downscaling when `max_size` is
exceeded. Valid methods are the same as in
:func:`~imgaug.imgaug.imresize_single_image`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.KMeansColorQuantization()
Create an augmenter to apply k-Means color quantization to images using a
random amount of colors, sampled uniformly from the interval ``[2..16]``.
It assumes the input image colorspace to be ``RGB`` and clusters colors
randomly in ``RGB`` or ``Lab`` colorspace.
>>> aug = iaa.KMeansColorQuantization(n_colors=8)
Create an augmenter that quantizes images to (up to) eight colors.
>>> aug = iaa.KMeansColorQuantization(n_colors=(4, 16))
Create an augmenter that quantizes images to (up to) ``n`` colors,
where ``n`` is randomly and uniformly sampled from the discrete interval
``[4..16]``.
>>> aug = iaa.KMeansColorQuantization(
>>> from_colorspace=iaa.CSPACE_BGR)
Create an augmenter that quantizes input images that are in
``BGR`` colorspace. The quantization happens in ``RGB`` or ``Lab``
colorspace, into which the images are temporarily converted.
>>> aug = iaa.KMeansColorQuantization(
>>> to_colorspace=[iaa.CSPACE_RGB, iaa.CSPACE_HSV])
Create an augmenter that quantizes images by clustering colors randomly
in either ``RGB`` or ``HSV`` colorspace. The assumed input colorspace
of images is ``RGB``.
"""
def __init__(self, n_colors=(2, 16), from_colorspace=CSPACE_RGB,
to_colorspace=[CSPACE_RGB, CSPACE_Lab],
max_size=128, interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
super(KMeansColorQuantization, self).__init__(
counts=n_colors,
from_colorspace=from_colorspace,
to_colorspace=to_colorspace,
max_size=max_size,
interpolation=interpolation,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
@property
def n_colors(self):
"""Alias for property ``counts``.
Added in 0.4.0.
"""
return self.counts
def _quantize(self, image, counts):
return quantize_kmeans(image, counts)
@ia.deprecated("imgaug.augmenters.colors.quantize_kmeans")
def quantize_colors_kmeans(image, n_colors, n_max_iter=10, eps=1.0):
"""Outdated name of :func:`quantize_kmeans`.
Deprecated since 0.4.0.
"""
return quantize_kmeans(arr=image, nb_clusters=n_colors,
nb_max_iter=n_max_iter, eps=eps)
def quantize_kmeans(arr, nb_clusters, nb_max_iter=10, eps=1.0):
"""Quantize an array into N bins using k-means clustering.
If the input is an image, this method returns in an image with a maximum
of ``N`` colors. Similar colors are grouped to their mean. The k-means
clustering happens across channels and not channelwise.
Code similar to https://docs.opencv.org/3.0-beta/doc/py_tutorials/py_ml/
py_kmeans/py_kmeans_opencv/py_kmeans_opencv.html
.. warning::
This function currently changes the RNG state of both OpenCV's
internal RNG and imgaug's global RNG. This is necessary in order
to ensure that the k-means clustering happens deterministically.
Added in 0.4.0. (Previously called ``quantize_colors_kmeans()``.)
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
arr : ndarray
Array to quantize. Expected to be of shape ``(H,W)`` or ``(H,W,C)``
with ``C`` usually being ``1`` or ``3``.
nb_clusters : int
Number of clusters to quantize into, i.e. ``k`` in k-means clustering.
This corresponds to the maximum number of colors in an output image.
nb_max_iter : int, optional
Maximum number of iterations that the k-means clustering algorithm
is run.
eps : float, optional
Minimum change of all clusters per k-means iteration. If all clusters
change by less than this amount in an iteration, the clustering is
stopped.
Returns
-------
ndarray
Image with quantized colors.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> import numpy as np
>>> image = np.arange(4 * 4 * 3, dtype=np.uint8).reshape((4, 4, 3))
>>> image_quantized = iaa.quantize_kmeans(image, 6)
Generates a ``4x4`` image with ``3`` channels, containing consecutive
values from ``0`` to ``4*4*3``, leading to an equal number of colors.
These colors are then quantized so that only ``6`` are remaining. Note
that the six remaining colors do have to appear in the input image.
"""
iadt.allow_only_uint8({arr.dtype})
assert arr.ndim in [2, 3], (
"Expected two- or three-dimensional array shape, "
"got shape %s." % (arr.shape,))
assert 2 <= nb_clusters <= 256, (
"Expected nb_clusters to be in the discrete interval [2..256]. "
"Got a value of %d instead." % (nb_clusters,))
# without this check, kmeans throws an exception
n_pixels = np.prod(arr.shape[0:2])
if nb_clusters >= n_pixels:
return np.copy(arr)
nb_channels = 1 if arr.ndim == 2 else arr.shape[-1]
pixel_vectors = arr.reshape((-1, nb_channels)).astype(np.float32)
criteria = (cv2.TERM_CRITERIA_MAX_ITER + cv2.TERM_CRITERIA_EPS,
nb_max_iter, eps)
attempts = 1
# We want our quantization function to be deterministic (so that the
# augmenter using it can also be executed deterministically). Hence we
# set the RGN seed here.
# This is fairly ugly, but in cv2 there seems to be no other way to
# achieve determinism. Using cv2.KMEANS_PP_CENTERS does not help, as it
# is non-deterministic (tested). In C++ the function has an rgn argument,
# but not in python. In python there also seems to be no way to read out
# cv2's RNG state, so we can't set it back after executing this function.
# TODO this is quite hacky
cv2.setRNGSeed(1)
_compactness, labels, centers = cv2.kmeans(
pixel_vectors, nb_clusters, None, criteria, attempts,
cv2.KMEANS_RANDOM_CENTERS)
# TODO replace by sample_seed function
# cv2 seems to be able to handle SEED_MAX_VALUE (tested) but not floats
cv2.setRNGSeed(iarandom.get_global_rng().generate_seed_())
# Convert back to uint8 (or whatever the image dtype was) and to input
# image shape
centers_uint8 = np.array(centers, dtype=arr.dtype)
quantized_flat = centers_uint8[labels.flatten()]
return quantized_flat.reshape(arr.shape)
class UniformColorQuantization(_AbstractColorQuantization):
"""Quantize colors into N bins with regular distance.
For ``uint8`` images the equation is ``floor(v/q)*q + q/2`` with
``q = 256/N``, where ``v`` is a pixel intensity value and ``N`` is
the target number of colors after quantization.
This augmenter is faster than ``KMeansColorQuantization``, but the
set of possible output colors is constant (i.e. independent of the
input images). It may produce unsatisfying outputs for input images
that are made up of very similar colors.
.. note::
This augmenter expects input images to be either grayscale
or to have 3 or 4 channels and use colorspace `from_colorspace`. If
images have 4 channels, it is assumed that the 4th channel is an alpha
channel and it will not be quantized.
**Supported dtypes**:
if (image size <= max_size):
minimum of (
:class:`~imgaug.augmenters.color.ChangeColorspace`,
:func:`~imgaug.augmenters.color.quantize_uniform_`
)
if (image size > max_size):
minimum of (
:class:`~imgaug.augmenters.color.ChangeColorspace`,
:func:`~imgaug.augmenters.color.quantize_uniform_`,
:func:`~imgaug.imgaug.imresize_single_image`
)
Parameters
----------
n_colors : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Target number of colors to use in the generated output image.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
to_colorspace : None or str or list of str or imgaug.parameters.StochasticParameter
The colorspace in which to perform the quantization.
See :func:`~imgaug.augmenters.color.change_colorspace_` for valid values.
This will be ignored for grayscale input images.
* If ``None`` the colorspace of input images will not be changed.
* If a string, it must be among the allowed colorspaces.
* If a list, it is expected to be a list of strings, each one
being an allowed colorspace. A random element from the list
will be chosen per image.
* If a StochasticParameter, it is expected to return string. A new
sample will be drawn per image.
from_colorspace : str, optional
The colorspace of the input images.
See `to_colorspace`. Only a single string is allowed.
max_size : None or int, optional
Maximum image size at which to perform the augmentation.
If the width or height of an image exceeds this value, it will be
downscaled before running the augmentation so that the longest side
matches `max_size`.
This is done to speed up the augmentation. The final output image has
the same size as the input image. Use ``None`` to apply no downscaling.
interpolation : int or str, optional
Interpolation method to use during downscaling when `max_size` is
exceeded. Valid methods are the same as in
:func:`~imgaug.imgaug.imresize_single_image`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.UniformColorQuantization()
Create an augmenter to apply uniform color quantization to images using a
random amount of colors, sampled uniformly from the discrete interval
``[2..16]``.
>>> aug = iaa.UniformColorQuantization(n_colors=8)
Create an augmenter that quantizes images to (up to) eight colors.
>>> aug = iaa.UniformColorQuantization(n_colors=(4, 16))
Create an augmenter that quantizes images to (up to) ``n`` colors,
where ``n`` is randomly and uniformly sampled from the discrete interval
``[4..16]``.
>>> aug = iaa.UniformColorQuantization(
>>> from_colorspace=iaa.CSPACE_BGR,
>>> to_colorspace=[iaa.CSPACE_RGB, iaa.CSPACE_HSV])
Create an augmenter that uniformly quantizes images in either ``RGB``
or ``HSV`` colorspace (randomly picked per image). The input colorspace
of all images has to be ``BGR``.
"""
def __init__(self,
n_colors=(2, 16),
from_colorspace=CSPACE_RGB,
to_colorspace=None,
max_size=None,
interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
super(UniformColorQuantization, self).__init__(
counts=n_colors,
from_colorspace=from_colorspace,
to_colorspace=to_colorspace,
max_size=max_size,
interpolation=interpolation,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
@property
def n_colors(self):
"""Alias for property ``counts``.
Added in 0.4.0.
"""
return self.counts
def _quantize(self, image, counts):
return quantize_uniform_(image, counts)
class UniformColorQuantizationToNBits(_AbstractColorQuantization):
"""Quantize images by setting ``8-B`` bits of each component to zero.
This augmenter sets the ``8-B`` highest frequency (rightmost) bits of
each array component to zero. For ``B`` bits this is equivalent to
changing each component's intensity value ``v`` to
``v' = v & (2**(8-B) - 1)``, e.g. for ``B=3`` this results in
``v' = c & ~(2**(3-1) - 1) = c & ~3 = c & ~0000 0011 = c & 1111 1100``.
This augmenter behaves for ``B`` similarly to
``UniformColorQuantization(2**B)``, but quantizes each bin with interval
``(a, b)`` to ``a`` instead of to ``a + (b-a)/2``.
This augmenter is comparable to :func:`PIL.ImageOps.posterize`.
.. note::
This augmenter expects input images to be either grayscale
or to have 3 or 4 channels and use colorspace `from_colorspace`. If
images have 4 channels, it is assumed that the 4th channel is an alpha
channel and it will not be quantized.
Added in 0.4.0.
**Supported dtypes**:
if (image size <= max_size):
minimum of (
:class:`~imgaug.augmenters.color.ChangeColorspace`,
:func:`~imgaug.augmenters.color.quantize_uniform`
)
if (image size > max_size):
minimum of (
:class:`~imgaug.augmenters.color.ChangeColorspace`,
:func:`~imgaug.augmenters.color.quantize_uniform`,
:func:`~imgaug.imgaug.imresize_single_image`
)
Parameters
----------
nb_bits : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of bits to keep in each image's array component.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
to_colorspace : None or str or list of str or imgaug.parameters.StochasticParameter
The colorspace in which to perform the quantization.
See :func:`~imgaug.augmenters.color.change_colorspace_` for valid values.
This will be ignored for grayscale input images.
* If ``None`` the colorspace of input images will not be changed.
* If a string, it must be among the allowed colorspaces.
* If a list, it is expected to be a list of strings, each one
being an allowed colorspace. A random element from the list
will be chosen per image.
* If a StochasticParameter, it is expected to return string. A new
sample will be drawn per image.
from_colorspace : str, optional
The colorspace of the input images.
See `to_colorspace`. Only a single string is allowed.
max_size : None or int, optional
Maximum image size at which to perform the augmentation.
If the width or height of an image exceeds this value, it will be
downscaled before running the augmentation so that the longest side
matches `max_size`.
This is done to speed up the augmentation. The final output image has
the same size as the input image. Use ``None`` to apply no downscaling.
interpolation : int or str, optional
Interpolation method to use during downscaling when `max_size` is
exceeded. Valid methods are the same as in
:func:`~imgaug.imgaug.imresize_single_image`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.UniformColorQuantizationToNBits()
Create an augmenter to apply uniform color quantization to images using a
random amount of bits to remove, sampled uniformly from the discrete
interval ``[1..8]``.
>>> aug = iaa.UniformColorQuantizationToNBits(nb_bits=(2, 8))
Create an augmenter that quantizes images by removing ``8-B`` rightmost
bits from each component, where ``B`` is uniformly sampled from the
discrete interval ``[2..8]``.
>>> aug = iaa.UniformColorQuantizationToNBits(
>>> from_colorspace=iaa.CSPACE_BGR,
>>> to_colorspace=[iaa.CSPACE_RGB, iaa.CSPACE_HSV])
Create an augmenter that uniformly quantizes images in either ``RGB``
or ``HSV`` colorspace (randomly picked per image). The input colorspace
of all images has to be ``BGR``.
"""
# Added in 0.4.0.
def __init__(self,
nb_bits=(1, 8),
from_colorspace=CSPACE_RGB,
to_colorspace=None,
max_size=None,
interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=dangerous-default-value
# wrt value range: for discrete params, (1, 8) results in
# DiscreteUniform with interval [1, 8]
super(UniformColorQuantizationToNBits, self).__init__(
counts=nb_bits,
counts_value_range=(1, 8),
from_colorspace=from_colorspace,
to_colorspace=to_colorspace,
max_size=max_size,
interpolation=interpolation,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def _quantize(self, image, counts):
return quantize_uniform_to_n_bits_(image, counts)
class Posterize(UniformColorQuantizationToNBits):
"""Alias for :class:`UniformColorQuantizationToNBits`.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.UniformColorQuantizationToNBits`.
"""
@ia.deprecated("imgaug.augmenters.colors.quantize_uniform")
def quantize_colors_uniform(image, n_colors):
"""Outdated name for :func:`quantize_uniform`.
Deprecated since 0.4.0.
"""
return quantize_uniform(arr=image, nb_bins=n_colors)
def quantize_uniform(arr, nb_bins, to_bin_centers=True):
"""Quantize an array into N equally-sized bins.
See :func:`quantize_uniform_` for details.
Added in 0.4.0. (Previously called ``quantize_colors_uniform()``.)
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.quantize_uniform_`.
Parameters
----------
arr : ndarray
See :func:`quantize_uniform_`.
nb_bins : int
See :func:`quantize_uniform_`.
to_bin_centers : bool
See :func:`quantize_uniform_`.
Returns
-------
ndarray
Array with quantized components.
"""
return quantize_uniform_(np.copy(arr),
nb_bins=nb_bins,
to_bin_centers=to_bin_centers)
def quantize_uniform_(arr, nb_bins, to_bin_centers=True):
"""Quantize an array into N equally-sized bins in-place.
This can be used to quantize/posterize an image into N colors.
For ``uint8`` arrays the equation is ``floor(v/q)*q + q/2`` with
``q = 256/N``, where ``v`` is a pixel intensity value and ``N`` is
the target number of bins (roughly matches number of colors) after
quantization.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
arr : ndarray
Array to quantize, usually an image. Expected to be of shape ``(H,W)``
or ``(H,W,C)`` with ``C`` usually being ``1`` or ``3``.
This array *may* be changed in-place.
nb_bins : int
Number of equally-sized bins to quantize into. This corresponds to
the maximum number of colors in an output image.
to_bin_centers : bool
Whether to quantize each bin ``(a, b)`` to ``a + (b-a)/2`` (center
of bin, ``True``) or to ``a`` (lower boundary, ``False``).
Returns
-------
ndarray
Array with quantized components. This *may* be the input array with
components changed in-place.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> import numpy as np
>>> image = np.arange(4 * 4 * 3, dtype=np.uint8).reshape((4, 4, 3))
>>> image_quantized = iaa.quantize_uniform_(np.copy(image), 6)
Generates a ``4x4`` image with ``3`` channels, containing consecutive
values from ``0`` to ``4*4*3``, leading to an equal number of colors.
Each component is then quantized into one of ``6`` bins that regularly
split up the value range of ``[0..255]``, i.e. the resolution w.r.t. to
the value range is reduced.
"""
if nb_bins == 256 or 0 in arr.shape:
return arr
# TODO remove dtype check here? apply_lut_() does that already
iadt.allow_only_uint8({arr.dtype})
assert 2 <= nb_bins <= 256, (
"Expected nb_bins to be in the discrete interval [2..256]. "
"Got a value of %d instead." % (nb_bins,))
table_class = (_QuantizeUniformCenterizedLUTTableSingleton
if to_bin_centers
else _QuantizeUniformNotCenterizedLUTTableSingleton)
table = (table_class
.get_instance()
.get_for_nb_bins(nb_bins))
arr = ia.apply_lut_(arr, table)
return arr
# Added in 0.4.0.
class _QuantizeUniformCenterizedLUTTableSingleton(object):
_INSTANCE = None
@classmethod
def get_instance(cls):
"""Get singleton instance of :class:`_QuantizeUniformLUTTable`.
Added in 0.4.0.
Returns
-------
_QuantizeUniformLUTTable
The global instance of :class:`_QuantizeUniformLUTTable`.
"""
if cls._INSTANCE is None:
cls._INSTANCE = _QuantizeUniformLUTTable(centerize=True)
return cls._INSTANCE
# Added in 0.4.0.
class _QuantizeUniformNotCenterizedLUTTableSingleton(object):
"""Table for :func:`quantize_uniform` with ``to_bin_centers=False``."""
_INSTANCE = None
@classmethod
def get_instance(cls):
"""Get singleton instance of :class:`_QuantizeUniformLUTTable`.
Added in 0.4.0.
Returns
-------
_QuantizeUniformLUTTable
The global instance of :class:`_QuantizeUniformLUTTable`.
"""
if cls._INSTANCE is None:
cls._INSTANCE = _QuantizeUniformLUTTable(centerize=False)
return cls._INSTANCE
# Added in 0.4.0.
class _QuantizeUniformLUTTable(object):
def __init__(self, centerize):
self.table = self._generate_quantize_uniform_table(centerize)
def get_for_nb_bins(self, nb_bins):
"""Get LUT ndarray for a provided number of bins.
Added in 0.4.0.
"""
return self.table[nb_bins, :]
# Added in 0.4.0.
@classmethod
def _generate_quantize_uniform_table(cls, centerize):
# For simplicity, we generate here the tables for nb_bins=0 (results
# in all zeros) and nb_bins=256 too, even though these should usually
# not be requested.
table = np.arange(0, 256).astype(np.float32)
table_all_nb_bins = np.zeros((256, 256), dtype=np.float32)
# This loop could be done a little bit faster by vectorizing it.
# It is expected to be run exactly once per run of a whole script,
# making the difference negligible.
for nb_bins in np.arange(1, 255).astype(np.uint8):
binsize = 256 / nb_bins
table_q_f32 = np.floor(table / binsize) * binsize
if centerize:
table_q_f32 = table_q_f32 + binsize/2
table_all_nb_bins[nb_bins] = table_q_f32
table_all_nb_bins = np.clip(
np.round(table_all_nb_bins), 0, 255).astype(np.uint8)
return table_all_nb_bins
def quantize_uniform_to_n_bits(arr, nb_bits):
"""Reduce each component in an array to a maximum number of bits.
See :func:`quantize_uniform_to_n_bits` for details.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.quantize_uniform_to_n_bits_`.
Parameters
----------
arr : ndarray
See :func:`quantize_uniform_to_n_bits`.
nb_bits : int
See :func:`quantize_uniform_to_n_bits`.
Returns
-------
ndarray
Array with quantized components.
"""
return quantize_uniform_to_n_bits_(np.copy(arr), nb_bits=nb_bits)
def quantize_uniform_to_n_bits_(arr, nb_bits):
"""Reduce each component in an array to a maximum number of bits in-place.
This operation sets the ``8-B`` highest frequency (rightmost) bits to zero.
For ``B`` bits this is equivalent to changing each component's intensity
value ``v`` to ``v' = v & (2**(8-B) - 1)``, e.g. for ``B=3`` this results
in ``v' = c & ~(2**(3-1) - 1) = c & ~3 = c & ~0000 0011 = c & 1111 1100``.
This is identical to :func:`quantize_uniform` with ``nb_bins=2**nb_bits``
and ``to_bin_centers=False``.
This function produces the same outputs as :func:`PIL.ImageOps.posterize`,
but is significantly faster.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.quantize_uniform_`.
Parameters
----------
arr : ndarray
Array to quantize, usually an image. Expected to be of shape ``(H,W)``
or ``(H,W,C)`` with ``C`` usually being ``1`` or ``3``.
This array *may* be changed in-place.
nb_bits : int
Number of bits to keep in each array component.
Returns
-------
ndarray
Array with quantized components. This *may* be the input array with
components changed in-place.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> import numpy as np
>>> image = np.arange(4 * 4 * 3, dtype=np.uint8).reshape((4, 4, 3))
>>> image_quantized = iaa.quantize_uniform_to_n_bits_(np.copy(image), 6)
Generates a ``4x4`` image with ``3`` channels, containing consecutive
values from ``0`` to ``4*4*3``, leading to an equal number of colors.
These colors are then quantized so that each component's ``8-6=2``
rightmost bits are set to zero.
"""
assert 1 <= nb_bits <= 8, (
"Expected nb_bits to be in the discrete interval [1..8]. "
"Got a value of %d instead." % (nb_bits,))
return quantize_uniform_(arr, nb_bins=2**nb_bits, to_bin_centers=False)
def posterize(arr, nb_bits):
"""Alias for :func:`quantize_uniform_to_n_bits`.
This function is an alias for :func:`quantize_uniform_to_n_bits` and was
added for users familiar with the same function in PIL.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.quantize_uniform_to_n_bits`.
Parameters
----------
arr : ndarray
See :func:`quantize_uniform_to_n_bits`.
nb_bits : int
See :func:`quantize_uniform_to_n_bits`.
Returns
-------
ndarray
Array with quantized components.
"""
return quantize_uniform_to_n_bits(arr, nb_bits)
================================================
FILE: imgaug/augmenters/contrast.py
================================================
"""
Augmenters that perform contrast changes.
List of augmenters:
* :class:`GammaContrast`
* :class:`SigmoidContrast`
* :class:`LogContrast`
* :class:`LinearContrast`
* :class:`AllChannelsHistogramEqualization`
* :class:`HistogramEqualization`
* :class:`AllChannelsCLAHE`
* :class:`CLAHE`
"""
from __future__ import print_function, division, absolute_import
import numpy as np
import six.moves as sm
import skimage.exposure as ski_exposure
import cv2
import imgaug as ia
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from . import color as color_lib
from .. import parameters as iap
from .. import dtypes as iadt
from ..augmentables.batches import _BatchInAugmentation
class _ContrastFuncWrapper(meta.Augmenter):
def __init__(self, func, params1d, per_channel, dtypes_allowed=None,
dtypes_disallowed=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(_ContrastFuncWrapper, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.func = func
self.params1d = params1d
self.per_channel = iap.handle_probability_param(per_channel,
"per_channel")
self.dtypes_allowed = dtypes_allowed
self.dtypes_disallowed = dtypes_disallowed
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
if self.dtypes_allowed is not None:
iadt.gate_dtypes_strs(
images,
allowed=self.dtypes_allowed,
disallowed=self.dtypes_disallowed,
augmenter=self
)
nb_images = len(images)
rss = random_state.duplicate(1+nb_images)
per_channel = self.per_channel.draw_samples((nb_images,),
random_state=rss[0])
gen = enumerate(zip(images, per_channel, rss[1:]))
for i, (image, per_channel_i, rs) in gen:
nb_channels = 1 if per_channel_i <= 0.5 else image.shape[2]
# TODO improve efficiency by sampling once
samples_i = [
param.draw_samples((nb_channels,), random_state=rs)
for param in self.params1d]
if per_channel_i > 0.5:
input_dtype = image.dtype
# TODO This was previously a cast of image to float64. Do the
# adjust_* functions return float64?
result = []
for c in sm.xrange(nb_channels):
samples_i_c = [sample_i[c] for sample_i in samples_i]
args = tuple([image[..., c]] + samples_i_c)
result.append(self.func(*args))
image_aug = np.stack(result, axis=-1)
image_aug = image_aug.astype(input_dtype)
else:
# don't use something like samples_i[...][0] here, because
# that returns python scalars and is slightly less accurate
# than keeping the numpy values
args = tuple([image] + samples_i)
image_aug = self.func(*args)
batch.images[i] = image_aug
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return self.params1d
# TODO quite similar to the other adjust_contrast_*() functions, make DRY
def adjust_contrast_gamma(arr, gamma):
"""
Adjust image contrast by scaling pixel values to ``255*((v/255)**gamma)``.
**Supported dtypes**:
* ``uint8``: yes; fully tested (1) (2) (3)
* ``uint16``: yes; tested (2) (3)
* ``uint32``: yes; tested (2) (3)
* ``uint64``: yes; tested (2) (3) (4)
* ``int8``: limited; tested (2) (3) (5)
* ``int16``: limited; tested (2) (3) (5)
* ``int32``: limited; tested (2) (3) (5)
* ``int64``: limited; tested (2) (3) (4) (5)
* ``float16``: limited; tested (5)
* ``float32``: limited; tested (5)
* ``float64``: limited; tested (5)
* ``float128``: no (6)
* ``bool``: no (7)
- (1) Handled by ``cv2``. Other dtypes are handled by ``skimage``.
- (2) Normalization is done as ``I_ij/max``, where ``max`` is the
maximum value of the dtype, e.g. 255 for ``uint8``. The
normalization is reversed afterwards, e.g. ``result*255`` for
``uint8``.
- (3) Integer-like values are not rounded after applying the contrast
adjustment equation (before inverting the normalization to
``[0.0, 1.0]`` space), i.e. projection from continuous
space to discrete happens according to floor function.
- (4) Note that scikit-image doc says that integers are converted to
``float64`` values before applying the contrast normalization
method. This might lead to inaccuracies for large 64bit integer
values. Tests showed no indication of that happening though.
- (5) Must not contain negative values. Values >=0 are fully supported.
- (6) Leads to error in scikit-image.
- (7) Does not make sense for contrast adjustments.
Parameters
----------
arr : numpy.ndarray
Array for which to adjust the contrast. Dtype ``uint8`` is fastest.
gamma : number
Exponent for the contrast adjustment. Higher values darken the image.
Returns
-------
numpy.ndarray
Array with adjusted contrast.
"""
if arr.size == 0:
return np.copy(arr)
# int8 is also possible according to docs
# https://docs.opencv.org/3.0-beta/modules/core/doc/operations_on_arrays.html#cv2.LUT ,
# but here it seemed like `d` was 0 for CV_8S, causing that to fail
if arr.dtype == iadt._UINT8_DTYPE:
min_value, _center_value, max_value = \
iadt.get_value_range_of_dtype(arr.dtype)
dynamic_range = max_value - min_value
value_range = np.linspace(0, 1.0, num=dynamic_range+1,
dtype=np.float32)
# 255 * ((I_ij/255)**gamma)
# using np.float32(.) here still works when the input is a numpy array
# of size 1
table = (min_value
+ (value_range ** np.float32(gamma))
* dynamic_range)
table = np.clip(table, min_value, max_value).astype(arr.dtype)
arr_aug = ia.apply_lut(arr, table)
return arr_aug
return ski_exposure.adjust_gamma(arr, gamma)
# TODO quite similar to the other adjust_contrast_*() functions, make DRY
def adjust_contrast_sigmoid(arr, gain, cutoff):
"""
Adjust image contrast to ``255*1/(1+exp(gain*(cutoff-I_ij/255)))``.
**Supported dtypes**:
* ``uint8``: yes; fully tested (1) (2) (3)
* ``uint16``: yes; tested (2) (3)
* ``uint32``: yes; tested (2) (3)
* ``uint64``: yes; tested (2) (3) (4)
* ``int8``: limited; tested (2) (3) (5)
* ``int16``: limited; tested (2) (3) (5)
* ``int32``: limited; tested (2) (3) (5)
* ``int64``: limited; tested (2) (3) (4) (5)
* ``float16``: limited; tested (5)
* ``float32``: limited; tested (5)
* ``float64``: limited; tested (5)
* ``float128``: no (6)
* ``bool``: no (7)
- (1) Handled by ``cv2``. Other dtypes are handled by ``skimage``.
- (2) Normalization is done as ``I_ij/max``, where ``max`` is the
maximum value of the dtype, e.g. 255 for ``uint8``. The
normalization is reversed afterwards, e.g. ``result*255``
for ``uint8``.
- (3) Integer-like values are not rounded after applying the contrast
adjustment equation before inverting the normalization
to ``[0.0, 1.0]`` space), i.e. projection from continuous
space to discrete happens according to floor function.
- (4) Note that scikit-image doc says that integers are converted to
``float64`` values before applying the contrast normalization
method. This might lead to inaccuracies for large 64bit integer
values. Tests showed no indication of that happening though.
- (5) Must not contain negative values. Values >=0 are fully supported.
- (6) Leads to error in scikit-image.
- (7) Does not make sense for contrast adjustments.
Parameters
----------
arr : numpy.ndarray
Array for which to adjust the contrast. Dtype ``uint8`` is fastest.
gain : number
Multiplier for the sigmoid function's output.
Higher values lead to quicker changes from dark to light pixels.
cutoff : number
Cutoff that shifts the sigmoid function in horizontal direction.
Higher values mean that the switch from dark to light pixels happens
later, i.e. the pixels will remain darker.
Returns
-------
numpy.ndarray
Array with adjusted contrast.
"""
if arr.size == 0:
return np.copy(arr)
# int8 is also possible according to docs
# https://docs.opencv.org/3.0-beta/modules/core/doc/operations_on_arrays.html#cv2.LUT ,
# but here it seemed like `d` was 0 for CV_8S, causing that to fail
if arr.dtype == iadt._UINT8_DTYPE:
min_value, _center_value, max_value = \
iadt.get_value_range_of_dtype(arr.dtype)
dynamic_range = max_value - min_value
value_range = np.linspace(0, 1.0, num=dynamic_range+1,
dtype=np.float32)
# 255 * 1/(1 + exp(gain*(cutoff - I_ij/255)))
# using np.float32(.) here still works when the input is a numpy array
# of size 1
gain = np.float32(gain)
cutoff = np.float32(cutoff)
table = (min_value
+ dynamic_range
* 1/(1 + np.exp(gain * (cutoff - value_range))))
table = np.clip(table, min_value, max_value).astype(arr.dtype)
arr_aug = ia.apply_lut(arr, table)
return arr_aug
return ski_exposure.adjust_sigmoid(arr, cutoff=cutoff, gain=gain)
# TODO quite similar to the other adjust_contrast_*() functions, make DRY
# TODO add dtype gating
def adjust_contrast_log(arr, gain):
"""
Adjust image contrast by scaling pixels to ``255*gain*log_2(1+v/255)``.
**Supported dtypes**:
* ``uint8``: yes; fully tested (1) (2) (3)
* ``uint16``: yes; tested (2) (3)
* ``uint32``: no; tested (2) (3) (8)
* ``uint64``: no; tested (2) (3) (4) (8)
* ``int8``: limited; tested (2) (3) (5)
* ``int16``: limited; tested (2) (3) (5)
* ``int32``: no; tested (2) (3) (5) (8)
* ``int64``: no; tested (2) (3) (4) (5) (8)
* ``float16``: limited; tested (5)
* ``float32``: limited; tested (5)
* ``float64``: limited; tested (5)
* ``float128``: no (6)
* ``bool``: no (7)
- (1) Handled by ``cv2``. Other dtypes are handled by ``skimage``.
- (2) Normalization is done as ``I_ij/max``, where ``max`` is the
maximum value of the dtype, e.g. 255 for ``uint8``. The
normalization is reversed afterwards, e.g. ``result*255`` for
``uint8``.
- (3) Integer-like values are not rounded after applying the contrast
adjustment equation (before inverting the normalization
to ``[0.0, 1.0]`` space), i.e. projection from continuous
space to discrete happens according to floor function.
- (4) Note that scikit-image doc says that integers are converted to
``float64`` values before applying the contrast normalization
method. This might lead to inaccuracies for large 64bit integer
values. Tests showed no indication of that happening though.
- (5) Must not contain negative values. Values >=0 are fully supported.
- (6) Leads to error in scikit-image.
- (7) Does not make sense for contrast adjustments.
- (8) No longer supported since numpy 1.17. Previously: 'yes' for
``uint32``, ``uint64``; 'limited' for ``int32``, ``int64``.
Parameters
----------
arr : numpy.ndarray
Array for which to adjust the contrast. Dtype ``uint8`` is fastest.
gain : number
Multiplier for the logarithm result. Values around 1.0 lead to a
contrast-adjusted images. Values above 1.0 quickly lead to partially
broken images due to exceeding the datatype's value range.
Returns
-------
numpy.ndarray
Array with adjusted contrast.
"""
if arr.size == 0:
return np.copy(arr)
# int8 is also possible according to docs
# https://docs.opencv.org/3.0-beta/modules/core/doc/operations_on_arrays.html#cv2.LUT ,
# but here it seemed like `d` was 0 for CV_8S, causing that to fail
if arr.dtype == iadt._UINT8_DTYPE:
min_value, _center_value, max_value = \
iadt.get_value_range_of_dtype(arr.dtype)
dynamic_range = max_value - min_value
value_range = np.linspace(0, 1.0, num=dynamic_range+1,
dtype=np.float32)
# 255 * 1/(1 + exp(gain*(cutoff - I_ij/255)))
# using np.float32(.) here still works when the input is a numpy array
# of size 1
gain = np.float32(gain)
table = min_value + dynamic_range * gain * np.log2(1 + value_range)
table = np.clip(table, min_value, max_value).astype(arr.dtype)
arr_aug = ia.apply_lut(arr, table)
return arr_aug
return ski_exposure.adjust_log(arr, gain=gain)
# TODO quite similar to the other adjust_contrast_*() functions, make DRY
def adjust_contrast_linear(arr, alpha):
"""Adjust contrast by scaling each pixel to ``127 + alpha*(v-127)``.
**Supported dtypes**:
* ``uint8``: yes; fully tested (1) (2)
* ``uint16``: yes; tested (2)
* ``uint32``: yes; tested (2)
* ``uint64``: no (3)
* ``int8``: yes; tested (2)
* ``int16``: yes; tested (2)
* ``int32``: yes; tested (2)
* ``int64``: no (2)
* ``float16``: yes; tested (2)
* ``float32``: yes; tested (2)
* ``float64``: yes; tested (2)
* ``float128``: no (2)
* ``bool``: no (4)
- (1) Handled by ``cv2``. Other dtypes are handled by raw ``numpy``.
- (2) Only tested for reasonable alphas with up to a value of
around ``100``.
- (3) Conversion to ``float64`` is done during augmentation, hence
``uint64``, ``int64``, and ``float128`` support cannot be
guaranteed.
- (4) Does not make sense for contrast adjustments.
Parameters
----------
arr : numpy.ndarray
Array for which to adjust the contrast. Dtype ``uint8`` is fastest.
alpha : number
Multiplier to linearly pronounce (``>1.0``), dampen (``0.0`` to
``1.0``) or invert (``<0.0``) the difference between each pixel value
and the dtype's center value, e.g. ``127`` for ``uint8``.
Returns
-------
numpy.ndarray
Array with adjusted contrast.
"""
# pylint: disable=no-else-return
if arr.size == 0:
return np.copy(arr)
# int8 is also possible according to docs
# https://docs.opencv.org/3.0-beta/modules/core/doc/operations_on_arrays.html#cv2.LUT ,
# but here it seemed like `d` was 0 for CV_8S, causing that to fail
if arr.dtype == iadt._UINT8_DTYPE:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(arr.dtype)
# TODO get rid of this int(...)
center_value = int(center_value)
value_range = np.arange(0, 256, dtype=np.float32)
# 127 + alpha*(I_ij-127)
# using np.float32(.) here still works when the input is a numpy array
# of size 1
alpha = np.float32(alpha)
table = center_value + alpha * (value_range - center_value)
table = np.clip(table, min_value, max_value).astype(arr.dtype)
arr_aug = ia.apply_lut(arr, table)
return arr_aug
else:
input_dtype = arr.dtype
_min_value, center_value, _max_value = \
iadt.get_value_range_of_dtype(input_dtype)
# TODO get rid of this int(...)
if input_dtype.kind in ["u", "i"]:
center_value = int(center_value)
image_aug = (center_value
+ alpha
* (arr.astype(np.float64)-center_value))
image_aug = iadt.restore_dtypes_(image_aug, input_dtype)
return image_aug
class GammaContrast(_ContrastFuncWrapper):
"""
Adjust image contrast by scaling pixel values to ``255*((v/255)**gamma)``.
Values in the range ``gamma=(0.5, 2.0)`` seem to be sensible.
**Supported dtypes**:
See :func:`~imgaug.augmenters.contrast.adjust_contrast_gamma`.
Parameters
----------
gamma : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Exponent for the contrast adjustment. Higher values darken the image.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the range ``[a, b]``
will be used per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
per_channel : bool or float, optional
Whether to use the same value for all channels (``False``) or to
sample a new value for each channel (``True``). If this value is a
float ``p``, then for ``p`` percent of all images `per_channel` will
be treated as ``True``, otherwise as ``False``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.GammaContrast((0.5, 2.0))
Modify the contrast of images according to ``255*((v/255)**gamma)``,
where ``v`` is a pixel value and ``gamma`` is sampled uniformly from
the interval ``[0.5, 2.0]`` (once per image).
>>> aug = iaa.GammaContrast((0.5, 2.0), per_channel=True)
Same as in the previous example, but ``gamma`` is sampled once per image
*and* channel.
"""
def __init__(self, gamma=(0.7, 1.7), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
params1d = [iap.handle_continuous_param(
gamma, "gamma", value_range=None, tuple_to_uniform=True,
list_to_choice=True)]
func = adjust_contrast_gamma
super(GammaContrast, self).__init__(
func, params1d, per_channel,
dtypes_allowed="uint8 uint16 uint32 uint64 int8 int16 int32 int64 "
"float16 float32 float64",
dtypes_disallowed="float128 bool",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class SigmoidContrast(_ContrastFuncWrapper):
"""
Adjust image contrast to ``255*1/(1+exp(gain*(cutoff-I_ij/255)))``.
Values in the range ``gain=(5, 20)`` and ``cutoff=(0.25, 0.75)`` seem to
be sensible.
A combination of ``gain=5.5`` and ``cutof=0.45`` is fairly close to
the identity function.
**Supported dtypes**:
See :func:`~imgaug.augmenters.contrast.adjust_contrast_sigmoid`.
Parameters
----------
gain : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier for the sigmoid function's output.
Higher values lead to quicker changes from dark to light pixels.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the interval
``[a, b]`` will be sampled uniformly per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
cutoff : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Cutoff that shifts the sigmoid function in horizontal direction.
Higher values mean that the switch from dark to light pixels happens
later, i.e. the pixels will remain darker.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the range ``[a, b]``
will be used per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
per_channel : bool or float, optional
Whether to use the same value for all channels (``False``) or to
sample a new value for each channel (``True``). If this value is a
float ``p``, then for ``p`` percent of all images `per_channel` will
be treated as ``True``, otherwise as ``False``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.SigmoidContrast(gain=(3, 10), cutoff=(0.4, 0.6))
Modify the contrast of images according to
``255*1/(1+exp(gain*(cutoff-v/255)))``, where ``v`` is a pixel value,
``gain`` is sampled uniformly from the interval ``[3, 10]`` (once per
image) and ``cutoff`` is sampled uniformly from the interval
``[0.4, 0.6]`` (also once per image).
>>> aug = iaa.SigmoidContrast(
>>> gain=(3, 10), cutoff=(0.4, 0.6), per_channel=True)
Same as in the previous example, but ``gain`` and ``cutoff`` are each
sampled once per image *and* channel.
"""
def __init__(self, gain=(5, 6), cutoff=(0.3, 0.6), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# TODO add inv parameter?
params1d = [
iap.handle_continuous_param(
gain, "gain", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True),
iap.handle_continuous_param(
cutoff, "cutoff", value_range=(0, 1.0), tuple_to_uniform=True,
list_to_choice=True)
]
func = adjust_contrast_sigmoid
super(SigmoidContrast, self).__init__(
func, params1d, per_channel,
dtypes_allowed="uint8 uint16 uint32 uint64 int8 int16 int32 int64 "
"float16 float32 float64",
dtypes_disallowed="float128 bool",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class LogContrast(_ContrastFuncWrapper):
"""Adjust image contrast by scaling pixels to ``255*gain*log_2(1+v/255)``.
This augmenter is fairly similar to
``imgaug.augmenters.arithmetic.Multiply``.
**Supported dtypes**:
See :func:`~imgaug.augmenters.contrast.adjust_contrast_log`.
Parameters
----------
gain : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier for the logarithm result. Values around ``1.0`` lead to a
contrast-adjusted images. Values above ``1.0`` quickly lead to
partially broken images due to exceeding the datatype's value range.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the interval ``[a, b]``
will uniformly sampled be used per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
per_channel : bool or float, optional
Whether to use the same value for all channels (``False``) or to
sample a new value for each channel (``True``). If this value is a
float ``p``, then for ``p`` percent of all images `per_channel` will
be treated as ``True``, otherwise as ``False``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.LogContrast(gain=(0.6, 1.4))
Modify the contrast of images according to ``255*gain*log_2(1+v/255)``,
where ``v`` is a pixel value and ``gain`` is sampled uniformly from the
interval ``[0.6, 1.4]`` (once per image).
>>> aug = iaa.LogContrast(gain=(0.6, 1.4), per_channel=True)
Same as in the previous example, but ``gain`` is sampled once per image
*and* channel.
"""
def __init__(self, gain=(0.4, 1.6), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# TODO add inv parameter?
params1d = [iap.handle_continuous_param(
gain, "gain", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True)]
func = adjust_contrast_log
super(LogContrast, self).__init__(
func, params1d, per_channel,
dtypes_allowed="uint8 uint16 uint32 uint64 int8 int16 int32 int64 "
"float16 float32 float64",
dtypes_disallowed="float128 bool",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class LinearContrast(_ContrastFuncWrapper):
"""Adjust contrast by scaling each pixel to ``127 + alpha*(v-127)``.
**Supported dtypes**:
See :func:`~imgaug.augmenters.contrast.adjust_contrast_linear`.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier to linearly pronounce (``>1.0``), dampen (``0.0`` to
``1.0``) or invert (``<0.0``) the difference between each pixel value
and the dtype's center value, e.g. ``127`` for ``uint8``.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the interval ``[a, b]``
will be used per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
per_channel : bool or float, optional
Whether to use the same value for all channels (``False``) or to
sample a new value for each channel (``True``). If this value is a
float ``p``, then for ``p`` percent of all images `per_channel` will
be treated as ``True``, otherwise as ``False``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.LinearContrast((0.4, 1.6))
Modify the contrast of images according to `127 + alpha*(v-127)``,
where ``v`` is a pixel value and ``alpha`` is sampled uniformly from the
interval ``[0.4, 1.6]`` (once per image).
>>> aug = iaa.LinearContrast((0.4, 1.6), per_channel=True)
Same as in the previous example, but ``alpha`` is sampled once per image
*and* channel.
"""
def __init__(self, alpha=(0.6, 1.4), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
params1d = [
iap.handle_continuous_param(
alpha, "alpha", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
]
func = adjust_contrast_linear
super(LinearContrast, self).__init__(
func, params1d, per_channel,
dtypes_allowed="uint8 uint16 uint32 int8 int16 int32 float16 "
"float32 float64",
dtypes_disallowed="uint64 int64 float128 bool",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO maybe offer the other contrast augmenters also wrapped in this, similar
# to CLAHE and HistogramEqualization?
# this is essentially tested by tests for CLAHE
class _IntensityChannelBasedApplier(object):
RGB = color_lib.CSPACE_RGB
BGR = color_lib.CSPACE_BGR
HSV = color_lib.CSPACE_HSV
HLS = color_lib.CSPACE_HLS
Lab = color_lib.CSPACE_Lab
_CHANNEL_MAPPING = {
HSV: 2,
HLS: 1,
Lab: 0
}
def __init__(self, from_colorspace, to_colorspace):
super(_IntensityChannelBasedApplier, self).__init__()
# TODO maybe add CIE, Luv?
valid_from_colorspaces = [self.RGB, self.BGR, self.Lab, self.HLS,
self.HSV]
assert from_colorspace in valid_from_colorspaces, (
"Expected 'from_colorspace' to be one of %s, got %s." % (
valid_from_colorspaces, from_colorspace))
valid_to_colorspaces = [self.Lab, self.HLS, self.HSV]
assert to_colorspace in valid_to_colorspaces, (
"Expected 'to_colorspace' to be one of %s, got %s." % (
valid_to_colorspaces, to_colorspace))
self.from_colorspace = from_colorspace
self.to_colorspace = to_colorspace
def apply(self, images, random_state, parents, hooks, func):
input_was_array = ia.is_np_array(images)
rss = random_state.duplicate(3)
# normalize images
# (H, W, 1) will be used directly in AllChannelsCLAHE
# (H, W, 3) will be converted to target colorspace in the next
# block
# (H, W, 4) will be reduced to (H, W, 3) (remove 4th channel) and
# converted to target colorspace in next block
# (H, W, ) will raise a warning and be treated channelwise by
# AllChannelsCLAHE
images_normalized = []
images_change_cs = []
images_change_cs_indices = []
for i, image in enumerate(images):
nb_channels = image.shape[2]
if nb_channels == 1:
images_normalized.append(image)
elif nb_channels == 3:
images_normalized.append(None)
images_change_cs.append(image)
images_change_cs_indices.append(i)
elif nb_channels == 4:
# assume that 4th channel is an alpha channel, e.g. in RGBA
images_normalized.append(None)
images_change_cs.append(image[..., 0:3])
images_change_cs_indices.append(i)
else:
ia.warn(
"Got image with %d channels in "
"_IntensityChannelBasedApplier (parents: %s), "
"expected 0, 1, 3 or 4 channels." % (
nb_channels, ", ".join(
parent.name for parent in parents)))
images_normalized.append(image)
# convert colorspaces of normalized 3-channel images
images_after_color_conversion = [None] * len(images_normalized)
if len(images_change_cs) > 0:
images_new_cs = color_lib.change_colorspaces_(
images_change_cs,
to_colorspaces=self.to_colorspace,
from_colorspaces=self.from_colorspace)
for image_new_cs, target_idx in zip(images_new_cs,
images_change_cs_indices):
chan_idx = self._CHANNEL_MAPPING[self.to_colorspace]
images_normalized[target_idx] = image_new_cs[
..., chan_idx:chan_idx+1]
images_after_color_conversion[target_idx] = image_new_cs
# apply function channelwise
images_aug = func(images_normalized, rss[1])
# denormalize
result = []
images_change_cs = []
images_change_cs_indices = []
gen = enumerate(zip(images, images_after_color_conversion, images_aug))
for i, (image, image_conv, image_aug) in gen:
nb_channels = image.shape[2]
if nb_channels in [3, 4]:
chan_idx = self._CHANNEL_MAPPING[self.to_colorspace]
image_tmp = image_conv
image_tmp[..., chan_idx:chan_idx+1] = image_aug
result.append(None if nb_channels == 3 else image[..., 3:4])
images_change_cs.append(image_tmp)
images_change_cs_indices.append(i)
else:
result.append(image_aug)
# invert colorspace conversion
if len(images_change_cs) > 0:
images_new_cs = color_lib.change_colorspaces_(
images_change_cs,
to_colorspaces=self.from_colorspace,
from_colorspaces=self.to_colorspace)
for image_new_cs, target_idx in zip(images_new_cs,
images_change_cs_indices):
if result[target_idx] is None:
result[target_idx] = image_new_cs
else:
# input image had four channels, 4th channel is already
# in result
result[target_idx] = np.dstack((image_new_cs,
result[target_idx]))
# convert to array if necessary
if input_was_array:
result = np.array(result, dtype=result[0].dtype)
return result
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.from_colorspace, self.to_colorspace]
# TODO add parameter `tile_grid_size_percent`
class AllChannelsCLAHE(meta.Augmenter):
"""Apply CLAHE to all channels of images in their original colorspaces.
CLAHE (Contrast Limited Adaptive Histogram Equalization) performs
histogram equilization within image patches, i.e. over local
neighbourhoods.
In contrast to ``imgaug.augmenters.contrast.CLAHE``, this augmenter
operates directly on all channels of the input images. It does not
perform any colorspace transformations and does not focus on specific
channels (e.g. ``L`` in ``Lab`` colorspace).
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: no (2)
* ``int16``: no (2)
* ``int32``: no (2)
* ``int64``: no (2)
* ``float16``: no (2)
* ``float32``: no (2)
* ``float64``: no (2)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) rejected by cv2
- (2) results in error in cv2: ``cv2.error:
OpenCV(3.4.2) (...)/clahe.cpp:351: error: (-215:Assertion
failed) src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3))
|| _src.type() == (((2) & ((1 << 3) - 1)) + (((1)-1) << 3)) in
function 'apply'``
Parameters
----------
clip_limit : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See ``imgaug.augmenters.contrast.CLAHE``.
tile_grid_size_px : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or tuple of tuple of int or tuple of list of int or tuple of imgaug.parameters.StochasticParameter, optional
See ``imgaug.augmenters.contrast.CLAHE``.
tile_grid_size_px_min : int, optional
See ``imgaug.augmenters.contrast.CLAHE``.
per_channel : bool or float, optional
Whether to use the same value for all channels (``False``) or to
sample a new value for each channel (``True``). If this value is a
float ``p``, then for ``p`` percent of all images `per_channel` will
be treated as ``True``, otherwise as ``False``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AllChannelsCLAHE()
Create an augmenter that applies CLAHE to all channels of input images.
>>> aug = iaa.AllChannelsCLAHE(clip_limit=(1, 10))
Same as in the previous example, but the `clip_limit` used by CLAHE is
uniformly sampled per image from the interval ``[1, 10]``. Some images
will therefore have stronger contrast than others (i.e. higher clip limit
values).
>>> aug = iaa.AllChannelsCLAHE(clip_limit=(1, 10), per_channel=True)
Same as in the previous example, but the `clip_limit` is sampled per
image *and* channel, leading to different levels of contrast for each
channel.
"""
def __init__(self, clip_limit=(0.1, 8), tile_grid_size_px=(3, 12),
tile_grid_size_px_min=3, per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AllChannelsCLAHE, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.clip_limit = iap.handle_continuous_param(
clip_limit, "clip_limit", value_range=(0+1e-4, None),
tuple_to_uniform=True, list_to_choice=True)
self.tile_grid_size_px = iap.handle_discrete_kernel_size_param(
tile_grid_size_px, "tile_grid_size_px", value_range=(0, None),
allow_floats=False)
self.tile_grid_size_px_min = tile_grid_size_px_min
self.per_channel = iap.handle_probability_param(per_channel,
"per_channel")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
iadt.gate_dtypes_strs(
images,
allowed="uint8 uint16",
disallowed="bool uint32 uint64 int8 int16 int32 int64 "
"float16 float32 float64 float128",
augmenter=self
)
nb_images = len(images)
nb_channels = meta.estimate_max_number_of_channels(images)
mode = "single" if self.tile_grid_size_px[1] is None else "two"
rss = random_state.duplicate(3 if mode == "single" else 4)
per_channel = self.per_channel.draw_samples((nb_images,),
random_state=rss[0])
clip_limit = self.clip_limit.draw_samples((nb_images, nb_channels),
random_state=rss[1])
tile_grid_size_px_h = self.tile_grid_size_px[0].draw_samples(
(nb_images, nb_channels), random_state=rss[2])
if mode == "single":
tile_grid_size_px_w = tile_grid_size_px_h
else:
tile_grid_size_px_w = self.tile_grid_size_px[1].draw_samples(
(nb_images, nb_channels), random_state=rss[3])
tile_grid_size_px_w = np.maximum(tile_grid_size_px_w,
self.tile_grid_size_px_min)
tile_grid_size_px_h = np.maximum(tile_grid_size_px_h,
self.tile_grid_size_px_min)
gen = enumerate(zip(images, clip_limit, tile_grid_size_px_h,
tile_grid_size_px_w, per_channel))
for i, (image, clip_limit_i, tgs_px_h_i, tgs_px_w_i, pchannel_i) in gen:
if image.size == 0:
continue
nb_channels = image.shape[2]
c_param = 0
image_warped = []
for c in sm.xrange(nb_channels):
if tgs_px_w_i[c_param] > 1 or tgs_px_h_i[c_param] > 1:
clahe = cv2.createCLAHE(
clipLimit=clip_limit_i[c_param],
tileGridSize=(tgs_px_w_i[c_param], tgs_px_h_i[c_param])
)
channel_warped = clahe.apply(
_normalize_cv2_input_arr_(image[..., c])
)
image_warped.append(channel_warped)
else:
image_warped.append(image[..., c])
if pchannel_i > 0.5:
c_param += 1
# combine channels to one image
image_warped = np.stack(image_warped, axis=-1)
batch.images[i] = image_warped
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.clip_limit, self.tile_grid_size_px,
self.tile_grid_size_px_min, self.per_channel]
class CLAHE(meta.Augmenter):
"""Apply CLAHE to L/V/L channels in HLS/HSV/Lab colorspaces.
This augmenter applies CLAHE (Contrast Limited Adaptive Histogram
Equalization) to images, a form of histogram equalization that normalizes
within local image patches.
The augmenter transforms input images to a target colorspace (e.g.
``Lab``), extracts an intensity-related channel from the converted
images (e.g. ``L`` for ``Lab``), applies CLAHE to the channel and then
converts the resulting image back to the original colorspace.
Grayscale images (images without channel axis or with only one channel
axis) are automatically handled, `from_colorspace` does not have to be
adjusted for them. For images with four channels (e.g. ``RGBA``), the
fourth channel is ignored in the colorspace conversion (e.g. from an
``RGBA`` image, only the ``RGB`` part is converted, normalized, converted
back and concatenated with the input ``A`` channel). Images with unusual
channel numbers (2, 5 or more than 5) are normalized channel-by-channel
(same behaviour as ``AllChannelsCLAHE``, though a warning will be raised).
If you want to apply CLAHE to each channel of the original input image's
colorspace (without any colorspace conversion), use
``imgaug.augmenters.contrast.AllChannelsCLAHE`` instead.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no (1)
* ``uint32``: no (1)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (1)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) This augmenter uses
:class:`~imgaug.augmenters.color.ChangeColorspace`, which is
currently limited to ``uint8``.
Parameters
----------
clip_limit : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Clipping limit. Higher values result in stronger contrast. OpenCV
uses a default of ``40``, though values around ``5`` seem to already
produce decent contrast.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value from the range ``[a, b]``
will be used per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter.
tile_grid_size_px : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or tuple of tuple of int or tuple of list of int or tuple of imgaug.parameters.StochasticParameter, optional
Kernel size, i.e. size of each local neighbourhood in pixels.
* If an ``int``, then that value will be used for all images for
both kernel height and width.
* If a tuple ``(a, b)``, then a value from the discrete interval
``[a..b]`` will be uniformly sampled per image.
* If a list, then a random value will be sampled from that list
per image and used for both kernel height and width.
* If a ``StochasticParameter``, then a value will be sampled per
image from that parameter per image and used for both kernel
height and width.
* If a tuple of tuple of ``int`` given as ``((a, b), (c, d))``,
then two values will be sampled independently from the discrete
ranges ``[a..b]`` and ``[c..d]`` per image and used as the
kernel height and width.
* If a tuple of lists of ``int``, then two values will be sampled
independently per image, one from the first list and one from
the second, and used as the kernel height and width.
* If a tuple of ``StochasticParameter``, then two values will be
sampled indepdently per image, one from the first parameter and
one from the second, and used as the kernel height and width.
tile_grid_size_px_min : int, optional
Minimum kernel size in px, per axis. If the sampling results in a
value lower than this minimum, it will be clipped to this value.
from_colorspace : {"RGB", "BGR", "HSV", "HLS", "Lab"}, optional
Colorspace of the input images.
If any input image has only one or zero channels, this setting will
be ignored and it will be assumed that the input is grayscale.
If a fourth channel is present in an input image, it will be removed
before the colorspace conversion and later re-added.
See also :func:`~imgaug.augmenters.color.change_colorspace_` for
details.
to_colorspace : {"Lab", "HLS", "HSV"}, optional
Colorspace in which to perform CLAHE. For ``Lab``, CLAHE will only be
applied to the first channel (``L``), for ``HLS`` to the
second (``L``) and for ``HSV`` to the third (``V``). To apply CLAHE
to all channels of an input image (without colorspace conversion),
see ``imgaug.augmenters.contrast.AllChannelsCLAHE``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CLAHE()
Create a standard CLAHE augmenter.
>>> aug = iaa.CLAHE(clip_limit=(1, 10))
Create a CLAHE augmenter with a clip limit uniformly sampled from
``[1..10]``, where ``1`` is rather low contrast and ``10`` is rather
high contrast.
>>> aug = iaa.CLAHE(tile_grid_size_px=(3, 21))
Create a CLAHE augmenter with kernel sizes of ``SxS``, where ``S`` is
uniformly sampled from ``[3..21]``. Sampling happens once per image.
>>> aug = iaa.CLAHE(
>>> tile_grid_size_px=iap.Discretize(iap.Normal(loc=7, scale=2)),
>>> tile_grid_size_px_min=3)
Create a CLAHE augmenter with kernel sizes of ``SxS``, where ``S`` is
sampled from ``N(7, 2)``, but does not go below ``3``.
>>> aug = iaa.CLAHE(tile_grid_size_px=((3, 21), [3, 5, 7]))
Create a CLAHE augmenter with kernel sizes of ``HxW``, where ``H`` is
uniformly sampled from ``[3..21]`` and ``W`` is randomly picked from the
list ``[3, 5, 7]``.
>>> aug = iaa.CLAHE(
>>> from_colorspace=iaa.CSPACE_BGR,
>>> to_colorspace=iaa.CSPACE_HSV)
Create a CLAHE augmenter that converts images from BGR colorspace to
HSV colorspace and then applies the local histogram equalization to the
``V`` channel of the images (before converting back to ``BGR``).
Alternatively, ``Lab`` (default) or ``HLS`` can be used as the target
colorspace. Grayscale images (no channels / one channel) are never
converted and are instead directly normalized (i.e. `from_colorspace`
does not have to be changed for them).
"""
RGB = color_lib.CSPACE_RGB
BGR = color_lib.CSPACE_BGR
HSV = color_lib.CSPACE_HSV
HLS = color_lib.CSPACE_HLS
Lab = color_lib.CSPACE_Lab
def __init__(self, clip_limit=(0.1, 8), tile_grid_size_px=(3, 12),
tile_grid_size_px_min=3,
from_colorspace=color_lib.CSPACE_RGB,
to_colorspace=color_lib.CSPACE_Lab,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CLAHE, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.all_channel_clahe = AllChannelsCLAHE(
clip_limit=clip_limit,
tile_grid_size_px=tile_grid_size_px,
tile_grid_size_px_min=tile_grid_size_px_min,
name="%s_AllChannelsCLAHE" % (name,))
self.intensity_channel_based_applier = _IntensityChannelBasedApplier(
from_colorspace, to_colorspace)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
iadt.allow_only_uint8(images, augmenter=self)
def _augment_all_channels_clahe(images_normalized,
random_state_derived):
# pylint: disable=protected-access
# TODO would .augment_batch() be sufficient here?
batch_imgs = _BatchInAugmentation(
images=images_normalized)
return self.all_channel_clahe._augment_batch_(
batch_imgs, random_state_derived, parents + [self],
hooks
).images
batch.images = self.intensity_channel_based_applier.apply(
images, random_state, parents + [self], hooks,
_augment_all_channels_clahe)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
ac_clahe = self.all_channel_clahe
intb_applier = self.intensity_channel_based_applier
return [
ac_clahe.clip_limit,
ac_clahe.tile_grid_size_px,
ac_clahe.tile_grid_size_px_min
] + intb_applier.get_parameters()
class AllChannelsHistogramEqualization(meta.Augmenter):
"""
Apply Histogram Eq. to all channels of images in their original colorspaces.
In contrast to ``imgaug.augmenters.contrast.HistogramEqualization``, this
augmenter operates directly on all channels of the input images. It does
not perform any colorspace transformations and does not focus on specific
channels (e.g. ``L`` in ``Lab`` colorspace).
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no (1)
* ``uint32``: no (2)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (2)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (2)
* ``bool``: no (1)
- (1) causes cv2 error: ``cv2.error:
OpenCV(3.4.5) (...)/histogram.cpp:3345: error: (-215:Assertion
failed) src.type() == CV_8UC1 in function 'equalizeHist'``
- (2) rejected by cv2
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AllChannelsHistogramEqualization()
Create an augmenter that applies histogram equalization to all channels
of input images in the original colorspaces.
>>> aug = iaa.Alpha((0.0, 1.0), iaa.AllChannelsHistogramEqualization())
Same as in the previous example, but alpha-blends the contrast-enhanced
augmented images with the original input images using random blend
strengths. This leads to random strengths of the contrast adjustment.
"""
def __init__(self, seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AllChannelsHistogramEqualization, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
iadt.allow_only_uint8(images, augmenter=self)
for i, image in enumerate(images):
if image.size == 0:
continue
image_warped = [
cv2.equalizeHist(_normalize_cv2_input_arr_(image[..., c]))
for c in sm.xrange(image.shape[2])]
image_warped = np.array(image_warped, dtype=image_warped[0].dtype)
image_warped = image_warped.transpose((1, 2, 0))
batch.images[i] = image_warped
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return []
class HistogramEqualization(meta.Augmenter):
"""
Apply Histogram Eq. to L/V/L channels of images in HLS/HSV/Lab colorspaces.
This augmenter is similar to ``imgaug.augmenters.contrast.CLAHE``.
The augmenter transforms input images to a target colorspace (e.g.
``Lab``), extracts an intensity-related channel from the converted images
(e.g. ``L`` for ``Lab``), applies Histogram Equalization to the channel
and then converts the resulting image back to the original colorspace.
Grayscale images (images without channel axis or with only one channel
axis) are automatically handled, `from_colorspace` does not have to be
adjusted for them. For images with four channels (e.g. RGBA), the fourth
channel is ignored in the colorspace conversion (e.g. from an ``RGBA``
image, only the ``RGB`` part is converted, normalized, converted back and
concatenated with the input ``A`` channel). Images with unusual channel
numbers (2, 5 or more than 5) are normalized channel-by-channel (same
behaviour as ``AllChannelsHistogramEqualization``, though a warning will
be raised).
If you want to apply HistogramEqualization to each channel of the original
input image's colorspace (without any colorspace conversion), use
``imgaug.augmenters.contrast.AllChannelsHistogramEqualization`` instead.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no (1)
* ``uint32``: no (1)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (1)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) This augmenter uses :class:`AllChannelsHistogramEqualization`,
which only supports ``uint8``.
Parameters
----------
from_colorspace : {"RGB", "BGR", "HSV", "HLS", "Lab"}, optional
Colorspace of the input images.
If any input image has only one or zero channels, this setting will be
ignored and it will be assumed that the input is grayscale.
If a fourth channel is present in an input image, it will be removed
before the colorspace conversion and later re-added.
See also :func:`~imgaug.augmenters.color.change_colorspace_` for
details.
to_colorspace : {"Lab", "HLS", "HSV"}, optional
Colorspace in which to perform Histogram Equalization. For ``Lab``,
the equalization will only be applied to the first channel (``L``),
for ``HLS`` to the second (``L``) and for ``HSV`` to the third (``V``).
To apply histogram equalization to all channels of an input image
(without colorspace conversion), see
``imgaug.augmenters.contrast.AllChannelsHistogramEqualization``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.HistogramEqualization()
Create an augmenter that converts images to ``HLS``/``HSV``/``Lab``
colorspaces, extracts intensity-related channels (i.e. ``L``/``V``/``L``),
applies histogram equalization to these channels and converts back to the
input colorspace.
>>> aug = iaa.Alpha((0.0, 1.0), iaa.HistogramEqualization())
Same as in the previous example, but alpha blends the result, leading
to various strengths of contrast normalization.
>>> aug = iaa.HistogramEqualization(
>>> from_colorspace=iaa.CSPACE_BGR,
>>> to_colorspace=iaa.CSPACE_HSV)
Same as in the first example, but the colorspace of input images has
to be ``BGR`` (instead of default ``RGB``) and the histogram equalization
is applied to the ``V`` channel in ``HSV`` colorspace.
"""
RGB = color_lib.CSPACE_RGB
BGR = color_lib.CSPACE_BGR
HSV = color_lib.CSPACE_HSV
HLS = color_lib.CSPACE_HLS
Lab = color_lib.CSPACE_Lab
def __init__(self, from_colorspace=color_lib.CSPACE_RGB,
to_colorspace=color_lib.CSPACE_Lab,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(HistogramEqualization, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.all_channel_histogram_equalization = \
AllChannelsHistogramEqualization(
name="%s_AllChannelsHistogramEqualization" % (name,))
self.intensity_channel_based_applier = _IntensityChannelBasedApplier(
from_colorspace, to_colorspace)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
iadt.allow_only_uint8(images, augmenter=self)
def _augment_all_channels_histogram_equalization(images_normalized,
random_state_derived):
# pylint: disable=protected-access
# TODO would .augment_batch() be sufficient here
batch_imgs = _BatchInAugmentation(
images=images_normalized)
return self.all_channel_histogram_equalization._augment_batch_(
batch_imgs, random_state_derived, parents + [self],
hooks
).images
batch.images = self.intensity_channel_based_applier.apply(
images, random_state, parents + [self], hooks,
_augment_all_channels_histogram_equalization)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
icb_applier = self.intensity_channel_based_applier
return icb_applier.get_parameters()
================================================
FILE: imgaug/augmenters/convolutional.py
================================================
"""
Augmenters that are based on applying convolution kernels to images.
List of augmenters:
* :class:`Convolve`
* :class:`Sharpen`
* :class:`Emboss`
* :class:`EdgeDetect`
* :class:`DirectedEdgeDetect`
For MotionBlur, see ``blur.py``.
"""
from __future__ import print_function, division, absolute_import
import itertools
import numpy as np
import cv2
import six.moves as sm
import imgaug as ia
from . import meta
from .. import parameters as iap
from .. import dtypes as iadt
def convolve(image, kernel):
"""Apply a convolution kernel (or one per channel) to an image.
See :func:`convolve_` for details.
Added in 0.5.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.convolutional.convolve_`.
Parameters
----------
image : ndarray
``(H,W)`` or ``(H,W,C)`` image array.
kernel : ndarray or list of ndarray
Either a single 2D kernel matrix (will be applied to all channels)
or a list of 2D matrices (one per image channel).
Returns
-------
image
Image of the same shape and dtype as the input array.
"""
return convolve_(np.copy(image), kernel)
def convolve_(image, kernel):
"""Apply a convolution kernel (or one per channel) in-place to an image.
Use a list of matrices to apply one kernel per channel.
Added in 0.5.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested (3)
* ``int16``: yes; tested
* ``int32``: no (2)
* ``int64``: no (2)
* ``float16``: yes; tested (4)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (1)
* ``bool``: yes; tested (4)
- (1) rejected by ``cv2.filter2D()``.
- (2) causes error: cv2.error: OpenCV(3.4.2) (...)/filter.cpp:4487:
error: (-213:The function/feature is not implemented)
Unsupported combination of source format (=1), and destination
format (=1) in function 'getLinearFilter'.
- (3) mapped internally to ``int16``.
- (4) mapped internally to ``float32``.
Parameters
----------
image : ndarray
``(H,W)`` or ``(H,W,C)`` image array.
May be modified in-place.
kernel : ndarray or list of ndarray
Either a single 2D kernel matrix (will be applied to all channels)
or a list of 2D matrices (one per image channel).
Returns
-------
image
Image of the same shape and dtype as the input array.
Might have been modified in-place.
"""
iadt.gate_dtypes_strs(
{image.dtype},
allowed="bool uint8 uint16 int8 int16 float16 float32 float64",
disallowed="uint32 uint64 int32 int64 float128"
)
# currently we don't have to worry here about alignemnt with
# non-image data and therefore can just place this before any
# sampling
if image.size == 0:
return image
input_shape = image.shape
nb_channels = 1 if len(input_shape) == 2 else input_shape[2]
input_dtype = image.dtype
if image.dtype in {iadt._BOOL_DTYPE, iadt._FLOAT16_DTYPE}:
image = image.astype(np.float32, copy=False)
elif image.dtype == iadt._INT8_DTYPE:
image = image.astype(np.int16, copy=False)
if ia.is_np_array(kernel):
assert kernel.ndim == 2, (
"Expected kernel to be either a list of (H,W) arrays or a "
"single (H,W) array, got array of shape %s." % (kernel.shape,)
)
matrices = [kernel]
else:
assert isinstance(kernel, list), (
"Expected kernel to be either a list of (H,W) arrays or a "
"single (H,W) array, got type %s." % (type(kernel).__name__,)
)
assert len(kernel) == nb_channels, (
"Kernel was given as a list. Expected that list to contain as "
"many arrays as there are image channels. "
"Got %d, but expected %d for image of shape %s." % (
len(kernel), nb_channels, image.shape
)
)
matrices = kernel
if not image.flags["C_CONTIGUOUS"]:
image = np.ascontiguousarray(image)
# force channelwise application for >512 channels
if nb_channels > 512 and len(matrices) == 1:
matrices = [matrices[0]] * nb_channels
if len(matrices) == 1:
if matrices[0] is not None:
if image.base is not None and image.base.shape[0] == 1:
image = np.copy(image)
image = cv2.filter2D(image, -1, matrices[0], dst=image)
else:
for channel in sm.xrange(nb_channels):
if matrices[channel] is not None:
arr_channel = np.copy(image[..., channel])
image[..., channel] = cv2.filter2D(
arr_channel,
-1,
matrices[channel],
dst=arr_channel
)
if input_dtype.kind == "b":
image = image > 0.5
elif input_dtype in {iadt._INT8_DTYPE, iadt._FLOAT16_DTYPE}:
image = iadt.restore_dtypes_(image, input_dtype)
if len(input_shape) == 3 and image.ndim == 2:
image = image[:, :, np.newaxis]
return image
# TODO allow 3d matrices as input (not only 2D)
# TODO add _augment_keypoints and other _augment funcs, as these should do
# something for e.g. [[0, 0, 1]]
class Convolve(meta.Augmenter):
"""
Apply a convolution to input images.
**Supported dtypes**:
See :func:`~imgaug.augmenters.convolutional.convolve_`.
Parameters
----------
matrix : None or (H, W) ndarray or imgaug.parameters.StochasticParameter or callable, optional
The weight matrix of the convolution kernel to apply.
* If ``None``, the input images will not be changed.
* If a 2D numpy array, that array will always be used for all
images and channels as the kernel.
* If a callable, that method will be called for each image
via ``parameter(image, C, random_state)``. The function must
either return a list of ``C`` matrices (i.e. one per channel)
or a 2D numpy array (will be used for all channels) or a
3D ``HxWxC`` numpy array. If a list is returned, each entry may
be ``None``, which will result in no changes to the respective
channel.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> matrix = np.array([[0, -1, 0],
>>> [-1, 4, -1],
>>> [0, -1, 0]])
>>> aug = iaa.Convolve(matrix=matrix)
Convolves all input images with the kernel shown in the ``matrix``
variable.
>>> def gen_matrix(image, nb_channels, random_state):
>>> matrix_A = np.array([[0, -1, 0],
>>> [-1, 4, -1],
>>> [0, -1, 0]])
>>> matrix_B = np.array([[0, 1, 0],
>>> [1, -4, 1],
>>> [0, 1, 0]])
>>> if image.shape[0] % 2 == 0:
>>> return [matrix_A] * nb_channels
>>> else:
>>> return [matrix_B] * nb_channels
>>> aug = iaa.Convolve(matrix=gen_matrix)
Convolves images that have an even height with matrix A and images
having an odd height with matrix B.
"""
def __init__(self, matrix=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Convolve, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
if matrix is None:
self.matrix = None
self.matrix_type = "None"
elif ia.is_np_array(matrix):
assert matrix.ndim == 2, (
"Expected convolution matrix to have exactly two dimensions, "
"got %d (shape %s)." % (matrix.ndim, matrix.shape))
self.matrix = matrix
self.matrix_type = "constant"
elif ia.is_callable(matrix):
self.matrix = matrix
self.matrix_type = "function"
else:
raise Exception(
"Expected float, int, tuple/list with 2 entries or "
"StochasticParameter. Got %s." % (
type(matrix),))
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
rss = random_state.duplicate(len(images))
for i, image in enumerate(images):
_height, _width, nb_channels = image.shape
if self.matrix_type == "None":
matrix = None
elif self.matrix_type == "constant":
matrix = self.matrix
else:
assert self.matrix_type == "function"
# TODO check if sampled matrices are identical over channels
# and if so merge. (does that really help wrt speed?)
matrix = self.matrix(images[i], nb_channels, rss[i])
if matrix is not None:
batch.images[i] = convolve_(image, matrix)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.matrix, self.matrix_type]
class Sharpen(Convolve):
"""
Sharpen images and alpha-blend the result with the original input images.
**Supported dtypes**:
See :class:`~imgaug.augmenters.convolutional.Convolve`.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Blending factor of the sharpened image. At ``0.0``, only the original
image is visible, at ``1.0`` only its sharpened version is visible.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value will be sampled from the
interval ``[a, b]`` per image.
* If a list, a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from that
parameter per image.
lightness : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Lightness/brightness of the sharped image.
Sane values are somewhere in the interval ``[0.5, 2.0]``.
The value ``0.0`` results in an edge map. Values higher than ``1.0``
create bright images. Default value is ``1.0``.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value will be sampled from the
interval ``[a, b]`` per image.
* If a list, a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from that
parameter per image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Sharpen(alpha=(0.0, 1.0))
Sharpens input images and blends the sharpened image with the input image
using a random blending factor between ``0%`` and ``100%`` (uniformly
sampled).
>>> aug = iaa.Sharpen(alpha=(0.0, 1.0), lightness=(0.75, 2.0))
Sharpens input images with a variable `lightness` sampled uniformly from
the interval ``[0.75, 2.0]`` and with a fully random blending factor
(as in the above example).
"""
def __init__(self, alpha=(0.0, 0.2), lightness=(0.8, 1.2),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
alpha_param = iap.handle_continuous_param(
alpha, "alpha",
value_range=(0, 1.0), tuple_to_uniform=True, list_to_choice=True)
lightness_param = iap.handle_continuous_param(
lightness, "lightness",
value_range=(0, None), tuple_to_uniform=True, list_to_choice=True)
matrix_gen = _SharpeningMatrixGenerator(alpha_param, lightness_param)
super(Sharpen, self).__init__(
matrix=matrix_gen,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class _SharpeningMatrixGenerator(object):
def __init__(self, alpha, lightness):
self.alpha = alpha
self.lightness = lightness
def __call__(self, _image, nb_channels, random_state):
alpha_sample = self.alpha.draw_sample(random_state=random_state)
assert 0 <= alpha_sample <= 1.0, (
"Expected 'alpha' to be in the interval [0.0, 1.0], "
"got %.4f." % (alpha_sample,))
lightness_sample = self.lightness.draw_sample(random_state=random_state)
matrix_nochange = np.array([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
], dtype=np.float32)
matrix_effect = np.array([
[-1, -1, -1],
[-1, 8+lightness_sample, -1],
[-1, -1, -1]
], dtype=np.float32)
matrix = (
(1-alpha_sample) * matrix_nochange
+ alpha_sample * matrix_effect
)
return matrix
class Emboss(Convolve):
"""
Emboss images and alpha-blend the result with the original input images.
The embossed version pronounces highlights and shadows,
letting the image look as if it was recreated on a metal plate ("embossed").
**Supported dtypes**:
See :class:`~imgaug.augmenters.convolutional.Convolve`.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Blending factor of the embossed image. At ``0.0``, only the original
image is visible, at ``1.0`` only its embossed version is visible.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value will be sampled from the
interval ``[a, b]`` per image.
* If a list, a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from that
parameter per image.
strength : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Parameter that controls the strength of the embossing.
Sane values are somewhere in the interval ``[0.0, 2.0]`` with ``1.0``
being the standard embossing effect. Default value is ``1.0``.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value will be sampled from the
interval ``[a, b]`` per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Emboss(alpha=(0.0, 1.0), strength=(0.5, 1.5))
Emboss an image with a strength sampled uniformly from the interval
``[0.5, 1.5]`` and alpha-blend the result with the original input image
using a random blending factor between ``0%`` and ``100%``.
"""
def __init__(self, alpha=(0.0, 1.0), strength=(0.25, 1.0),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
alpha_param = iap.handle_continuous_param(
alpha, "alpha",
value_range=(0, 1.0), tuple_to_uniform=True, list_to_choice=True)
strength_param = iap.handle_continuous_param(
strength, "strength",
value_range=(0, None), tuple_to_uniform=True, list_to_choice=True)
matrix_gen = _EmbossMatrixGenerator(alpha_param, strength_param)
super(Emboss, self).__init__(
matrix=matrix_gen,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class _EmbossMatrixGenerator(object):
def __init__(self, alpha, strength):
self.alpha = alpha
self.strength = strength
def __call__(self, _image, nb_channels, random_state):
alpha_sample = self.alpha.draw_sample(random_state=random_state)
assert 0 <= alpha_sample <= 1.0, (
"Expected 'alpha' to be in the interval [0.0, 1.0], "
"got %.4f." % (alpha_sample,))
strength_sample = self.strength.draw_sample(random_state=random_state)
matrix_nochange = np.array([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
], dtype=np.float32)
matrix_effect = np.array([
[-1-strength_sample, 0-strength_sample, 0],
[0-strength_sample, 1, 0+strength_sample],
[0, 0+strength_sample, 1+strength_sample]
], dtype=np.float32)
matrix = (
(1-alpha_sample) * matrix_nochange
+ alpha_sample * matrix_effect
)
return matrix
# TODO add tests
# TODO move this to edges.py?
class EdgeDetect(Convolve):
"""
Generate a black & white edge image and alpha-blend it with the input image.
**Supported dtypes**:
See :class:`~imgaug.augmenters.convolutional.Convolve`.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Blending factor of the edge image. At ``0.0``, only the original
image is visible, at ``1.0`` only the edge image is visible.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value will be sampled from the
interval ``[a, b]`` per image.
* If a list, a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from that
parameter per image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.EdgeDetect(alpha=(0.0, 1.0))
Detect edges in an image, mark them as black (non-edge) and white (edges)
and alpha-blend the result with the original input image using a random
blending factor between ``0%`` and ``100%``.
"""
def __init__(self, alpha=(0.0, 0.75),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
alpha_param = iap.handle_continuous_param(
alpha, "alpha",
value_range=(0, 1.0), tuple_to_uniform=True, list_to_choice=True)
matrix_gen = _EdgeDetectMatrixGenerator(alpha_param)
super(EdgeDetect, self).__init__(
matrix=matrix_gen,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class _EdgeDetectMatrixGenerator(object):
def __init__(self, alpha):
self.alpha = alpha
def __call__(self, _image, nb_channels, random_state):
alpha_sample = self.alpha.draw_sample(random_state=random_state)
assert 0 <= alpha_sample <= 1.0, (
"Expected 'alpha' to be in the interval [0.0, 1.0], "
"got %.4f." % (alpha_sample,))
matrix_nochange = np.array([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
], dtype=np.float32)
matrix_effect = np.array([
[0, 1, 0],
[1, -4, 1],
[0, 1, 0]
], dtype=np.float32)
matrix = (
(1-alpha_sample) * matrix_nochange
+ alpha_sample * matrix_effect
)
return matrix
# TODO add tests
# TODO merge EdgeDetect and DirectedEdgeDetect?
# TODO deprecate and rename to AngledEdgeDetect
# TODO rename arg "direction" to "angle"
# TODO change direction/angle value range to (0, 360)
# TODO move this to edges.py?
class DirectedEdgeDetect(Convolve):
"""
Detect edges from specified angles and alpha-blend with the input image.
This augmenter first detects edges along a certain angle.
Usually, edges are detected in x- or y-direction, while here the edge
detection kernel is rotated to match a specified angle.
The result of applying the kernel is a black (non-edges) and white (edges)
image. That image is alpha-blended with the input image.
**Supported dtypes**:
See :class:`~imgaug.augmenters.convolutional.Convolve`.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Blending factor of the edge image. At ``0.0``, only the original
image is visible, at ``1.0`` only the edge image is visible.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value will be sampled from the
interval ``[a, b]`` per image.
* If a list, a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from that
parameter per image.
direction : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Angle (in degrees) of edges to pronounce, where ``0`` represents
``0`` degrees and ``1.0`` represents 360 degrees (both clockwise,
starting at the top). Default value is ``(0.0, 1.0)``, i.e. pick a
random angle per image.
* If a number, exactly that value will always be used.
* If a tuple ``(a, b)``, a random value will be sampled from the
interval ``[a, b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, a value will be sampled from the
parameter per image.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.DirectedEdgeDetect(alpha=1.0, direction=0)
Turn input images into edge images in which edges are detected from
the top side of the image (i.e. the top sides of horizontal edges are
part of the edge image, while vertical edges are ignored).
>>> aug = iaa.DirectedEdgeDetect(alpha=1.0, direction=90/360)
Same as before, but edges are detected from the right. Horizontal edges
are now ignored.
>>> aug = iaa.DirectedEdgeDetect(alpha=1.0, direction=(0.0, 1.0))
Same as before, but edges are detected from a random angle sampled
uniformly from the interval ``[0deg, 360deg]``.
>>> aug = iaa.DirectedEdgeDetect(alpha=(0.0, 0.3), direction=0)
Similar to the previous examples, but here the edge image is alpha-blended
with the input image. The result is a mixture between the edge image and
the input image. The blending factor is randomly sampled between ``0%``
and ``30%``.
"""
def __init__(self, alpha=(0.0, 0.75), direction=(0.0, 1.0),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
alpha_param = iap.handle_continuous_param(
alpha, "alpha",
value_range=(0, 1.0), tuple_to_uniform=True, list_to_choice=True)
direction_param = iap.handle_continuous_param(
direction, "direction",
value_range=None, tuple_to_uniform=True, list_to_choice=True)
matrix_gen = _DirectedEdgeDetectMatrixGenerator(alpha_param,
direction_param)
super(DirectedEdgeDetect, self).__init__(
matrix=matrix_gen,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class _DirectedEdgeDetectMatrixGenerator(object):
def __init__(self, alpha, direction):
self.alpha = alpha
self.direction = direction
def __call__(self, _image, nb_channels, random_state):
alpha_sample = self.alpha.draw_sample(random_state=random_state)
assert 0 <= alpha_sample <= 1.0, (
"Expected 'alpha' to be in the interval [0.0, 1.0], "
"got %.4f." % (alpha_sample,))
direction_sample = self.direction.draw_sample(random_state=random_state)
deg = int(direction_sample * 360) % 360
rad = np.deg2rad(deg)
x = np.cos(rad - 0.5*np.pi)
y = np.sin(rad - 0.5*np.pi)
direction_vector = np.array([x, y])
matrix_effect = np.array([
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
], dtype=np.float32)
for x, y in itertools.product([-1, 0, 1], [-1, 0, 1]):
if (x, y) != (0, 0):
cell_vector = np.array([x, y])
distance_deg = np.rad2deg(
ia.angle_between_vectors(cell_vector,
direction_vector))
distance = distance_deg / 180
similarity = (1 - distance)**4
matrix_effect[y+1, x+1] = similarity
matrix_effect = matrix_effect / np.sum(matrix_effect)
matrix_effect = matrix_effect * (-1)
matrix_effect[1, 1] = 1
matrix_nochange = np.array([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
], dtype=np.float32)
matrix = (
(1-alpha_sample) * matrix_nochange
+ alpha_sample * matrix_effect
)
return matrix
================================================
FILE: imgaug/augmenters/debug.py
================================================
"""Augmenters that help with debugging.
List of augmenters:
* :class:`SaveDebugImageEveryNBatches`
Added in 0.4.0.
"""
from __future__ import print_function, division, absolute_import
from abc import ABCMeta, abstractmethod, abstractproperty
import os
import collections
import six
import numpy as np
import imageio
import imgaug as ia
from .. import dtypes as iadt
from . import meta
from . import size as sizelib
from . import blend as blendlib
_COLOR_PINK = (255, 192, 203)
_COLOR_GRID_BACKGROUND = _COLOR_PINK
def _resizepad_to_size(image, size, cval):
"""Resize and pad and image to given size.
This first resizes until one image size matches one size in `size` (while
retaining the aspect ratio).
Then it pads the other side until both sides match `size`.
Added in 0.4.0.
"""
# resize to height H and width W while keeping aspect ratio
height = size[0]
width = size[1]
height_im = image.shape[0]
width_im = image.shape[1]
aspect_ratio_im = width_im / height_im
# we know that height_im <= height and width_im <= width
height_diff = height - height_im
width_diff = width - width_im
if height_diff < width_diff:
height_im_rs = height
width_im_rs = height * aspect_ratio_im
else:
height_im_rs = width / aspect_ratio_im
width_im_rs = width
height_im_rs = max(int(np.round(height_im_rs)), 1)
width_im_rs = max(int(np.round(width_im_rs)), 1)
image_rs = ia.imresize_single_image(image, (height_im_rs, width_im_rs))
# pad to remaining size
pad_y = height - height_im_rs
pad_x = width - width_im_rs
pad_top = int(np.floor(pad_y / 2))
pad_right = int(np.ceil(pad_x / 2))
pad_bottom = int(np.ceil(pad_y / 2))
pad_left = int(np.floor(pad_x / 2))
image_rs_pad = sizelib.pad(image_rs,
top=pad_top, right=pad_right,
bottom=pad_bottom, left=pad_left,
cval=cval)
paddings = (pad_top, pad_right, pad_bottom, pad_left)
return image_rs_pad, (height_im_rs, width_im_rs), paddings
# TODO rename to Grid
@six.add_metaclass(ABCMeta)
class _IDebugGridCell(object):
"""A single cell within a debug image's grid.
Usually corresponds to one image, but can also be e.g. a title/description.
Added in 0.4.0.
"""
@abstractproperty
def min_width(self):
"""Minimum width in pixels that the cell requires.
Added in 0.4.0.
"""
@abstractproperty
def min_height(self):
"""Minimum height in pixels that the cell requires.
Added in 0.4.0.
"""
@abstractmethod
def draw(self, height, width):
"""Draw the debug image grid cell's content.
Added in 0.4.0.
Parameters
----------
height : int
Expected height of the drawn cell image/array.
width : int
Expected width of the drawn cell image/array.
Returns
-------
ndarray
``(H,W,3)`` Image.
"""
class _DebugGridBorderCell(_IDebugGridCell):
"""Helper to add a border around a cell within the debug image grid.
Added in 0.4.0.
"""
# Added in 0.4.0.
def __init__(self, size, color, child):
self.size = size
self.color = color
self.child = child
# Added in 0.4.0.
@property
def min_height(self):
return self.child.min_height
# Added in 0.4.0.
@property
def min_width(self):
return self.child.min_width
# Added in 0.4.0.
def draw(self, height, width):
content = self.child.draw(height, width)
content = sizelib.pad(content,
top=self.size, right=self.size,
bottom=self.size, left=self.size,
mode="constant", cval=self.color)
return content
class _DebugGridTextCell(_IDebugGridCell):
"""Cell containing text.
Added in 0.4.0.
"""
# Added in 0.4.0.
def __init__(self, text):
self.text = text
# Added in 0.4.0.
@property
def min_height(self):
return max(20, len(self.text.split("\n")) * 17)
# Added in 0.4.0.
@property
def min_width(self):
lines = self.text.split("\n")
if len(lines) == 0:
return 20
return max(20, int(7 * max([len(line) for line in lines])))
# Added in 0.4.0.
def draw(self, height, width):
image = np.full((height, width, 3), 255, dtype=np.uint8)
image = ia.draw_text(image, 0, 0, self.text, color=(0, 0, 0),
size=12)
return image
class _DebugGridImageCell(_IDebugGridCell):
"""Cell containing an image, possibly with an different-shaped overlay.
Added in 0.4.0.
"""
# Added in 0.4.0.
def __init__(self, image, overlay=None, overlay_alpha=0.75):
self.image = image
self.overlay = overlay
self.overlay_alpha = overlay_alpha
# Added in 0.4.0.
@property
def min_height(self):
return self.image.shape[0]
# Added in 0.4.0.
@property
def min_width(self):
return self.image.shape[1]
# Added in 0.4.0.
def draw(self, height, width):
image = self.image
kind = image.dtype.kind
if kind == "b":
image = image.astype(np.uint8) * 255
elif kind == "u":
min_value, _, max_value = iadt.get_value_range_of_dtype(image.dtype)
image = image.astype(np.float64) / max_value
elif kind == "i":
min_value, _, max_value = iadt.get_value_range_of_dtype(image.dtype)
dynamic_range = (max_value - min_value)
image = (min_value + image.astype(np.float64)) / dynamic_range
if image.dtype.kind == "f":
image = (np.clip(image, 0, 1.0) * 255).astype(np.uint8)
image_rsp, size_rs, paddings = _resizepad_to_size(
image, (height, width), cval=_COLOR_GRID_BACKGROUND)
blend = image_rsp
if self.overlay is not None:
overlay_rs = self._resize_overlay(self.overlay,
image.shape[0:2])
overlay_rsp = self._resize_overlay(overlay_rs, size_rs)
overlay_rsp = sizelib.pad(overlay_rsp,
top=paddings[0], right=paddings[1],
bottom=paddings[2], left=paddings[3],
cval=_COLOR_GRID_BACKGROUND)
blend = blendlib.blend_alpha(overlay_rsp, image_rsp,
alpha=self.overlay_alpha)
return blend
# Added in 0.4.0.
@classmethod
def _resize_overlay(cls, arr, size):
arr_rs = ia.imresize_single_image(arr, size, interpolation="nearest")
return arr_rs
class _DebugGridCBAsOICell(_IDebugGridCell):
"""Cell visualizing a coordinate-based augmentable.
CBAsOI = coordinate-based augmentables on images,
e.g. ``KeypointsOnImage``.
Added in 0.4.0.
"""
# Added in 0.4.0.
def __init__(self, cbasoi, image):
self.cbasoi = cbasoi
self.image = image
# Added in 0.4.0.
@property
def min_height(self):
return self.image.shape[0]
# Added in 0.4.0.
@property
def min_width(self):
return self.image.shape[1]
# Added in 0.4.0.
def draw(self, height, width):
image_rsp, size_rs, paddings = _resizepad_to_size(
self.image, (height, width), cval=_COLOR_GRID_BACKGROUND)
cbasoi = self.cbasoi.deepcopy()
cbasoi = cbasoi.on_(size_rs)
cbasoi = cbasoi.shift_(y=paddings[0], x=paddings[3])
cbasoi.shape = image_rsp.shape
return cbasoi.draw_on_image(image_rsp)
class _DebugGridColumn(object):
"""A single column within the debug image grid.
Added in 0.4.0.
"""
def __init__(self, cells):
self.cells = cells
@property
def nb_rows(self):
"""Number of rows in the column, i.e. examples in batch.
Added in 0.4.0.
"""
return len(self.cells)
@property
def max_cell_width(self):
"""Width in pixels of the widest cell in the column.
Added in 0.4.0.
"""
return max([cell.min_width for cell in self.cells])
@property
def max_cell_height(self):
"""Height in pixels of the tallest cell in the column.
Added in 0.4.0.
"""
return max([cell.min_height for cell in self.cells])
def draw(self, heights):
"""Convert this column to an image array.
Added in 0.4.0.
"""
width = self.max_cell_width
return np.vstack([cell.draw(height=height, width=width)
for cell, height
in zip(self.cells, heights)])
class _DebugGrid(object):
"""A debug image grid.
Columns correspond to the input datatypes (e.g. images, bounding boxes).
Rows correspond to the examples within a batch.
Added in 0.4.0.
"""
# Added in 0.4.0.
def __init__(self, columns):
assert len(columns) > 0
self.columns = columns
def draw(self):
"""Convert this grid to an image array.
Added in 0.4.0.
"""
nb_rows_by_col = [column.nb_rows for column in self.columns]
assert len(set(nb_rows_by_col)) == 1
rowwise_heights = np.zeros((self.columns[0].nb_rows,), dtype=np.int32)
for column in self.columns:
heights = [cell.min_height for cell in column.cells]
rowwise_heights = np.maximum(rowwise_heights, heights)
return np.hstack([column.draw(heights=rowwise_heights)
for column in self.columns])
# TODO image subtitles
# TODO run start date
# TODO main process id, process id
# TODO warning if map aspect ratio is different from image aspect ratio
# TODO error if non-image shapes differ from image shapes
def draw_debug_image(images, heatmaps=None, segmentation_maps=None,
keypoints=None, bounding_boxes=None, polygons=None,
line_strings=None):
"""Generate a debug image grid of a single batch and various datatypes.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; tested
* ``uint16``: ?
* ``uint32``: ?
* ``uint64``: ?
* ``int8``: ?
* ``int16``: ?
* ``int32``: ?
* ``int64``: ?
* ``float16``: ?
* ``float32``: ?
* ``float64``: ?
* ``float128``: ?
* ``bool``: ?
Parameters
----------
images : ndarray or list of ndarray
Images in the batch. Must always be provided. Batches without images
cannot be visualized.
heatmaps : None or list of imgaug.augmentables.heatmaps.HeatmapsOnImage, optional
Heatmaps on the provided images.
segmentation_maps : None or list of imgaug.augmentables.segmaps.SegmentationMapsOnImage, optional
Segmentation maps on the provided images.
keypoints : None or list of imgaug.augmentables.kps.KeypointsOnImage, optional
Keypoints on the provided images.
bounding_boxes : None or list of imgaug.augmentables.bbs.BoundingBoxesOnImage, optional
Bounding boxes on the provided images.
polygons : None or list of imgaug.augmentables.polys.PolygonsOnImage, optional
Polygons on the provided images.
line_strings : None or list of imgaug.augmentables.lines.LineStringsOnImage, optional
Line strings on the provided images.
Returns
-------
ndarray
Visualized batch as RGB image.
Examples
--------
>>> import numpy as np
>>> import imgaug.augmenters as iaa
>>> image = np.zeros((64, 64, 3), dtype=np.uint8)
>>> debug_image = iaa.draw_debug_image(images=[image, image])
Generate a debug image for two empty images.
>>> from imgaug.augmentables.kps import KeypointsOnImage
>>> kpsoi = KeypointsOnImage.from_xy_array([(10.5, 20.5), (30.5, 30.5)],
>>> shape=image.shape)
>>> debug_image = iaa.draw_debug_image(images=[image, image],
>>> keypoints=[kpsoi, kpsoi])
Generate a debug image for two empty images, each having two keypoints
drawn on them.
>>> from imgaug.augmentables.batches import UnnormalizedBatch
>>> segmap_arr = np.zeros((32, 32, 1), dtype=np.int32)
>>> kp_tuples = [(10.5, 20.5), (30.5, 30.5)]
>>> batch = UnnormalizedBatch(images=[image, image],
>>> segmentation_maps=[segmap_arr, segmap_arr],
>>> keypoints=[kp_tuples, kp_tuples])
>>> batch = batch.to_normalized_batch()
>>> debug_image = iaa.draw_debug_image(
>>> images=batch.images_unaug,
>>> segmentation_maps=batch.segmentation_maps_unaug,
>>> keypoints=batch.keypoints_unaug)
Generate a debug image for two empty images, each having an empty
segmentation map and two keypoints drawn on them. This example uses
``UnnormalizedBatch`` to show how to mostly evade going through imgaug
classes.
"""
columns = [_create_images_column(images)]
if heatmaps is not None:
columns.extend(_create_heatmaps_columns(heatmaps, images))
if segmentation_maps is not None:
columns.extend(_create_segmap_columns(segmentation_maps, images))
if keypoints is not None:
columns.append(_create_cbasois_column(keypoints, images, "Keypoints"))
if bounding_boxes is not None:
columns.append(_create_cbasois_column(bounding_boxes, images,
"Bounding Boxes"))
if polygons is not None:
columns.append(_create_cbasois_column(polygons, images, "Polygons"))
if line_strings is not None:
columns.append(_create_cbasois_column(line_strings, images,
"Line Strings"))
result = _DebugGrid(columns)
result = result.draw()
result = sizelib.pad(result, top=1, right=1, bottom=1, left=1,
mode="constant", cval=_COLOR_GRID_BACKGROUND)
return result
# Added in 0.4.0.
def _add_borders(cells):
"""Add a border (cell) around a cell."""
return [_DebugGridBorderCell(1, _COLOR_GRID_BACKGROUND, cell)
for cell in cells]
# Added in 0.4.0.
def _add_text_cell(title, cells):
"""Add a text cell before other cells."""
return [_DebugGridTextCell(title)] + cells
# Added in 0.4.0.
def _create_images_column(images):
"""Create columns for image data."""
cells = [_DebugGridImageCell(image) for image in images]
images_descr = _generate_images_description(images)
column = _DebugGridColumn(
_add_borders(
_add_text_cell(
"Images",
_add_text_cell(
images_descr,
cells)
)
)
)
return column
# Added in 0.4.0.
def _create_heatmaps_columns(heatmaps, images):
"""Create columns for heatmap data."""
nb_map_channels = max([heatmap.arr_0to1.shape[2]
for heatmap in heatmaps])
columns = [[] for _ in np.arange(nb_map_channels)]
for image, heatmap in zip(images, heatmaps):
heatmap_drawn = heatmap.draw()
for c, heatmap_drawn_c in enumerate(heatmap_drawn):
columns[c].append(
_DebugGridImageCell(image, overlay=heatmap_drawn_c))
columns = [
_DebugGridColumn(
_add_borders(
_add_text_cell(
"Heatmaps",
_add_text_cell(
_generate_heatmaps_description(
heatmaps,
channel_idx=c,
show_details=(c == 0)),
cells)
)
)
)
for c, cells in enumerate(columns)
]
return columns
# Added in 0.4.0.
def _create_segmap_columns(segmentation_maps, images):
"""Create columns for segmentation map data."""
nb_map_channels = max([segmap.arr.shape[2]
for segmap in segmentation_maps])
columns = [[] for _ in np.arange(nb_map_channels)]
for image, segmap in zip(images, segmentation_maps):
# TODO this currently draws the background in black, hence the
# resulting blended image is dark at class id 0
segmap_drawn = segmap.draw()
for c, segmap_drawn_c in enumerate(segmap_drawn):
columns[c].append(
_DebugGridImageCell(image, overlay=segmap_drawn_c))
columns = [
_DebugGridColumn(
_add_borders(
_add_text_cell(
"SegMaps",
_add_text_cell(
_generate_segmaps_description(
segmentation_maps,
channel_idx=c,
show_details=(c == 0)),
cells
)
)
)
)
for c, cells in enumerate(columns)
]
return columns
# Added in 0.4.0.
def _create_cbasois_column(cbasois, images, column_name):
"""Create a column for coordinate-based augmentables."""
cells = [_DebugGridCBAsOICell(cbasoi, image)
for cbasoi, image
in zip(cbasois, images)]
descr = _generate_cbasois_description(cbasois, images)
column = _DebugGridColumn(
_add_borders(
_add_text_cell(
column_name,
_add_text_cell(descr, cells)
)
)
)
return column
# Added in 0.4.0.
def _generate_images_description(images):
"""Generate description for image columns."""
if ia.is_np_array(images):
shapes_str = "array, shape %11s" % (str(images.shape),)
dtypes_str = "dtype %8s" % (images.dtype.name,)
if len(images) == 0:
value_range_str = ""
elif images.dtype.kind in ["u", "i", "b"]:
value_range_str = "value range: %3d to %3d" % (
np.min(images), np.max(images))
else:
value_range_str = "value range: %7.4f to %7.4f" % (
np.min(images), np.max(images))
else:
stats = _ListOfArraysStats(images)
if stats.empty:
shapes_str = ""
elif stats.all_same_shape:
shapes_str = (
"list of %3d arrays\n"
"all shape %11s"
) % (len(images), stats.shapes[0],)
else:
shapes_str = (
"list of %3d arrays\n"
"varying shapes\n"
"smallest image: %11s\n"
"largest image: %11s\n"
"height: %3d to %3d\n"
"width: %3d to %3d\n"
"channels: %1s to %1s"
) % (len(images),
stats.smallest_shape, stats.largest_shape,
stats.height_min, stats.height_max,
stats.width_min, stats.width_max,
stats.get_channels_min("None"),
stats.get_channels_max("None"))
if stats.empty:
dtypes_str = ""
elif stats.all_same_dtype:
dtypes_str = "all dtype %8s" % (stats.dtypes[0],)
else:
dtypes_str = "dtypes: %s" % (", ".join(stats.unique_dtype_names),)
if stats.empty:
value_range_str = ""
else:
value_range_str = "value range: %3d to %3d"
if not stats.all_dtypes_intlike:
value_range_str = "value range: %6.4f to %6.4f"
value_range_str = value_range_str % (stats.value_min,
stats.value_max)
strs = [shapes_str, dtypes_str, value_range_str]
return _join_description_strs(strs)
# Added in 0.4.0.
def _generate_segmaps_description(segmaps, channel_idx, show_details):
"""Generate description for segmap columns."""
if len(segmaps) == 0:
return "empty list"
strs = _generate_sm_hm_description(segmaps, channel_idx, show_details)
arrs_channel = [segmap.arr[:, :, channel_idx] for segmap in segmaps]
stats_channel = _ListOfArraysStats(arrs_channel)
value_range_str = (
"value range: %3d to %3d\n"
"number of unique classes: %2d"
) % (stats_channel.value_min, stats_channel.value_max,
stats_channel.nb_unique_values)
return _join_description_strs(strs + [value_range_str])
# Added in 0.4.0.
def _generate_heatmaps_description(heatmaps, channel_idx, show_details):
"""Generate description for heatmap columns."""
if len(heatmaps) == 0:
return "empty list"
strs = _generate_sm_hm_description(heatmaps, channel_idx, show_details)
arrs_channel = [heatmap.arr_0to1[:, :, channel_idx] for heatmap in heatmaps]
stats_channel = _ListOfArraysStats(arrs_channel)
value_range_str = (
"value range: %6.4f to %6.4f\n"
" (internal, max is [0.0, 1.0])"
) % (stats_channel.value_min, stats_channel.value_max)
return _join_description_strs(strs + [value_range_str])
# Added in 0.4.0.
def _generate_sm_hm_description(augmentables, channel_idx, show_details):
"""Generate description for SegMap/Heatmap columns."""
if augmentables is None:
return ""
if len(augmentables) == 0:
return "empty list"
arrs = [augmentable.get_arr() for augmentable in augmentables]
stats = _ListOfArraysStats(arrs)
if stats.get_channels_max(-1) > -1:
channel_str = "Channel %1d of %1d" % (channel_idx+1,
stats.get_channels_max(-1))
else:
channel_str = ""
if not show_details:
shapes_str = ""
elif stats.all_same_shape:
shapes_str = (
"items for %3d images\n"
"all arrays of shape %11s"
) % (len(augmentables), stats.shapes[0],)
else:
shapes_str = (
"items for %3d images\n"
"varying array shapes\n"
"smallest: %11s\n"
"largest: %11s\n"
"height: %3d to %3d\n"
"width: %3d to %3d\n"
"channels: %1s to %1s"
) % (len(augmentables),
stats.smallest_shape, stats.largest_shape,
stats.height_min, stats.height_max,
stats.width_min, stats.width_max,
stats.get_channels_min("None"),
stats.get_channels_max("None"))
if not show_details:
on_shapes_str = ""
else:
on_shapes_str = _generate_on_image_shapes_descr(augmentables)
return [channel_str, shapes_str, on_shapes_str]
# Added in 0.4.0.
def _generate_cbasois_description(cbasois, images):
"""Generate description for coordinate-based augmentable columns."""
images_str = "items for %d images" % (len(cbasois),)
nb_items_lst = [len(cbasoi.items) for cbasoi in cbasois]
nb_items_lst = nb_items_lst if len(cbasois) > 0 else [-1]
nb_items = sum(nb_items_lst)
items_str = (
"fewest items on image: %3d\n"
"most items on image: %3d\n"
"total items: %6d"
) % (min(nb_items_lst), max(nb_items_lst), nb_items)
areas = [
cba.area if hasattr(cba, "area") else -1
for cbasoi in cbasois
for cba in cbasoi.items]
areas = areas if len(cbasois) > 0 else [-1]
areas_str = (
"smallest area: %7.4f\n"
"largest area: %7.4f"
) % (min(areas), max(areas))
labels = list(ia.flatten([item.label if hasattr(item, "label") else None
for cbasoi in cbasois
for item in cbasoi.items]))
labels_ctr = collections.Counter(labels)
labels_most_common = []
for label, count in labels_ctr.most_common(10):
labels_most_common.append("\n - %s (%3d, %6.2f%%)" % (
label, count, count/nb_items * 100))
labels_str = (
"unique labels: %2d\n"
"most common labels:"
"%s"
) % (len(labels_ctr.keys()), "".join(labels_most_common))
coords_ooi = []
dists = []
for cbasoi, image in zip(cbasois, images):
h, w = image.shape[0:2]
for cba in cbasoi.items:
coords = cba.coords
for coord in coords:
x, y = coord
dist = (x - w/2)**2 + (y - h/2) ** 2
coords_ooi.append(not (0 <= x < w and 0 <= y < h))
dists.append(((x, y), dist))
# use x_ and y_ because otherwise we get a 'redefines x' error in pylint
coords_extreme = [(x_, y_)
for (x_, y_), _
in sorted(dists, key=lambda t: t[1])]
nb_ooi = sum(coords_ooi)
ooi_str = (
"coords out of image: %d (%6.2f%%)\n"
"most extreme coord: (%5.1f, %5.1f)"
# TODO "items anyhow out of image: %d (%.2f%%)\n"
# TODO "items fully out of image: %d (%.2f%%)\n"
) % (nb_ooi, nb_ooi / len(coords_ooi) * 100,
coords_extreme[-1][0], coords_extreme[-1][1])
on_shapes_str = _generate_on_image_shapes_descr(cbasois)
return _join_description_strs([images_str, items_str, areas_str,
labels_str, ooi_str, on_shapes_str])
# Added in 0.4.0.
def _generate_on_image_shapes_descr(augmentables):
"""Generate text block for non-image data describing their image shapes."""
on_shapes = [augmentable.shape for augmentable in augmentables]
stats_imgs = _ListOfArraysStats([np.empty(on_shape)
for on_shape in on_shapes])
if stats_imgs.all_same_shape:
on_shapes_str = "all on image shape %11s" % (stats_imgs.shapes[0],)
else:
on_shapes_str = (
"on varying image shapes\n"
"smallest image: %11s\n"
"largest image: %11s"
) % (stats_imgs.smallest_shape, stats_imgs.largest_shape)
return on_shapes_str
# Added in 0.4.0.
def _join_description_strs(strs):
"""Join lines to a single string while removing empty lines."""
strs = [str_i for str_i in strs if len(str_i) > 0]
return "\n".join(strs)
class _ListOfArraysStats(object):
"""Class to derive aggregated values from a list of arrays.
E.g. shape of the largest array, number of unique dtypes etc.
Added in 0.4.0.
"""
def __init__(self, arrays):
self.arrays = arrays
# Added in 0.4.0.
@property
def empty(self):
return len(self.arrays) == 0
# Added in 0.4.0.
@property
def areas(self):
return [np.prod(arr.shape[0:2]) for arr in self.arrays]
# Added in 0.4.0.
@property
def arrays_by_area(self):
arrays_by_area = [
arr for arr, _
in sorted(zip(self.arrays, self.areas), key=lambda t: t[1])
]
return arrays_by_area
# Added in 0.4.0.
@property
def shapes(self):
return [arr.shape for arr in self.arrays]
# Added in 0.4.0.
@property
def all_same_shape(self):
if self.empty:
return True
return len(set(self.shapes)) == 1
# Added in 0.4.0.
@property
def smallest_shape(self):
if self.empty:
return tuple()
return self.arrays_by_area[0].shape
# Added in 0.4.0.
@property
def largest_shape(self):
if self.empty:
return tuple()
return self.arrays_by_area[-1].shape
# Added in 0.4.0.
@property
def area_max(self):
if self.empty:
return tuple()
return np.prod(self.arrays_by_area[-1][0:2])
# Added in 0.4.0.
@property
def heights(self):
return [arr.shape[0] for arr in self.arrays]
# Added in 0.4.0.
@property
def height_min(self):
heights = self.heights
return min(heights) if len(heights) > 0 else 0
# Added in 0.4.0.
@property
def height_max(self):
heights = self.heights
return max(heights) if len(heights) > 0 else 0
# Added in 0.4.0.
@property
def widths(self):
return [arr.shape[1] for arr in self.arrays]
# Added in 0.4.0.
@property
def width_min(self):
widths = self.widths
return min(widths) if len(widths) > 0 else 0
# Added in 0.4.0.
@property
def width_max(self):
widths = self.widths
return max(widths) if len(widths) > 0 else 0
# Added in 0.4.0.
def get_channels_min(self, default):
if self.empty:
return -1
if any([arr.ndim == 2 for arr in self.arrays]):
return default
return min([arr.shape[2] for arr in self.arrays if arr.ndim > 2])
# Added in 0.4.0.
def get_channels_max(self, default):
if self.empty:
return -1
if not any([arr.ndim > 2 for arr in self.arrays]):
return default
return max([arr.shape[2] for arr in self.arrays if arr.ndim > 2])
# Added in 0.4.0.
@property
def dtypes(self):
return [arr.dtype for arr in self.arrays]
# Added in 0.4.0.
@property
def dtype_names(self):
return [dtype.name for dtype in self.dtypes]
# Added in 0.4.0.
@property
def all_same_dtype(self):
return len(set(self.dtype_names)) in [0, 1]
# Added in 0.4.0.
@property
def all_dtypes_intlike(self):
if self.empty:
return True
return all([arr.dtype.kind in ["u", "i", "b"] for arr in self.arrays])
# Added in 0.4.0.
@property
def unique_dtype_names(self):
return sorted(list({arr.dtype.name for arr in self.arrays}))
# Added in 0.4.0.
@property
def value_min(self):
return min([np.min(arr) for arr in self.arrays])
# Added in 0.4.0.
@property
def value_max(self):
return max([np.max(arr) for arr in self.arrays])
# Added in 0.4.0.
@property
def nb_unique_values(self):
values_uq = set()
for arr in self.arrays:
values_uq.update(np.unique(arr))
return len(values_uq)
# Added in 0.4.0.
@six.add_metaclass(ABCMeta)
class _IImageDestination(object):
"""A destination which receives images to save."""
def on_batch(self, batch):
"""Signal to the destination that a new batch is processed.
This is intended to be used by the destination e.g. to count batches.
Added in 0.4.0.
Parameters
----------
batch : imgaug.augmentables.batches._BatchInAugmentation
A batch to which the next ``receive()`` call may correspond.
"""
def receive(self, image):
"""Receive and handle an image.
Added in 0.4.0.
Parameters
----------
image : ndarray
Image to be handled by the destination.
"""
# Added in 0.4.0.
class _MultiDestination(_IImageDestination):
"""A list of multiple destinations behaving like a single one."""
# Added in 0.4.0.
def __init__(self, destinations):
self.destinations = destinations
# Added in 0.4.0.
def on_batch(self, batch):
for destination in self.destinations:
destination.on_batch(batch)
# Added in 0.4.0.
def receive(self, image):
for destination in self.destinations:
destination.receive(image)
# Added in 0.4.0.
class _FolderImageDestination(_IImageDestination):
"""A destination which saves images to a directory."""
# Added in 0.4.0.
def __init__(self, folder_path,
filename_pattern="batch_{batch_id:06d}.png"):
super(_FolderImageDestination, self).__init__()
self.folder_path = folder_path
self.filename_pattern = filename_pattern
self._batch_id = -1
self._filepath = None
# Added in 0.4.0.
def on_batch(self, batch):
self._batch_id += 1
self._filepath = os.path.join(
self.folder_path,
self.filename_pattern.format(batch_id=self._batch_id))
# Added in 0.4.0.
def receive(self, image):
imageio.imwrite(self._filepath, image)
# Added in 0.4.0.
@six.add_metaclass(ABCMeta)
class _IBatchwiseSchedule(object):
"""A schedule determining per batch whether a condition is met."""
def on_batch(self, batch):
"""Determine for the given batch whether the condition is met.
Added in 0.4.0.
Parameters
----------
batch : _BatchInAugmentation
Batch for which to evaluate the condition.
Returns
-------
bool
Signal whether the condition is met.
"""
# Added in 0.4.0.
class _EveryNBatchesSchedule(_IBatchwiseSchedule):
"""A schedule that generates a signal at every ``N`` th batch.
This schedule must be called for *every* batch in order to count them.
Added in 0.4.0.
"""
def __init__(self, interval):
self.interval = interval
self._batch_id = -1
# Added in 0.4.0.
def on_batch(self, batch):
self._batch_id += 1
signal = (self._batch_id % self.interval == 0)
return signal
class _SaveDebugImage(meta.Augmenter):
"""Augmenter saving debug images to a destination according to a schedule.
Added in 0.4.0.
Parameters
----------
destination : _IImageDestination
The destination receiving debug images.
schedule : _IBatchwiseSchedule
The schedule to use to determine for which batches an image is
supposed to be generated.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
"""
# Added in 0.4.0.
def __init__(self, destination, schedule,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(_SaveDebugImage, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.destination = destination
self.schedule = schedule
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
save = self.schedule.on_batch(batch)
self.destination.on_batch(batch)
if save:
image = draw_debug_image(
images=batch.images,
heatmaps=batch.heatmaps,
segmentation_maps=batch.segmentation_maps,
keypoints=batch.keypoints,
bounding_boxes=batch.bounding_boxes,
polygons=batch.polygons,
line_strings=batch.line_strings)
self.destination.receive(image)
return batch
class SaveDebugImageEveryNBatches(_SaveDebugImage):
"""Visualize data in batches and save corresponding plots to a folder.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.debug.draw_debug_image`.
Parameters
----------
destination : str or _IImageDestination
Path to a folder. The saved images will follow a filename pattern
of ``batch_.png``. The latest image will additionally be
saved to ``latest.png``.
interval : int
Interval in batches. If set to ``N``, every ``N`` th batch an
image will be generated and saved, starting with the first observed
batch.
Note that the augmenter only counts batches that it sees. If it is
executed conditionally or re-instantiated, it may not see all batches
or the counter may be wrong in other ways.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> import tempfile
>>> folder_path = tempfile.mkdtemp()
>>> seq = iaa.Sequential([
>>> iaa.Sequential([
>>> iaa.Fliplr(0.5),
>>> iaa.Crop(px=(0, 16))
>>> ], random_order=True),
>>> iaa.SaveDebugImageEveryNBatches(folder_path, 100)
>>> ])
"""
# Added in 0.4.0.
def __init__(self, destination, interval,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
schedule = _EveryNBatchesSchedule(interval)
if not isinstance(destination, _IImageDestination):
assert os.path.isdir(destination), (
"Expected 'destination' to be a string path to an existing "
"directory. Got path '%s'." % (destination,))
destination = _MultiDestination([
_FolderImageDestination(destination),
_FolderImageDestination(destination,
filename_pattern="batch_latest.png")
])
super(SaveDebugImageEveryNBatches, self).__init__(
destination=destination, schedule=schedule,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def get_parameters(self):
dests = self.destination.destinations
return [
dests[0].folder_path,
dests[0].filename_pattern,
dests[1].folder_path,
dests[1].filename_pattern,
self.schedule.interval
]
================================================
FILE: imgaug/augmenters/edges.py
================================================
"""
Augmenters that deal with edge detection.
List of augmenters:
* :class:`Canny`
:class:`~imgaug.augmenters.convolutional.EdgeDetect` and
:class:`~imgaug.augmenters.convolutional.DirectedEdgeDetect` are currently
still in ``convolutional.py``.
"""
from __future__ import print_function, division, absolute_import
from abc import ABCMeta, abstractmethod
import numpy as np
import cv2
import six
import imgaug as ia
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from . import blend
from .. import parameters as iap
from .. import dtypes as iadt
# TODO this should be placed in some other file than edges.py as it could be
# re-used wherever a binary image is the result
@six.add_metaclass(ABCMeta)
class IBinaryImageColorizer(object):
"""Interface for classes that convert binary masks to color images."""
@abstractmethod
def colorize(self, image_binary, image_original, nth_image, random_state):
"""
Convert a binary image to a colorized one.
Parameters
----------
image_binary : ndarray
Boolean ``(H,W)`` image.
image_original : ndarray
Original ``(H,W,C)`` input image.
nth_image : int
Index of the image in the batch.
random_state : imgaug.random.RNG
Random state to use.
Returns
-------
ndarray
Colorized form of `image_binary`.
"""
# TODO see above, this should be moved to another file
class RandomColorsBinaryImageColorizer(IBinaryImageColorizer):
"""
Colorizer using two randomly sampled foreground/background colors.
Parameters
----------
color_true : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Color of the foreground, i.e. all pixels in binary images that are
``True``. This parameter will be queried once per image to
generate ``(3,)`` samples denoting the color. (Note that even for
grayscale images three values will be sampled and converted to
grayscale according to ``0.299*R + 0.587*G + 0.114*B``. This is the
same equation that is also used by OpenCV.)
* If an int, exactly that value will always be used, i.e. every
color will be ``(v, v, v)`` for value ``v``.
* If a tuple ``(a, b)``, three random values from the range
``a <= x <= b`` will be sampled per image.
* If a list, then three random values will be sampled from that
list per image.
* If a StochasticParameter, three values will be sampled from the
parameter per image.
color_false : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Analogous to `color_true`, but denotes the color for all pixels that
are ``False`` in the binary input image.
"""
def __init__(self, color_true=(0, 255), color_false=(0, 255)):
self.color_true = iap.handle_discrete_param(
color_true,
"color_true",
value_range=(0, 255),
tuple_to_uniform=True,
list_to_choice=True,
allow_floats=False)
self.color_false = iap.handle_discrete_param(
color_false,
"color_false",
value_range=(0, 255),
tuple_to_uniform=True,
list_to_choice=True,
allow_floats=False)
def _draw_samples(self, random_state):
color_true = self.color_true.draw_samples((3,),
random_state=random_state)
color_false = self.color_false.draw_samples((3,),
random_state=random_state)
return color_true, color_false
def colorize(self, image_binary, image_original, nth_image, random_state):
assert image_binary.ndim == 2, (
"Expected binary image to colorize to be 2-dimensional, "
"got %d dimensions." % (image_binary.ndim,))
assert image_binary.dtype.kind == "b", (
"Expected binary image to colorize to be boolean, "
"got dtype kind %s." % (image_binary.dtype.kind,))
assert image_original.ndim == 3, (
"Expected original image to be 3-dimensional, got %d "
"dimensions." % (image_original.ndim,))
assert image_original.shape[-1] in [1, 3, 4], (
"Expected original image to have 1, 3 or 4 channels. "
"Got %d channels." % (image_original.shape[-1],))
assert image_original.dtype == iadt._UINT8_DTYPE, (
"Expected original image to have dtype uint8, got dtype %s." % (
image_original.dtype.name))
color_true, color_false = self._draw_samples(random_state)
nb_channels = min(image_original.shape[-1], 3)
image_colorized = np.zeros(
(image_original.shape[0], image_original.shape[1], nb_channels),
dtype=image_original.dtype)
if nb_channels == 1:
# single channel input image, convert colors to grayscale
image_colorized[image_binary] = (
0.299*color_true[0]
+ 0.587*color_true[1]
+ 0.114*color_true[2])
image_colorized[~image_binary] = (
0.299*color_false[0]
+ 0.587*color_false[1]
+ 0.114*color_false[2])
else:
image_colorized[image_binary] = color_true
image_colorized[~image_binary] = color_false
# re-attach alpha channel if it was present in input image
if image_original.shape[-1] == 4:
image_colorized = np.dstack(
[image_colorized, image_original[:, :, 3:4]])
return image_colorized
def __str__(self):
return ("RandomColorsBinaryImageColorizer("
"color_true=%s, color_false=%s)") % (
self.color_true, self.color_false)
class Canny(meta.Augmenter):
"""
Apply a canny edge detector to input images.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no; not tested
* ``uint32``: no; not tested
* ``uint64``: no; not tested
* ``int8``: no; not tested
* ``int16``: no; not tested
* ``int32``: no; not tested
* ``int64``: no; not tested
* ``float16``: no; not tested
* ``float32``: no; not tested
* ``float64``: no; not tested
* ``float128``: no; not tested
* ``bool``: no; not tested
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Blending factor to use in alpha blending.
A value close to 1.0 means that only the edge image is visible.
A value close to 0.0 means that only the original image is visible.
A value close to 0.5 means that the images are merged according to
`0.5*image + 0.5*edge_image`.
If a sample from this parameter is 0, no action will be performed for
the corresponding image.
* If an int or float, exactly that value will be used.
* If a tuple ``(a, b)``, a random value from the range
``a <= x <= b`` will be sampled per image.
* If a list, then a random value will be sampled from that list
per image.
* If a StochasticParameter, a value will be sampled from the
parameter per image.
hysteresis_thresholds : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or tuple of tuple of number or tuple of list of number or tuple of imgaug.parameters.StochasticParameter, optional
Min and max values to use in hysteresis thresholding.
(This parameter seems to have not very much effect on the results.)
Either a single parameter or a tuple of two parameters.
If a single parameter is provided, the sampling happens once for all
images with `(N,2)` samples being requested from the parameter,
where each first value denotes the hysteresis minimum and each second
the maximum.
If a tuple of two parameters is provided, one sampling of `(N,)` values
is independently performed per parameter (first parameter: hysteresis
minimum, second: hysteresis maximum).
* If this is a single number, both min and max value will always be
exactly that value.
* If this is a tuple of numbers ``(a, b)``, two random values from
the range ``a <= x <= b`` will be sampled per image.
* If this is a list, two random values will be sampled from that
list per image.
* If this is a StochasticParameter, two random values will be
sampled from that parameter per image.
* If this is a tuple ``(min, max)`` with ``min`` and ``max``
both *not* being numbers, they will be treated according to the
rules above (i.e. may be a number, tuple, list or
StochasticParameter). A single value will be sampled per image
and parameter.
sobel_kernel_size : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Kernel size of the sobel operator initially applied to each image.
This corresponds to ``apertureSize`` in ``cv2.Canny()``.
If a sample from this parameter is ``<=1``, no action will be performed
for the corresponding image.
The maximum for this parameter is ``7`` (inclusive). Higher values are
not accepted by OpenCV.
If an even value ``v`` is sampled, it is automatically changed to
``v-1``.
* If this is a single integer, the kernel size always matches that
value.
* If this is a tuple of integers ``(a, b)``, a random discrete
value will be sampled from the range ``a <= x <= b`` per image.
* If this is a list, a random value will be sampled from that
list per image.
* If this is a StochasticParameter, a random value will be sampled
from that parameter per image.
colorizer : None or imgaug.augmenters.edges.IBinaryImageColorizer, optional
A strategy to convert binary edge images to color images.
If this is ``None``, an instance of ``RandomColorBinaryImageColorizer``
is created, which means that each edge image is converted into an
``uint8`` image, where edge and non-edge pixels each have a different
color that was uniformly randomly sampled from the space of all
``uint8`` colors.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Canny()
Create an augmenter that generates random blends between images and
their canny edge representations.
>>> aug = iaa.Canny(alpha=(0.0, 0.5))
Create a canny edge augmenter that generates edge images with a blending
factor of max ``50%``, i.e. the original (non-edge) image is always at
least partially visible.
>>> aug = iaa.Canny(
>>> alpha=(0.0, 0.5),
>>> colorizer=iaa.RandomColorsBinaryImageColorizer(
>>> color_true=255,
>>> color_false=0
>>> )
>>> )
Same as in the previous example, but the edge image always uses the
color white for edges and black for the background.
>>> aug = iaa.Canny(alpha=(0.5, 1.0), sobel_kernel_size=[3, 7])
Create a canny edge augmenter that initially preprocesses images using
a sobel filter with kernel size of either ``3x3`` or ``13x13`` and
alpha-blends with result using a strength of ``50%`` (both images
equally visible) to ``100%`` (only edge image visible).
>>> aug = iaa.Alpha(
>>> (0.0, 1.0),
>>> iaa.Canny(alpha=1),
>>> iaa.MedianBlur(13)
>>> )
Create an augmenter that blends a canny edge image with a median-blurred
version of the input image. The median blur uses a fixed kernel size
of ``13x13`` pixels.
"""
def __init__(self,
alpha=(0.0, 1.0),
hysteresis_thresholds=((100-40, 100+40), (200-40, 200+40)),
sobel_kernel_size=(3, 7),
colorizer=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Canny, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.alpha = iap.handle_continuous_param(
alpha, "alpha", value_range=(0, 1.0), tuple_to_uniform=True,
list_to_choice=True)
if isinstance(hysteresis_thresholds, tuple) \
and len(hysteresis_thresholds) == 2 \
and not ia.is_single_number(hysteresis_thresholds[0]) \
and not ia.is_single_number(hysteresis_thresholds[1]):
self.hysteresis_thresholds = (
iap.handle_discrete_param(
hysteresis_thresholds[0],
"hysteresis_thresholds[0]",
value_range=(0, 255),
tuple_to_uniform=True,
list_to_choice=True,
allow_floats=True),
iap.handle_discrete_param(
hysteresis_thresholds[1],
"hysteresis_thresholds[1]",
value_range=(0, 255),
tuple_to_uniform=True,
list_to_choice=True,
allow_floats=True)
)
else:
self.hysteresis_thresholds = iap.handle_discrete_param(
hysteresis_thresholds,
"hysteresis_thresholds",
value_range=(0, 255),
tuple_to_uniform=True,
list_to_choice=True,
allow_floats=True)
# we don't use handle_discrete_kernel_size_param() here, because
# cv2.Canny() can't handle independent height/width values, only a
# single kernel size
self.sobel_kernel_size = iap.handle_discrete_param(
sobel_kernel_size,
"sobel_kernel_size",
value_range=(0, 7), # OpenCV only accepts ksize up to 7
tuple_to_uniform=True,
list_to_choice=True,
allow_floats=False)
self.colorizer = (
colorizer
if colorizer is not None
else RandomColorsBinaryImageColorizer()
)
def _draw_samples(self, augmentables, random_state):
nb_images = len(augmentables)
rss = random_state.duplicate(4)
alpha_samples = self.alpha.draw_samples((nb_images,), rss[0])
hthresh = self.hysteresis_thresholds
if isinstance(hthresh, tuple):
min_values = hthresh[0].draw_samples((nb_images,), rss[1])
max_values = hthresh[1].draw_samples((nb_images,), rss[2])
hthresh_samples = np.stack([min_values, max_values], axis=-1)
else:
hthresh_samples = hthresh.draw_samples((nb_images, 2), rss[1])
sobel_samples = self.sobel_kernel_size.draw_samples((nb_images,),
rss[3])
# verify for hysteresis thresholds that min_value < max_value everywhere
invalid = (hthresh_samples[:, 0] > hthresh_samples[:, 1])
if np.any(invalid):
hthresh_samples[invalid, :] = hthresh_samples[invalid, :][:, [1, 0]]
# ensure that sobel kernel sizes are correct
# note that OpenCV accepts only kernel sizes that are (a) even
# and (b) <=7
assert not np.any(sobel_samples < 0), (
"Sampled a sobel kernel size below 0 in Canny. "
"Allowed value range is 0 to 7.")
assert not np.any(sobel_samples > 7), (
"Sampled a sobel kernel size above 7 in Canny. "
"Allowed value range is 0 to 7.")
even_idx = (np.mod(sobel_samples, 2) == 0)
sobel_samples[even_idx] -= 1
return alpha_samples, hthresh_samples, sobel_samples
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
iadt.allow_only_uint8(images, augmenter=self)
rss = random_state.duplicate(len(images))
samples = self._draw_samples(images, rss[-1])
alpha_samples = samples[0]
hthresh_samples = samples[1]
sobel_samples = samples[2]
gen = enumerate(zip(images, alpha_samples, hthresh_samples,
sobel_samples))
for i, (image, alpha, hthreshs, sobel) in gen:
assert image.shape[-1] in [1, 3, 4], (
"Canny edge detector can currently only handle images with "
"channel numbers that are 1, 3 or 4. Got %d.") % (
image.shape[-1],)
has_zero_sized_axes = (0 in image.shape[0:2])
if alpha > 0 and sobel > 1 and not has_zero_sized_axes:
image_canny = cv2.Canny(
_normalize_cv2_input_arr_(image[:, :, 0:3]),
threshold1=hthreshs[0],
threshold2=hthreshs[1],
apertureSize=sobel,
L2gradient=True)
image_canny = (image_canny > 0)
# canny returns a boolean (H,W) image, so we change it to
# (H,W,C) and then uint8
image_canny_color = self.colorizer.colorize(
image_canny, image, nth_image=i, random_state=rss[i])
batch.images[i] = blend.blend_alpha(image_canny_color, image,
alpha)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.alpha, self.hysteresis_thresholds, self.sobel_kernel_size,
self.colorizer]
def __str__(self):
return ("Canny("
"alpha=%s, "
"hysteresis_thresholds=%s, "
"sobel_kernel_size=%s, "
"colorizer=%s, "
"name=%s, "
"deterministic=%s)" % (
self.alpha, self.hysteresis_thresholds,
self.sobel_kernel_size, self.colorizer,
self.name, self.deterministic))
================================================
FILE: imgaug/augmenters/flip.py
================================================
"""
Augmenters that apply mirroring/flipping operations to images.
List of augmenters:
* :class:`Fliplr`
* :class:`Flipud`
"""
from __future__ import print_function, division, absolute_import
import numpy as np
import cv2
import six.moves as sm
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from .. import parameters as iap
from .. import dtypes as iadt
# pylint:disable=pointless-string-statement
"""
Speed comparison by datatype and flip method.
HORIZONTAL FLIPS.
----------
bool
----------
slice 0.00052ms
slice, contig 0.21878ms
fliplr 0.00180ms
fliplr contig 0.22000ms
cv2 Error: Expected cv::UMat for argument 'src'
cv2 contig Error: Expected cv::UMat for argument 'src'
fort cv2 Error: Expected cv::UMat for argument 'src'
fort cv2 contig Error: Expected cv::UMat for argument 'src'
cv2_ Error: Expected cv::UMat for argument 'src'
cv2_ contig Error: Expected cv::UMat for argument 'src'
fort cv2_ Error: Expected cv::UMat for argument 'src'
fort cv2_ contig Error: Expected cv::UMat for argument 'src'
cv2_ get Error: Expected cv::UMat for argument 'src'
cv2_ get contig Error: Expected cv::UMat for argument 'src'
fort cv2_ get Error: Expected cv::UMat for argument 'src'
fort cv2_ get contig Error: Expected cv::UMat for argument 'src'
----------
uint8
----------
slice 0.00052ms
slice, contig 0.21878ms
fliplr 0.00174ms
fliplr contig 0.21828ms
cv2 0.07037ms (3.11x)
cv2 contig 0.07355ms (2.97x)
fort cv2 0.33900ms (0.65x)
fort cv2 contig 0.34198ms (0.64x)
cv2_ 0.08093ms (2.70x)
cv2_ contig 0.08554ms (2.56x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.25783ms (0.17x)
fort cv2_ get contig 1.25868ms (0.17x)
----------
uint16
----------
slice 0.00176ms
slice, contig 0.21250ms
fliplr 0.00489ms
fliplr contig 0.21438ms
cv2 0.16964ms (1.25x)
cv2 contig 0.17314ms (1.23x)
fort cv2 0.50989ms (0.42x)
fort cv2 contig 0.51188ms (0.42x)
cv2_ 0.18803ms (1.13x)
cv2_ contig 0.19136ms (1.11x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.48429ms (0.14x)
fort cv2_ get contig 1.48392ms (0.14x)
----------
uint32
----------
slice 0.00181ms
slice, contig 0.22855ms
fliplr 0.00486ms
fliplr contig 0.23070ms
cv2 Error: Expected cv::UMat for argument 'src'
cv2 contig Error: Expected cv::UMat for argument 'src'
fort cv2 Error: Expected cv::UMat for argument 'src'
fort cv2 contig Error: Expected cv::UMat for argument 'src'
cv2_ Error: Expected cv::UMat for argument 'src'
cv2_ contig Error: Expected cv::UMat for argument 'src'
fort cv2_ Error: Expected cv::UMat for argument 'src'
fort cv2_ contig Error: Expected cv::UMat for argument 'src'
cv2_ get Error: Expected cv::UMat for argument 'src'
cv2_ get contig Error: Expected cv::UMat for argument 'src'
fort cv2_ get Error: Expected cv::UMat for argument 'src'
fort cv2_ get contig Error: Expected cv::UMat for argument 'src'
----------
uint64
----------
slice 0.00175ms
slice, contig 0.28662ms
fliplr 0.00497ms
fliplr contig 0.28986ms
cv2 Error: Got dtype int32
cv2 contig Error: Got dtype int32
fort cv2 Error: Got dtype int32
fort cv2 contig Error: Got dtype int32
cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
cv2_ contig Error: Got dtype object
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: Got dtype int32
cv2_ get contig Error: Got dtype int32
fort cv2_ get Error: Got dtype int32
fort cv2_ get contig Error: Got dtype int32
----------
int8
----------
slice 0.00052ms
slice, contig 0.21802ms
fliplr 0.00183ms
fliplr contig 0.21866ms
cv2 0.07026ms (3.10x)
cv2 contig 0.07234ms (3.01x)
fort cv2 0.34137ms (0.64x)
fort cv2 contig 0.35426ms (0.62x)
cv2_ 0.08145ms (2.68x)
cv2_ contig 0.08498ms (2.57x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.27798ms (0.17x)
fort cv2_ get contig 1.26791ms (0.17x)
----------
int16
----------
slice 0.00146ms
slice, contig 0.21083ms
fliplr 0.00443ms
fliplr contig 0.21287ms
cv2 0.17461ms (1.21x)
cv2 contig 0.17523ms (1.20x)
fort cv2 0.51030ms (0.41x)
fort cv2 contig 0.50438ms (0.42x)
cv2_ 0.18627ms (1.13x)
cv2_ contig 0.19703ms (1.07x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.47157ms (0.14x)
fort cv2_ get contig 1.48715ms (0.14x)
----------
int32
----------
slice 0.00177ms
slice, contig 0.22641ms
fliplr 0.00505ms
fliplr contig 0.22934ms
cv2 0.33548ms (0.67x)
cv2 contig 0.34303ms (0.66x)
fort cv2 0.77874ms (0.29x)
fort cv2 contig 0.78380ms (0.29x)
cv2_ 0.39785ms (0.57x)
cv2_ contig 0.39249ms (0.58x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.89115ms (0.12x)
fort cv2_ get contig 1.89214ms (0.12x)
----------
int64
----------
slice 0.00176ms
slice, contig 0.28347ms
fliplr 0.00484ms
fliplr contig 0.28658ms
cv2 Error: Got dtype int32
cv2 contig Error: Got dtype int32
fort cv2 Error: Got dtype int32
fort cv2 contig Error: Got dtype int32
cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
cv2_ contig Error: Got dtype object
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: Got dtype int32
cv2_ get contig Error: Got dtype int32
fort cv2_ get Error: Got dtype int32
fort cv2_ get contig Error: Got dtype int32
----------
float16
----------
slice 0.00155ms
slice, contig 0.21066ms
fliplr 0.00435ms
fliplr contig 0.21295ms
cv2 Error: Expected cv::UMat for argument 'src'
cv2 contig Error: Expected cv::UMat for argument 'src'
fort cv2 Error: Expected cv::UMat for argument 'src'
fort cv2 contig Error: Expected cv::UMat for argument 'src'
cv2_ Error: Expected cv::UMat for argument 'src'
cv2_ contig Error: Expected cv::UMat for argument 'src'
fort cv2_ Error: Expected cv::UMat for argument 'src'
fort cv2_ contig Error: Expected cv::UMat for argument 'src'
cv2_ get Error: Expected cv::UMat for argument 'src'
cv2_ get contig Error: Expected cv::UMat for argument 'src'
fort cv2_ get Error: Expected cv::UMat for argument 'src'
fort cv2_ get contig Error: Expected cv::UMat for argument 'src'
----------
float32
----------
slice 0.00177ms
slice, contig 0.22798ms
fliplr 0.00495ms
fliplr contig 0.22916ms
cv2 0.33821ms (0.67x)
cv2 contig 0.31665ms (0.72x)
fort cv2 0.78119ms (0.29x)
fort cv2 contig 0.77838ms (0.29x)
cv2_ 0.39515ms (0.58x)
cv2_ contig 0.41023ms (0.56x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.88737ms (0.12x)
fort cv2_ get contig 1.89616ms (0.12x)
----------
float64
----------
slice 0.00179ms
slice, contig 0.28810ms
fliplr 0.00495ms
fliplr contig 0.29130ms
cv2 0.63258ms (0.46x)
cv2 contig 0.64089ms (0.45x)
fort cv2 1.64795ms (0.17x)
fort cv2 contig 1.65449ms (0.17x)
cv2_ 0.75202ms (0.38x)
cv2_ contig 0.74789ms (0.39x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 3.99237ms (0.07x)
fort cv2_ get contig 3.97847ms (0.07x)
----------
float128
----------
slice 0.00179ms
slice, contig 0.51371ms
fliplr 0.00545ms
fliplr contig 0.51618ms
cv2 Error: Expected cv::UMat for argument 'src'
cv2 contig Error: Expected cv::UMat for argument 'src'
fort cv2 Error: Expected cv::UMat for argument 'src'
fort cv2 contig Error: Expected cv::UMat for argument 'src'
cv2_ Error: Expected cv::UMat for argument 'src'
cv2_ contig Error: Expected cv::UMat for argument 'src'
fort cv2_ Error: Expected cv::UMat for argument 'src'
fort cv2_ contig Error: Expected cv::UMat for argument 'src'
cv2_ get Error: Expected cv::UMat for argument 'src'
cv2_ get contig Error: Expected cv::UMat for argument 'src'
fort cv2_ get Error: Expected cv::UMat for argument 'src'
fort cv2_ get contig Error: Expected cv::UMat for argument 'src'
==============================
flip method followed by Add
==============================
slice 1.29597ms
slice, contig 1.32523ms
fliplr 1.29298ms
fliplr contig 1.33087ms
cv2 1.17829ms
cv2 contig 1.18350ms
fort cv2 1.45182ms
fort cv2 contig 1.45762ms
cv2_ 1.19331ms
cv2_ contig 1.19364ms
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 2.43293ms
fort cv2_ get contig 2.48836ms
==============================
flip method followed by Affine
==============================
slice 2.83081ms
slice, contig 2.88243ms
fliplr 2.84253ms
fliplr contig 2.89106ms
cv2 2.72900ms
cv2 contig 2.74500ms
fort cv2 2.99842ms
fort cv2 contig 3.03457ms
cv2_ 2.73629ms
cv2_ contig 2.77505ms
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 4.01583ms
fort cv2_ get contig 4.03347ms
==============================
flip method followed by AverageBlur
==============================
slice 0.77109ms
slice, contig 0.80603ms
fliplr 0.77666ms
fliplr contig 0.81088ms
cv2 0.66065ms
cv2 contig 0.66496ms
fort cv2 0.94078ms
fort cv2 contig 0.92662ms
cv2_ 0.65560ms
cv2_ contig 0.66237ms
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.88932ms
fort cv2_ get contig 1.89190ms
"""
"""
Speed comparison by datatype and flip method.
VERTICAL FLIPS.
----------
bool
----------
slice 0.00047ms
slice, contig 0.02332ms
flipud 0.00143ms
flipud contig 0.02502ms
cv2 Error: Expected cv::UMat for argument 'src'
cv2 contig Error: Expected cv::UMat for argument 'src'
fort cv2 Error: Expected cv::UMat for argument 'src'
fort cv2 contig Error: Expected cv::UMat for argument 'src'
cv2_ Error: Expected cv::UMat for argument 'src'
cv2_ contig Error: Expected cv::UMat for argument 'src'
fort cv2_ Error: Expected cv::UMat for argument 'src'
fort cv2_ contig Error: Expected cv::UMat for argument 'src'
cv2_ get Error: Expected cv::UMat for argument 'src'
cv2_ get contig Error: Expected cv::UMat for argument 'src'
fort cv2_ get Error: Expected cv::UMat for argument 'src'
fort cv2_ get contig Error: Expected cv::UMat for argument 'src'
----------
uint8
----------
slice 0.00049ms
slice, contig 0.02311ms
flipud 0.00155ms
flipud contig 0.02506ms
cv2 0.03030ms (0.76x)
cv2 contig 0.03401ms (0.68x)
fort cv2 0.31000ms (0.07x)
fort cv2 contig 0.33619ms (0.07x)
cv2_ 0.01753ms (1.32x)
cv2_ contig 0.01841ms (1.26x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.26680ms (0.02x)
fort cv2_ get contig 1.27027ms (0.02x)
----------
uint16
----------
slice 0.00147ms
slice, contig 0.04861ms
flipud 0.00406ms
flipud contig 0.05060ms
cv2 0.06169ms (0.79x)
cv2 contig 0.06397ms (0.76x)
fort cv2 0.39190ms (0.12x)
fort cv2 contig 0.39098ms (0.12x)
cv2_ 0.03375ms (1.44x)
cv2_ contig 0.03619ms (1.34x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.51197ms (0.03x)
fort cv2_ get contig 1.52977ms (0.03x)
----------
uint32
----------
slice 0.00167ms
slice, contig 0.09415ms
flipud 0.00476ms
flipud contig 0.09660ms
cv2 Error: Expected cv::UMat for argument 'src'
cv2 contig Error: Expected cv::UMat for argument 'src'
fort cv2 Error: Expected cv::UMat for argument 'src'
fort cv2 contig Error: Expected cv::UMat for argument 'src'
cv2_ Error: Expected cv::UMat for argument 'src'
cv2_ contig Error: Expected cv::UMat for argument 'src'
fort cv2_ Error: Expected cv::UMat for argument 'src'
fort cv2_ contig Error: Expected cv::UMat for argument 'src'
cv2_ get Error: Expected cv::UMat for argument 'src'
cv2_ get contig Error: Expected cv::UMat for argument 'src'
fort cv2_ get Error: Expected cv::UMat for argument 'src'
fort cv2_ get contig Error: Expected cv::UMat for argument 'src'
----------
uint64
----------
slice 0.00175ms
slice, contig 0.17413ms
flipud 0.00488ms
flipud contig 0.17508ms
cv2 Error: Got dtype int32
cv2 contig Error: Got dtype int32
fort cv2 Error: Got dtype int32
fort cv2 contig Error: Got dtype int32
cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
cv2_ contig Error: Got dtype object
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: Got dtype int32
cv2_ get contig Error: Got dtype int32
fort cv2_ get Error: Got dtype int32
fort cv2_ get contig Error: Got dtype int32
----------
int8
----------
slice 0.00057ms
slice, contig 0.02604ms
flipud 0.00146ms
flipud contig 0.02806ms
cv2 0.03434ms (0.76x)
cv2 contig 0.03676ms (0.71x)
fort cv2 0.30920ms (0.08x)
fort cv2 contig 0.31123ms (0.08x)
cv2_ 0.01625ms (1.60x)
cv2_ contig 0.01799ms (1.45x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.29065ms (0.02x)
fort cv2_ get contig 1.29596ms (0.02x)
----------
int16
----------
slice 0.00142ms
slice, contig 0.05079ms
flipud 0.00403ms
flipud contig 0.05312ms
cv2 0.06277ms (0.81x)
cv2 contig 0.06527ms (0.78x)
fort cv2 0.39710ms (0.13x)
fort cv2 contig 0.39851ms (0.13x)
cv2_ 0.03419ms (1.49x)
cv2_ contig 0.03668ms (1.38x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.52476ms (0.03x)
fort cv2_ get contig 1.52594ms (0.03x)
----------
int32
----------
slice 0.00172ms
slice, contig 0.09621ms
flipud 0.00469ms
flipud contig 0.09868ms
cv2 0.12192ms (0.79x)
cv2 contig 0.12459ms (0.77x)
fort cv2 0.57915ms (0.17x)
fort cv2 contig 0.58571ms (0.16x)
cv2_ 0.07337ms (1.31x)
cv2_ contig 0.07595ms (1.27x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.97937ms (0.05x)
fort cv2_ get contig 1.97919ms (0.05x)
----------
int64
----------
slice 0.00167ms
slice, contig 0.17308ms
flipud 0.00470ms
flipud contig 0.17542ms
cv2 Error: Got dtype int32
cv2 contig Error: Got dtype int32
fort cv2 Error: Got dtype int32
fort cv2 contig Error: Got dtype int32
cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
cv2_ contig Error: Got dtype object
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: Got dtype int32
cv2_ get contig Error: Got dtype int32
fort cv2_ get Error: Got dtype int32
fort cv2_ get contig Error: Got dtype int32
----------
float16
----------
slice 0.00146ms
slice, contig 0.05094ms
flipud 0.00408ms
flipud contig 0.05359ms
cv2 Error: Expected cv::UMat for argument 'src'
cv2 contig Error: Expected cv::UMat for argument 'src'
fort cv2 Error: Expected cv::UMat for argument 'src'
fort cv2 contig Error: Expected cv::UMat for argument 'src'
cv2_ Error: Expected cv::UMat for argument 'src'
cv2_ contig Error: Expected cv::UMat for argument 'src'
fort cv2_ Error: Expected cv::UMat for argument 'src'
fort cv2_ contig Error: Expected cv::UMat for argument 'src'
cv2_ get Error: Expected cv::UMat for argument 'src'
cv2_ get contig Error: Expected cv::UMat for argument 'src'
fort cv2_ get Error: Expected cv::UMat for argument 'src'
fort cv2_ get contig Error: Expected cv::UMat for argument 'src'
----------
float32
----------
slice 0.00164ms
slice, contig 0.09606ms
flipud 0.00461ms
flipud contig 0.09897ms
cv2 0.12151ms (0.79x)
cv2 contig 0.12514ms (0.77x)
fort cv2 0.57887ms (0.17x)
fort cv2 contig 0.58280ms (0.16x)
cv2_ 0.07312ms (1.31x)
cv2_ contig 0.07581ms (1.27x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.97501ms (0.05x)
fort cv2_ get contig 1.98010ms (0.05x)
----------
float64
----------
slice 0.00170ms
slice, contig 0.17300ms
flipud 0.00483ms
flipud contig 0.17539ms
cv2 0.23941ms (0.72x)
cv2 contig 0.24220ms (0.71x)
fort cv2 1.26645ms (0.14x)
fort cv2 contig 1.27246ms (0.14x)
cv2_ 0.14879ms (1.16x)
cv2_ contig 0.15185ms (1.14x)
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 4.02958ms (0.04x)
fort cv2_ get contig 4.02822ms (0.04x)
----------
float128
----------
slice 0.00168ms
slice, contig 0.35407ms
flipud 0.00513ms
flipud contig 0.35716ms
cv2 Error: Expected cv::UMat for argument 'src'
cv2 contig Error: Expected cv::UMat for argument 'src'
fort cv2 Error: Expected cv::UMat for argument 'src'
fort cv2 contig Error: Expected cv::UMat for argument 'src'
cv2_ Error: Expected cv::UMat for argument 'src'
cv2_ contig Error: Expected cv::UMat for argument 'src'
fort cv2_ Error: Expected cv::UMat for argument 'src'
fort cv2_ contig Error: Expected cv::UMat for argument 'src'
cv2_ get Error: Expected cv::UMat for argument 'src'
cv2_ get contig Error: Expected cv::UMat for argument 'src'
fort cv2_ get Error: Expected cv::UMat for argument 'src'
fort cv2_ get contig Error: Expected cv::UMat for argument 'src'
==============================
flip method followed by Add
==============================
slice 1.11286ms
slice, contig 1.14822ms
flipud 1.11858ms
flipud contig 1.14703ms
cv2 1.14892ms
cv2 contig 1.15923ms
fort cv2 1.43633ms
fort cv2 contig 1.44342ms
cv2_ 1.15194ms
cv2_ contig 1.14864ms
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 2.47966ms
fort cv2_ get contig 2.48177ms
==============================
flip method followed by Affine
==============================
slice 2.61450ms
slice, contig 2.67402ms
flipud 2.62069ms
flipud contig 2.67267ms
cv2 2.66312ms
cv2 contig 2.68482ms
fort cv2 2.94520ms
fort cv2 contig 2.97767ms
cv2_ 2.64183ms
cv2_ contig 2.64857ms
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 3.94321ms
fort cv2_ get contig 4.01652ms
==============================
flip method followed by AverageBlur
==============================
slice 0.57750ms
slice, contig 0.60905ms
flipud 0.58156ms
flipud contig 0.61562ms
cv2 0.61704ms
cv2 contig 0.62293ms
fort cv2 0.90804ms
fort cv2 contig 0.90495ms
cv2_ 0.60047ms
cv2_ contig 0.60876ms
fort cv2_ Error: 'cv2.UMat' object has no attribute 'ndim'
fort cv2_ contig Error: Got dtype object
cv2_ get Error: 'numpy.ndarray' object has no attribute 'get'
cv2_ get contig Error: 'numpy.ndarray' object has no attribute 'get'
fort cv2_ get 1.93064ms
fort cv2_ get contig 1.95215ms
"""
# pylint:enable=pointless-string-statement
_FLIPLR_DTYPES_CV2 = iadt._convert_dtype_strs_to_types(
"uint8 uint16 int8 int16"
)
def fliplr(arr):
"""Flip an image-like array horizontally.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; fully tested
* ``uint32``: yes; fully tested
* ``uint64``: yes; fully tested
* ``int8``: yes; fully tested
* ``int16``: yes; fully tested
* ``int32``: yes; fully tested
* ``int64``: yes; fully tested
* ``float16``: yes; fully tested
* ``float32``: yes; fully tested
* ``float64``: yes; fully tested
* ``float128``: yes; fully tested
* ``bool``: yes; fully tested
Parameters
----------
arr : ndarray
A 2D/3D `(H, W, [C])` image array.
Returns
-------
ndarray
Horizontally flipped array.
Examples
--------
>>> import numpy as np
>>> import imgaug.augmenters.flip as flip
>>> arr = np.arange(16).reshape((4, 4))
>>> arr_flipped = flip.fliplr(arr)
Create a ``4x4`` array and flip it horizontally.
"""
# we don't check here if #channels > 512, because the cv2 function also
# kinda works with that, it is very rare to happen and would induce an
# additional check (with significant relative impact on runtime considering
# flipping is already ultra fast)
if arr.dtype in _FLIPLR_DTYPES_CV2:
return _fliplr_cv2(arr)
return _fliplr_sliced(arr)
def _fliplr_sliced(arr):
return arr[:, ::-1, ...]
def _fliplr_cv2(arr):
# cv2.flip() returns None for arrays with zero height or width
# and turns channels=0 to channels=512
if arr.size == 0:
return np.copy(arr)
# cv2.flip() fails for more than 512 channels
if arr.ndim == 3 and arr.shape[-1] > 512:
# TODO this is quite inefficient right now
channels = [
cv2.flip(_normalize_cv2_input_arr_(arr[..., c]), 1)
for c
in sm.xrange(arr.shape[-1])
]
result = np.stack(channels, axis=-1)
else:
# Normalization from imgaug.imgaug._normalize_cv2_input_arr_().
# Moved here for performance reasons. Keep this aligned.
# TODO recalculate timings, they were computed without this.
flags = arr.flags
if not flags["OWNDATA"]:
arr = np.copy(arr)
flags = arr.flags
if not flags["C_CONTIGUOUS"]:
arr = np.ascontiguousarray(arr)
result = cv2.flip(_normalize_cv2_input_arr_(arr), 1)
if result.ndim == 2 and arr.ndim == 3:
return result[..., np.newaxis]
return result
def flipud(arr):
"""Flip an image-like array vertically.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; fully tested
* ``uint32``: yes; fully tested
* ``uint64``: yes; fully tested
* ``int8``: yes; fully tested
* ``int16``: yes; fully tested
* ``int32``: yes; fully tested
* ``int64``: yes; fully tested
* ``float16``: yes; fully tested
* ``float32``: yes; fully tested
* ``float64``: yes; fully tested
* ``float128``: yes; fully tested
* ``bool``: yes; fully tested
Parameters
----------
arr : ndarray
A 2D/3D `(H, W, [C])` image array.
Returns
-------
ndarray
Vertically flipped array.
Examples
--------
>>> import numpy as np
>>> import imgaug.augmenters.flip as flip
>>> arr = np.arange(16).reshape((4, 4))
>>> arr_flipped = flip.flipud(arr)
Create a ``4x4`` array and flip it vertically.
"""
# Note that this function is currently not called by Flipud for performance
# reasons. Changing this will therefore not affect Flipud.
return arr[::-1, ...]
def HorizontalFlip(*args, **kwargs):
"""Alias for Fliplr."""
# pylint: disable=invalid-name
return Fliplr(*args, **kwargs)
def VerticalFlip(*args, **kwargs):
"""Alias for Flipud."""
# pylint: disable=invalid-name
return Flipud(*args, **kwargs)
class Fliplr(meta.Augmenter):
"""Flip/mirror input images horizontally.
.. note::
The default value for the probability is ``0.0``.
So, to flip *all* input images use ``Fliplr(1.0)`` and *not* just
``Fliplr()``.
**Supported dtypes**:
See :func:`~imgaug.augmenters.flip.fliplr`.
Parameters
----------
p : number or imgaug.parameters.StochasticParameter, optional
Probability of each image to get flipped.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Fliplr(0.5)
Flip ``50`` percent of all images horizontally.
>>> aug = iaa.Fliplr(1.0)
Flip all images horizontally.
"""
def __init__(self, p=1,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Fliplr, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.p = iap.handle_probability_param(p, "p")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
samples = self.p.draw_samples((batch.nb_rows,),
random_state=random_state)
for i, sample in enumerate(samples):
if sample >= 0.5:
if batch.images is not None:
batch.images[i] = fliplr(batch.images[i])
if batch.heatmaps is not None:
batch.heatmaps[i].arr_0to1 = fliplr(
batch.heatmaps[i].arr_0to1)
if batch.segmentation_maps is not None:
batch.segmentation_maps[i].arr = fliplr(
batch.segmentation_maps[i].arr)
if batch.keypoints is not None:
kpsoi = batch.keypoints[i]
width = kpsoi.shape[1]
for kp in kpsoi.keypoints:
kp.x = width - float(kp.x)
if batch.bounding_boxes is not None:
bbsoi = batch.bounding_boxes[i]
width = bbsoi.shape[1]
for bb in bbsoi.bounding_boxes:
# after flip, x1 ends up right of x2
x1, x2 = bb.x1, bb.x2
bb.x1 = width - x2
bb.x2 = width - x1
if batch.polygons is not None:
psoi = batch.polygons[i]
width = psoi.shape[1]
for poly in psoi.polygons:
# TODO maybe reverse the order of points afterwards?
# the flip probably inverts them
poly.exterior[:, 0] = width - poly.exterior[:, 0]
if batch.line_strings is not None:
lsoi = batch.line_strings[i]
width = lsoi.shape[1]
for ls in lsoi.line_strings:
# TODO maybe reverse the order of points afterwards?
# the flip probably inverts them
ls.coords[:, 0] = width - ls.coords[:, 0]
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.p]
# TODO merge with Fliplr
class Flipud(meta.Augmenter):
"""Flip/mirror input images vertically.
.. note::
The default value for the probability is ``0.0``.
So, to flip *all* input images use ``Flipud(1.0)`` and *not* just
``Flipud()``.
**Supported dtypes**:
See :func:`~imgaug.augmenters.flip.flipud`.
Parameters
----------
p : number or imgaug.parameters.StochasticParameter, optional
Probability of each image to get flipped.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Flipud(0.5)
Flip ``50`` percent of all images vertically.
>>> aug = iaa.Flipud(1.0)
Flip all images vertically.
"""
def __init__(self, p=1,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Flipud, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.p = iap.handle_probability_param(p, "p")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
samples = self.p.draw_samples((batch.nb_rows,),
random_state=random_state)
for i, sample in enumerate(samples):
if sample >= 0.5:
if batch.images is not None:
# We currently do not use flip.flipud() here, because that
# saves a function call.
batch.images[i] = batch.images[i][::-1, ...]
if batch.heatmaps is not None:
batch.heatmaps[i].arr_0to1 = \
batch.heatmaps[i].arr_0to1[::-1, ...]
if batch.segmentation_maps is not None:
batch.segmentation_maps[i].arr = \
batch.segmentation_maps[i].arr[::-1, ...]
if batch.keypoints is not None:
kpsoi = batch.keypoints[i]
height = kpsoi.shape[0]
for kp in kpsoi.keypoints:
kp.y = height - float(kp.y)
if batch.bounding_boxes is not None:
bbsoi = batch.bounding_boxes[i]
height = bbsoi.shape[0]
for bb in bbsoi.bounding_boxes:
# after flip, y1 ends up right of y2
y1, y2 = bb.y1, bb.y2
bb.y1 = height - y2
bb.y2 = height - y1
if batch.polygons is not None:
psoi = batch.polygons[i]
height = psoi.shape[0]
for poly in psoi.polygons:
# TODO maybe reverse the order of points afterwards?
# the flip probably inverts them
poly.exterior[:, 1] = height - poly.exterior[:, 1]
if batch.line_strings is not None:
lsoi = batch.line_strings[i]
height = lsoi.shape[0]
for ls in lsoi.line_strings:
# TODO maybe reverse the order of points afterwards?
# the flip probably inverts them
ls.coords[:, 1] = height - ls.coords[:, 1]
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.p]
================================================
FILE: imgaug/augmenters/geometric.py
================================================
"""Augmenters that apply affine or similar transformations.
List of augmenters:
* :class:`Affine`
* :class:`ScaleX`
* :class:`ScaleY`
* :class:`TranslateX`
* :class:`TranslateY`
* :class:`Rotate`
* :class:`ShearX`
* :class:`ShearY`
* :class:`AffineCv2`
* :class:`PiecewiseAffine`
* :class:`PerspectiveTransform`
* :class:`ElasticTransformation`
* :class:`Rot90`
* :class:`WithPolarWarping`
* :class:`Jigsaw`
"""
from __future__ import print_function, division, absolute_import
import math
import functools
import itertools
import numpy as np
from scipy import ndimage
from skimage import transform as tf
import cv2
import six.moves as sm
import imgaug as ia
from imgaug.imgaug import _normalize_cv2_input_arr_
from imgaug.augmentables.polys import _ConcavePolygonRecoverer
from . import meta
from . import blur as blur_lib
from . import size as size_lib
from .. import parameters as iap
from .. import dtypes as iadt
from .. import random as iarandom
_WARP_AFF_VALID_DTYPES_CV2_ORDER_0 = iadt._convert_dtype_strs_to_types(
"uint8 uint16 int8 int16 int32 float16 float32 float64 bool"
) # Added in 0.5.0.
_WARP_AFF_VALID_DTYPES_CV2_ORDER_NOT_0 = iadt._convert_dtype_strs_to_types(
"uint8 uint16 int8 int16 float16 float32 float64 bool"
) # Added in 0.5.0.
# skimage | cv2
# 0 | cv2.INTER_NEAREST
# 1 | cv2.INTER_LINEAR
# 2 | -
# 3 | cv2.INTER_CUBIC
# 4 | -
_AFFINE_INTERPOLATION_ORDER_SKIMAGE_TO_CV2 = {
0: cv2.INTER_NEAREST,
1: cv2.INTER_LINEAR,
2: cv2.INTER_CUBIC,
3: cv2.INTER_CUBIC,
4: cv2.INTER_CUBIC
}
# constant, edge, symmetric, reflect, wrap
# skimage | cv2
# constant | cv2.BORDER_CONSTANT
# edge | cv2.BORDER_REPLICATE
# symmetric | cv2.BORDER_REFLECT
# reflect | cv2.BORDER_REFLECT_101
# wrap | cv2.BORDER_WRAP
_AFFINE_MODE_SKIMAGE_TO_CV2 = {
"constant": cv2.BORDER_CONSTANT,
"edge": cv2.BORDER_REPLICATE,
"symmetric": cv2.BORDER_REFLECT,
"reflect": cv2.BORDER_REFLECT_101,
"wrap": cv2.BORDER_WRAP
}
_PI = 3.141592653589793
_RAD_PER_DEGREE = _PI / 180
@iap._prefetchable
def _handle_order_arg(order, backend):
# Peformance in skimage for Affine:
# 1.0x order 0
# 1.5x order 1
# 3.0x order 3
# 30.0x order 4
# 60.0x order 5
# measurement based on 256x256x3 batches, difference is smaller
# on smaller images (seems to grow more like exponentially with image
# size)
if order == ia.ALL:
if backend in ["auto", "cv2"]:
return iap.Choice([0, 1, 3])
# dont use order=2 (bi-quadratic) because that is apparently
# currently not recommended (and throws a warning)
return iap.Choice([0, 1, 3, 4, 5])
if ia.is_single_integer(order):
assert 0 <= order <= 5, (
"Expected order's integer value to be in the interval [0, 5], "
"got %d." % (order,))
if backend == "cv2":
assert order in [0, 1, 3], (
"Backend \"cv2\" and order=%d was chosen, but cv2 backend "
"can only handle order 0, 1 or 3." % (order,))
return iap.Deterministic(order)
if isinstance(order, list):
assert all([ia.is_single_integer(val) for val in order]), (
"Expected order list to only contain integers, "
"got types %s." % (str([type(val) for val in order]),))
assert all([0 <= val <= 5 for val in order]), (
"Expected all of order's integer values to be in range "
"0 <= x <= 5, got %s." % (str(order),))
if backend == "cv2":
assert all([val in [0, 1, 3] for val in order]), (
"cv2 backend can only handle order 0, 1 or 3. Got order "
"list of %s." % (order,))
return iap.Choice(order)
if isinstance(order, iap.StochasticParameter):
return order
raise Exception(
"Expected order to be imgaug.ALL, int, list of int or "
"StochasticParameter, got %s." % (type(order),))
@iap._prefetchable
def _handle_cval_arg(cval):
if cval == ia.ALL:
# TODO change this so that it is dynamically created per image
# (or once per dtype)
return iap.Uniform(0, 255) # skimage transform expects float
return iap.handle_continuous_param(
cval,
"cval",
value_range=None,
tuple_to_uniform=True,
list_to_choice=True
)
# currently used for Affine and PiecewiseAffine
# TODO use iap.handle_categorical_string_param() here
@iap._prefetchable_str
def _handle_mode_arg(mode):
if mode == ia.ALL:
return iap.Choice(["constant", "edge", "symmetric", "reflect", "wrap"])
if ia.is_string(mode):
return iap.Deterministic(mode)
if isinstance(mode, list):
assert all([ia.is_string(val) for val in mode]), (
"Expected list of modes to only contain strings, got "
"types %s" % (", ".join([str(type(v)) for v in mode]))
)
return iap.Choice(mode)
if isinstance(mode, iap.StochasticParameter):
return mode
raise Exception(
"Expected mode to be imgaug.ALL, a string, a list of strings "
"or StochasticParameter, got %s." % (type(mode),)
)
def _warp_affine_arr(arr, matrix, order=1, mode="constant", cval=0,
output_shape=None, backend="auto"):
# no changes to zero-sized arrays
if arr.size == 0:
return arr
if ia.is_single_integer(cval) or ia.is_single_float(cval):
cval = [cval] * len(arr.shape[2])
min_value, _center_value, max_value = \
iadt.get_value_range_of_dtype(arr.dtype)
cv2_bad_order = order not in [0, 1, 3]
if order == 0:
cv2_bad_dtype = (arr.dtype
not in _WARP_AFF_VALID_DTYPES_CV2_ORDER_0)
else:
cv2_bad_dtype = (arr.dtype
not in _WARP_AFF_VALID_DTYPES_CV2_ORDER_NOT_0)
cv2_impossible = cv2_bad_order or cv2_bad_dtype
use_skimage = (
backend == "skimage"
or (backend == "auto" and cv2_impossible)
)
if use_skimage:
# cval contains 3 values as cv2 can handle 3, but
# skimage only 1
cval = cval[0]
# skimage does not clip automatically
cval = max(min(cval, max_value), min_value)
image_warped = _warp_affine_arr_skimage(
arr,
matrix,
cval=cval,
mode=mode,
order=order,
output_shape=output_shape
)
else:
assert not cv2_bad_dtype, (
not cv2_bad_dtype,
"cv2 backend in Affine got a dtype %s, which it "
"cannot handle. Try using a different dtype or set "
"order=0." % (
arr.dtype,))
cval_type = float if arr.dtype.kind == "f" else int
image_warped = _warp_affine_arr_cv2(
arr,
matrix,
cval=tuple([cval_type(v) for v in cval]),
mode=mode,
order=order,
output_shape=output_shape
)
return image_warped
def _warp_affine_arr_skimage(arr, matrix, cval, mode, order, output_shape):
iadt.gate_dtypes_strs(
{arr.dtype},
allowed="bool uint8 uint16 uint32 int8 int16 int32 "
"float16 float32 float64",
disallowed="uint64 int64 float128"
)
input_dtype = arr.dtype
# tf.warp() produces a deprecation warning for bool images with
# order!=0. We either need to convert them to float or use NN
# interpolation.
if input_dtype == iadt._BOOL_DTYPE and order != 0:
arr = arr.astype(np.float32)
image_warped = tf.warp(
arr,
np.linalg.inv(matrix),
order=order,
mode=mode,
cval=cval,
preserve_range=True,
output_shape=output_shape,
)
# tf.warp changes all dtypes to float64, including uint8
if input_dtype.kind == "b":
image_warped = image_warped > 0.5
else:
image_warped = iadt.restore_dtypes_(image_warped, input_dtype)
return image_warped
def _warp_affine_arr_cv2(arr, matrix, cval, mode, order, output_shape):
iadt.gate_dtypes_strs(
{arr.dtype},
allowed="bool uint8 uint16 int8 int16 int32 float16 float32 float64",
disallowed="uint32 uint64 int64 float128"
)
if order != 0:
assert arr.dtype != iadt._INT32_DTYPE, (
"Affine only supports cv2-based transformations of int32 "
"arrays when using order=0, but order was set to %d." % (
order,))
input_dtype = arr.dtype
if input_dtype in {iadt._BOOL_DTYPE, iadt._FLOAT16_DTYPE}:
arr = arr.astype(np.float32)
elif input_dtype == iadt._INT8_DTYPE and order != 0:
arr = arr.astype(np.int16)
dsize = (
int(np.round(output_shape[1])),
int(np.round(output_shape[0]))
)
# map key X from skimage to cv2 or fall back to key X
mode = _AFFINE_MODE_SKIMAGE_TO_CV2.get(mode, mode)
order = _AFFINE_INTERPOLATION_ORDER_SKIMAGE_TO_CV2.get(order, order)
# TODO this uses always a tuple of 3 values for cval, even if
# #chans != 3, works with 1d but what in other cases?
nb_channels = arr.shape[-1]
if nb_channels <= 3:
# TODO this block can also be when order==0 for any nb_channels,
# but was deactivated for now, because cval would always
# contain 3 values and not nb_channels values
image_warped = cv2.warpAffine(
_normalize_cv2_input_arr_(arr),
matrix[0:2, :],
dsize=dsize,
flags=order,
borderMode=mode,
borderValue=cval
)
# cv2 warp drops last axis if shape is (H, W, 1)
if image_warped.ndim == 2:
image_warped = image_warped[..., np.newaxis]
else:
# warp each channel on its own, re-add channel axis, then stack
# the result from a list of [H, W, 1] to (H, W, C).
image_warped = [
cv2.warpAffine(
_normalize_cv2_input_arr_(arr[:, :, c]),
matrix[0:2, :],
dsize=dsize,
flags=order,
borderMode=mode,
borderValue=tuple([cval[0]])
)
for c in sm.xrange(nb_channels)
]
image_warped = np.stack(image_warped, axis=-1)
if input_dtype.kind == "b":
image_warped = image_warped > 0.5
elif input_dtype in {iadt._INT8_DTYPE, iadt._FLOAT16_DTYPE}:
image_warped = iadt.restore_dtypes_(image_warped, input_dtype)
return image_warped
# Added in 0.5.0.
def _warp_affine_coords(coords, matrix):
if len(coords) == 0:
return coords
assert coords.shape[1] == 2
assert matrix.shape == (3, 3)
# this is the same as in scikit-image, _geometric.py -> _apply_mat()
x, y = np.transpose(coords)
src = np.vstack((x, y, np.ones_like(x)))
dst = np.dot(src.T, matrix.T)
# below, we will divide by the last dimension of the homogeneous
# coordinate matrix. In order to avoid division by zero,
# we replace exact zeros in this column with a very small number.
dst[dst[:, 2] == 0, 2] = np.finfo(float).eps
# rescale to homogeneous coordinates
dst[:, :2] /= dst[:, 2:3]
return dst[:, :2]
def _compute_affine_warp_output_shape(matrix, input_shape):
height, width = input_shape[:2]
if height == 0 or width == 0:
return matrix, input_shape
# determine shape of output image
corners = np.array([
[0, 0],
[0, height - 1],
[width - 1, height - 1],
[width - 1, 0]
])
corners = _warp_affine_coords(corners, matrix)
minc = corners[:, 0].min()
minr = corners[:, 1].min()
maxc = corners[:, 0].max()
maxr = corners[:, 1].max()
out_height = maxr - minr + 1
out_width = maxc - minc + 1
if len(input_shape) == 3:
output_shape = np.ceil((out_height, out_width, input_shape[2]))
else:
output_shape = np.ceil((out_height, out_width))
output_shape = tuple([int(v) for v in output_shape.tolist()])
# fit output image in new shape
translation = (-minc, -minr)
matrix = _AffineMatrixGenerator(matrix).translate(
x_px=translation[0], y_px=translation[1]
).matrix
return matrix, output_shape
# TODO allow -1 destinations
def apply_jigsaw(arr, destinations):
"""Move cells of an image similar to a jigsaw puzzle.
This function will split the image into ``rows x cols`` cells and
move each cell to the target index given in `destinations`.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; fully tested
* ``uint32``: yes; fully tested
* ``uint64``: yes; fully tested
* ``int8``: yes; fully tested
* ``int16``: yes; fully tested
* ``int32``: yes; fully tested
* ``int64``: yes; fully tested
* ``float16``: yes; fully tested
* ``float32``: yes; fully tested
* ``float64``: yes; fully tested
* ``float128``: yes; fully tested
* ``bool``: yes; fully tested
Parameters
----------
arr : ndarray
Array with at least two dimensions denoting height and width.
destinations : ndarray
2-dimensional array containing for each cell the id of the destination
cell. The order is expected to a flattened c-order, i.e. row by row.
The height of the image must be evenly divisible by the number of
rows in this array. Analogous for the width and columns.
Returns
-------
ndarray
Modified image with cells moved according to `destinations`.
"""
# pylint complains about unravel_index() here
# pylint: disable=unbalanced-tuple-unpacking
nb_rows, nb_cols = destinations.shape[0:2]
assert arr.ndim >= 2, (
"Expected array with at least two dimensions, but got %d with "
"shape %s." % (arr.ndim, arr.shape))
assert (arr.shape[0] % nb_rows) == 0, (
"Expected image height to by divisible by number of rows, but got "
"height %d and %d rows. Use cropping or padding to modify the image "
"height or change the number of rows." % (arr.shape[0], nb_rows)
)
assert (arr.shape[1] % nb_cols) == 0, (
"Expected image width to by divisible by number of columns, but got "
"width %d and %d columns. Use cropping or padding to modify the image "
"width or change the number of columns." % (arr.shape[1], nb_cols)
)
cell_height = arr.shape[0] // nb_rows
cell_width = arr.shape[1] // nb_cols
dest_rows, dest_cols = np.unravel_index(
destinations.flatten(), (nb_rows, nb_cols))
result = np.zeros_like(arr)
i = 0
for source_row in np.arange(nb_rows):
for source_col in np.arange(nb_cols):
# TODO vectorize coords computation
dest_row, dest_col = dest_rows[i], dest_cols[i]
source_y1 = source_row * cell_height
source_y2 = source_y1 + cell_height
source_x1 = source_col * cell_width
source_x2 = source_x1 + cell_width
dest_y1 = dest_row * cell_height
dest_y2 = dest_y1 + cell_height
dest_x1 = dest_col * cell_width
dest_x2 = dest_x1 + cell_width
source = arr[source_y1:source_y2, source_x1:source_x2]
result[dest_y1:dest_y2, dest_x1:dest_x2] = source
i += 1
return result
def apply_jigsaw_to_coords(coords, destinations, image_shape):
"""Move coordinates on an image similar to a jigsaw puzzle.
This is the same as :func:`apply_jigsaw`, but moves coordinates within
the cells.
Added in 0.4.0.
Parameters
----------
coords : ndarray
``(N, 2)`` array denoting xy-coordinates.
destinations : ndarray
See :func:`apply_jigsaw`.
image_shape : tuple of int
``(height, width, ...)`` shape of the image on which the
coordinates are placed. Only height and width are required.
Returns
-------
ndarray
Moved coordinates.
"""
# pylint complains about unravel_index() here
# pylint: disable=unbalanced-tuple-unpacking
nb_rows, nb_cols = destinations.shape[0:2]
height, width = image_shape[0:2]
cell_height = height // nb_rows
cell_width = width // nb_cols
dest_rows, dest_cols = np.unravel_index(
destinations.flatten(), (nb_rows, nb_cols))
result = np.copy(coords)
# TODO vectorize this loop
for i, (x, y) in enumerate(coords):
ooi_x = (x < 0 or x >= width)
ooi_y = (y < 0 or y >= height)
if ooi_x or ooi_y:
continue
source_row = int(y // cell_height)
source_col = int(x // cell_width)
source_cell_idx = (source_row * nb_cols) + source_col
dest_row = dest_rows[source_cell_idx]
dest_col = dest_cols[source_cell_idx]
source_y1 = source_row * cell_height
source_x1 = source_col * cell_width
dest_y1 = dest_row * cell_height
dest_x1 = dest_col * cell_width
result[i, 0] = dest_x1 + (x - source_x1)
result[i, 1] = dest_y1 + (y - source_y1)
return result
def generate_jigsaw_destinations(nb_rows, nb_cols, max_steps, seed,
connectivity=4):
"""Generate a destination pattern for :func:`apply_jigsaw`.
Added in 0.4.0.
Parameters
----------
nb_rows : int
Number of rows to split the image into.
nb_cols : int
Number of columns to split the image into.
max_steps : int
Maximum number of cells that each cell may be moved.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
Seed value or alternatively RNG to use.
If ``None`` the global RNG will be used.
connectivity : int, optional
Whether a diagonal move of a cell counts as one step
(``connectivity=8``) or two steps (``connectivity=4``).
Returns
-------
ndarray
2-dimensional array containing for each cell the id of the target
cell.
"""
assert connectivity in (4, 8), (
"Expected connectivity of 4 or 8, got %d." % (connectivity,))
random_state = iarandom.RNG.create_if_not_rng_(seed)
steps = random_state.integers(0, max_steps, size=(nb_rows, nb_cols),
endpoint=True)
directions = random_state.integers(0, connectivity,
size=(nb_rows, nb_cols, max_steps),
endpoint=False)
destinations = np.arange(nb_rows*nb_cols).reshape((nb_rows, nb_cols))
for step in np.arange(max_steps):
directions_step = directions[:, :, step]
for y in np.arange(nb_rows):
for x in np.arange(nb_cols):
if steps[y, x] > 0:
y_target, x_target = {
0: (y-1, x+0),
1: (y+0, x+1),
2: (y+1, x+0),
3: (y+0, x-1),
4: (y-1, x-1),
5: (y-1, x+1),
6: (y+1, x+1),
7: (y+1, x-1)
}[directions_step[y, x]]
y_target = max(min(y_target, nb_rows-1), 0)
x_target = max(min(x_target, nb_cols-1), 0)
target_steps = steps[y_target, x_target]
if (y, x) != (y_target, x_target) and target_steps >= 1:
source_dest = destinations[y, x]
target_dest = destinations[y_target, x_target]
destinations[y, x] = target_dest
destinations[y_target, x_target] = source_dest
steps[y, x] -= 1
steps[y_target, x_target] -= 1
return destinations
# Added in 0.5.0.
class _AffineMatrixGenerator(object):
# Added in 0.5.0.
def __init__(self, matrix=None):
if matrix is None:
matrix = np.eye(3, dtype=np.float32)
self.matrix = matrix
# Added in 0.5.0.
def centerize(self, image_shape):
height, width = image_shape[0:2]
self.translate(-width/2, -height/2)
return self
# Added in 0.5.0.
def invert_centerize(self, image_shape):
height, width = image_shape[0:2]
self.translate(width/2, height/2)
return self
# Added in 0.5.0.
def translate(self, x_px, y_px):
if x_px < 1e-4 or x_px > 1e-4 or y_px < 1e-4 or x_px > 1e-4:
matrix = np.array([
[1, 0, x_px],
[0, 1, y_px],
[0, 0, 1]
], dtype=np.float32)
self._mul(matrix)
return self
# Added in 0.5.0.
def scale(self, x_frac, y_frac):
if (x_frac < 1.0-1e-4 or x_frac > 1.0+1e-4
or y_frac < 1.0-1e-4 or y_frac > 1.0+1e-4):
matrix = np.array([
[x_frac, 0, 0],
[0, y_frac, 0],
[0, 0, 1]
], dtype=np.float32)
self._mul(matrix)
return self
# Added in 0.5.0.
def rotate(self, rad):
if rad < 1e-4 or rad > 1e-4:
rad = -rad
matrix = np.array([
[np.cos(rad), np.sin(rad), 0],
[-np.sin(rad), np.cos(rad), 0],
[0, 0, 1]
], dtype=np.float32)
self._mul(matrix)
return self
# Added in 0.5.0.
def shear(self, x_rad, y_rad):
if x_rad < 1e-4 or x_rad > 1e-4 or y_rad < 1e-4 or y_rad > 1e-4:
matrix = np.array([
[1, np.tanh(-x_rad), 0],
[np.tanh(y_rad), 1, 0],
[0, 0, 1]
], dtype=np.float32)
self._mul(matrix)
return self
# Added in 0.5.0.
def _mul(self, matrix):
self.matrix = np.matmul(matrix, self.matrix)
class _AffineSamplingResult(object):
def __init__(self, scale=None, translate=None, translate_mode="px",
rotate=None, shear=None, cval=None, mode=None, order=None):
self.scale = scale
self.translate = translate
self.translate_mode = translate_mode
self.rotate = rotate
self.shear = shear
self.cval = cval
self.mode = mode
self.order = order
# Added in 0.4.0.
def get_affine_parameters(self, idx, arr_shape, image_shape):
scale_y = self.scale[1][idx] # TODO 1 and 0 should be inverted here
scale_x = self.scale[0][idx]
translate_y = self.translate[1][idx] # TODO same as above
translate_x = self.translate[0][idx]
assert self.translate_mode in ["px", "percent"], (
"Expected 'px' or 'percent', got '%s'." % (self.translate_mode,)
)
if self.translate_mode == "percent":
translate_y_px = translate_y * arr_shape[0]
translate_x_px = translate_x * arr_shape[1]
else:
translate_y_px = (translate_y / image_shape[0]) * arr_shape[0]
translate_x_px = (translate_x / image_shape[1]) * arr_shape[1]
rotate_deg = self.rotate[idx]
shear_x_deg = self.shear[0][idx]
shear_y_deg = self.shear[1][idx]
rotate_rad = rotate_deg * _RAD_PER_DEGREE
shear_x_rad = shear_x_deg * _RAD_PER_DEGREE
shear_y_rad = shear_y_deg * _RAD_PER_DEGREE
# we add the _deg versions of rotate and shear here for PILAffine,
# Affine itself only uses *_rad
return {
"scale_y": scale_y,
"scale_x": scale_x,
"translate_y_px": translate_y_px,
"translate_x_px": translate_x_px,
"rotate_rad": rotate_rad,
"shear_y_rad": shear_y_rad,
"shear_x_rad": shear_x_rad,
"rotate_deg": rotate_deg,
"shear_y_deg": shear_y_deg,
"shear_x_deg": shear_x_deg
}
# for images we use additional shifts of (0.5, 0.5) as otherwise
# we get an ugly black border for 90deg rotations
def to_matrix(
self,
idx,
arr_shape,
image_shape,
fit_output,
shift_add=(0.5, 0.5)
):
if 0 in image_shape:
return np.eye(3, dtype=np.float32), arr_shape
params = self.get_affine_parameters(
idx, arr_shape=arr_shape, image_shape=image_shape
)
matrix_gen = _AffineMatrixGenerator()
matrix_gen.centerize(arr_shape)
matrix_gen.translate(x_px=shift_add[1], y_px=shift_add[0])
matrix_gen.rotate(params["rotate_rad"])
matrix_gen.scale(x_frac=params["scale_x"], y_frac=params["scale_y"])
matrix_gen.shear(x_rad=params["shear_x_rad"],
y_rad=params["shear_y_rad"])
matrix_gen.translate(x_px=params["translate_x_px"],
y_px=params["translate_y_px"])
matrix_gen.translate(x_px=-shift_add[1], y_px=-shift_add[0])
matrix_gen.invert_centerize(arr_shape)
matrix = matrix_gen.matrix
if fit_output:
matrix, arr_shape = _compute_affine_warp_output_shape(
matrix, arr_shape
)
return matrix, arr_shape
# Added in 0.4.0.
def to_matrix_cba(self, idx, arr_shape, fit_output, shift_add=(0.0, 0.0)):
return self.to_matrix(idx, arr_shape, arr_shape, fit_output, shift_add)
# Added in 0.4.0.
def copy(self):
return _AffineSamplingResult(
scale=self.scale,
translate=self.translate,
translate_mode=self.translate_mode,
rotate=self.rotate,
shear=self.shear,
cval=self.cval,
mode=self.mode,
order=self.order
)
def _is_identity_matrix(matrix, eps=1e-4):
identity = np.eye(3, dtype=np.float32)
# about twice as fast as np.allclose()
return np.average(np.abs(matrix - identity)) <= eps
class Affine(meta.Augmenter):
"""
Augmenter to apply affine transformations to images.
This is mostly a wrapper around the corresponding classes and functions
in OpenCV and skimage.
Affine transformations involve:
- Translation ("move" image on the x-/y-axis)
- Rotation
- Scaling ("zoom" in/out)
- Shear (move one side of the image, turning a square into a trapezoid)
All such transformations can create "new" pixels in the image without a
defined content, e.g. if the image is translated to the left, pixels
are created on the right.
A method has to be defined to deal with these pixel values. The
parameters `cval` and `mode` of this class deal with this.
Some transformations involve interpolations between several pixels
of the input image to generate output pixel values. The parameter `order`
deals with the method of interpolation used for this.
.. note::
While this augmenter supports segmentation maps and heatmaps that
have a different size than the corresponding image, it is strongly
recommended to use the same aspect ratios. E.g. for an image of
shape ``(200, 100, 3)``, good segmap/heatmap array shapes also follow
a ``2:1`` ratio and ideally are ``(200, 100, C)``, ``(100, 50, C)`` or
``(50, 25, C)``. Otherwise, transformations involving rotations or
shearing will produce unaligned outputs.
For performance reasons, there is no explicit validation of whether
the aspect ratios are similar.
**Supported dtypes**:
if (backend="skimage", order in [0, 1]):
* ``uint8``: yes; tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested (1)
* ``uint64``: no (2)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested (1)
* ``int64``: no (2)
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (2)
* ``bool``: yes; tested
- (1) scikit-image converts internally to float64, which might
affect the accuracy of large integers. In tests this seemed
to not be an issue.
- (2) results too inaccurate
if (backend="skimage", order in [3, 4]):
* ``uint8``: yes; tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested (1)
* ``uint64``: no (2)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested (1)
* ``int64``: no (2)
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: limited; tested (3)
* ``float128``: no (2)
* ``bool``: yes; tested
- (1) scikit-image converts internally to float64, which might
affect the accuracy of large integers. In tests this seemed
to not be an issue.
- (2) results too inaccurate
- (3) ``NaN`` around minimum and maximum of float64 value range
if (backend="skimage", order=5]):
* ``uint8``: yes; tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested (1)
* ``uint64``: no (2)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested (1)
* ``int64``: no (2)
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: limited; not tested (3)
* ``float128``: no (2)
* ``bool``: yes; tested
- (1) scikit-image converts internally to ``float64``, which
might affect the accuracy of large integers. In tests
this seemed to not be an issue.
- (2) results too inaccurate
- (3) ``NaN`` around minimum and maximum of float64 value range
if (backend="cv2", order=0):
* ``uint8``: yes; tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: no (2)
* ``float16``: yes; tested (3)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (1)
* ``bool``: yes; tested (3)
- (1) rejected by cv2
- (2) changed to ``int32`` by cv2
- (3) mapped internally to ``float32``
if (backend="cv2", order=1):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested (3)
* ``int16``: yes; tested
* ``int32``: no (2)
* ``int64``: no (2)
* ``float16``: yes; tested (4)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (1)
* ``bool``: yes; tested (4)
- (1) rejected by cv2
- (2) causes cv2 error: ``cv2.error: OpenCV(3.4.4)
(...)imgwarp.cpp:1805: error:
(-215:Assertion failed) ifunc != 0 in function 'remap'``
- (3) mapped internally to ``int16``
- (4) mapped internally to ``float32``
if (backend="cv2", order=3):
* ``uint8``: yes; tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested (3)
* ``int16``: yes; tested
* ``int32``: no (2)
* ``int64``: no (2)
* ``float16``: yes; tested (4)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (1)
* ``bool``: yes; tested (4)
- (1) rejected by cv2
- (2) causes cv2 error: ``cv2.error: OpenCV(3.4.4)
(...)imgwarp.cpp:1805: error:
(-215:Assertion failed) ifunc != 0 in function 'remap'``
- (3) mapped internally to ``int16``
- (4) mapped internally to ``float32``
Parameters
----------
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
Scaling factor to use, where ``1.0`` denotes "no change" and
``0.5`` is zoomed out to ``50`` percent of the original size.
* If a single number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``. That value will be
used identically for both x- and y-axis.
* If a list, then a random value will be sampled from that list
per image (again, used for both x- and y-axis).
* If a ``StochasticParameter``, then from that parameter a value
will be sampled per image (again, used for both x- and y-axis).
* If a dictionary, then it is expected to have the keys ``x``
and/or ``y``. Each of these keys can have the same values as
described above. Using a dictionary allows to set different
values for the two axis and sampling will then happen
*independently* per axis, resulting in samples that differ
between the axes.
translate_percent : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
Translation as a fraction of the image height/width (x-translation,
y-translation), where ``0`` denotes "no change" and ``0.5`` denotes
"half of the axis size".
* If ``None`` then equivalent to ``0.0`` unless `translate_px` has
a value other than ``None``.
* If a single number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``. That sampled fraction
value will be used identically for both x- and y-axis.
* If a list, then a random value will be sampled from that list
per image (again, used for both x- and y-axis).
* If a ``StochasticParameter``, then from that parameter a value
will be sampled per image (again, used for both x- and y-axis).
* If a dictionary, then it is expected to have the keys ``x``
and/or ``y``. Each of these keys can have the same values as
described above. Using a dictionary allows to set different
values for the two axis and sampling will then happen
*independently* per axis, resulting in samples that differ
between the axes.
translate_px : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
Translation in pixels.
* If ``None`` then equivalent to ``0`` unless `translate_percent`
has a value other than ``None``.
* If a single int, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the discrete interval ``[a..b]``. That number
will be used identically for both x- and y-axis.
* If a list, then a random value will be sampled from that list
per image (again, used for both x- and y-axis).
* If a ``StochasticParameter``, then from that parameter a value
will be sampled per image (again, used for both x- and y-axis).
* If a dictionary, then it is expected to have the keys ``x``
and/or ``y``. Each of these keys can have the same values as
described above. Using a dictionary allows to set different
values for the two axis and sampling will then happen
*independently* per axis, resulting in samples that differ
between the axes.
rotate : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Rotation in degrees (**NOT** radians), i.e. expected value range is
around ``[-360, 360]``. Rotation happens around the *center* of the
image, not the top left corner as in some other frameworks.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]`` and used as the rotation
value.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then this parameter will be used to
sample the rotation value per image.
shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
Shear in degrees (**NOT** radians), i.e. expected value range is
around ``[-360, 360]``, with reasonable values being in the range
of ``[-45, 45]``.
* If a number, then that value will be used for all images as
the shear on the x-axis (no shear on the y-axis will be done).
* If a tuple ``(a, b)``, then two value will be uniformly sampled
per image from the interval ``[a, b]`` and be used as the
x- and y-shear value.
* If a list, then two random values will be sampled from that list
per image, denoting x- and y-shear.
* If a ``StochasticParameter``, then this parameter will be used
to sample the x- and y-shear values per image.
* If a dictionary, then similar to `translate_percent`, i.e. one
``x`` key and/or one ``y`` key are expected, denoting the
shearing on the x- and y-axis respectively. The allowed datatypes
are again ``number``, ``tuple`` ``(a, b)``, ``list`` or
``StochasticParameter``.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
Interpolation order to use. Same meaning as in ``skimage``:
* ``0``: ``Nearest-neighbor``
* ``1``: ``Bi-linear`` (default)
* ``2``: ``Bi-quadratic`` (not recommended by skimage)
* ``3``: ``Bi-cubic``
* ``4``: ``Bi-quartic``
* ``5``: ``Bi-quintic``
Method ``0`` and ``1`` are fast, ``3`` is a bit slower, ``4`` and
``5`` are very slow. If the backend is ``cv2``, the mapping to
OpenCV's interpolation modes is as follows:
* ``0`` -> ``cv2.INTER_NEAREST``
* ``1`` -> ``cv2.INTER_LINEAR``
* ``2`` -> ``cv2.INTER_CUBIC``
* ``3`` -> ``cv2.INTER_CUBIC``
* ``4`` -> ``cv2.INTER_CUBIC``
As datatypes this parameter accepts:
* If a single ``int``, then that order will be used for all images.
* If a list, then a random value will be sampled from that list
per image.
* If ``imgaug.ALL``, then equivalant to list ``[0, 1, 3, 4, 5]``
in case of ``backend=skimage`` and otherwise ``[0, 1, 3]``.
* If ``StochasticParameter``, then that parameter is queried per
image to sample the order value to use.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
The constant value to use when filling in newly created pixels.
(E.g. translating by 1px to the right will create a new 1px-wide
column of pixels on the left of the image). The value is only used
when `mode=constant`. The expected value range is ``[0, 255]`` for
``uint8`` images. It may be a float value.
* If this is a single number, then that value will be used
(e.g. 0 results in black pixels).
* If a tuple ``(a, b)``, then three values (for three image
channels) will be uniformly sampled per image from the
interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If ``imgaug.ALL`` then equivalent to tuple ``(0, 255)`.
* If a ``StochasticParameter``, a new value will be sampled from
the parameter per image.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
Method to use when filling in newly created pixels.
Same meaning as in ``skimage`` (and :func:`numpy.pad`):
* ``constant``: Pads with a constant value
* ``edge``: Pads with the edge values of array
* ``symmetric``: Pads with the reflection of the vector mirrored
along the edge of the array.
* ``reflect``: Pads with the reflection of the vector mirrored on
the first and last values of the vector along each axis.
* ``wrap``: Pads with the wrap of the vector along the axis.
The first values are used to pad the end and the end values
are used to pad the beginning.
If ``cv2`` is chosen as the backend the mapping is as follows:
* ``constant`` -> ``cv2.BORDER_CONSTANT``
* ``edge`` -> ``cv2.BORDER_REPLICATE``
* ``symmetric`` -> ``cv2.BORDER_REFLECT``
* ``reflect`` -> ``cv2.BORDER_REFLECT_101``
* ``wrap`` -> ``cv2.BORDER_WRAP``
The datatype of the parameter may be:
* If a single string, then that mode will be used for all images.
* If a list of strings, then a random mode will be picked
from that list per image.
* If ``imgaug.ALL``, then a random mode from all possible modes
will be picked.
* If ``StochasticParameter``, then the mode will be sampled from
that parameter per image, i.e. it must return only the above
mentioned strings.
fit_output : bool, optional
Whether to modify the affine transformation so that the whole output
image is always contained in the image plane (``True``) or accept
parts of the image being outside the image plane (``False``).
This can be thought of as first applying the affine transformation
and then applying a second transformation to "zoom in" on the new
image so that it fits the image plane,
This is useful to avoid corners of the image being outside of the image
plane after applying rotations. It will however negate translation
and scaling.
Note also that activating this may lead to image sizes differing from
the input image sizes. To avoid this, wrap ``Affine`` in
:class:`~imgaug.augmenters.size.KeepSizeByResize`,
e.g. ``KeepSizeByResize(Affine(...))``.
backend : str, optional
Framework to use as a backend. Valid values are ``auto``, ``skimage``
(scikit-image's warp) and ``cv2`` (OpenCV's warp).
If ``auto`` is used, the augmenter will automatically try
to use ``cv2`` whenever possible (order must be in ``[0, 1, 3]``). It
will silently fall back to skimage if order/dtype is not supported by
cv2. cv2 is generally faster than skimage. It also supports RGB cvals,
while skimage will resort to intensity cvals (i.e. 3x the same value
as RGB). If ``cv2`` is chosen and order is ``2`` or ``4``, it will
automatically fall back to order ``3``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Affine(scale=2.0)
Zoom in on all images by a factor of ``2``.
>>> aug = iaa.Affine(translate_px=16)
Translate all images on the x- and y-axis by 16 pixels (towards the
bottom right) and fill up any new pixels with zero (black values).
>>> aug = iaa.Affine(translate_percent=0.1)
Translate all images on the x- and y-axis by ``10`` percent of their
width/height (towards the bottom right). The pixel values are computed
per axis based on that axis' size. Fill up any new pixels with zero
(black values).
>>> aug = iaa.Affine(rotate=35)
Rotate all images by ``35`` *degrees*. Fill up any new pixels with zero
(black values).
>>> aug = iaa.Affine(shear=15)
Shear all images by ``15`` *degrees*. Fill up any new pixels with zero
(black values).
>>> aug = iaa.Affine(translate_px=(-16, 16))
Translate all images on the x- and y-axis by a random value
between ``-16`` and ``16`` pixels (to the bottom right) and fill up any new
pixels with zero (black values). The translation value is sampled once
per image and is the same for both axis.
>>> aug = iaa.Affine(translate_px={"x": (-16, 16), "y": (-4, 4)})
Translate all images on the x-axis by a random value
between ``-16`` and ``16`` pixels (to the right) and on the y-axis by a
random value between ``-4`` and ``4`` pixels to the bottom. The sampling
happens independently per axis, so even if both intervals were identical,
the sampled axis-wise values would likely be different.
This also fills up any new pixels with zero (black values).
>>> aug = iaa.Affine(scale=2.0, order=[0, 1])
Same as in the above `scale` example, but uses (randomly) either
nearest neighbour interpolation or linear interpolation. If `order` is
not specified, ``order=1`` would be used by default.
>>> aug = iaa.Affine(translate_px=16, cval=(0, 255))
Same as in the `translate_px` example above, but newly created pixels
are now filled with a random color (sampled once per image and the
same for all newly created pixels within that image).
>>> aug = iaa.Affine(translate_px=16, mode=["constant", "edge"])
Similar to the previous example, but the newly created pixels are
filled with black pixels in half of all images (mode ``constant`` with
default `cval` being ``0``) and in the other half of all images using
``edge`` mode, which repeats the color of the spatially closest pixel
of the corresponding image edge.
>>> aug = iaa.Affine(shear={"y": (-45, 45)})
Shear images only on the y-axis. Set `shear` to ``shear=(-45, 45)`` to
shear randomly on both axes, using for each image the same sample for
both the x- and y-axis. Use ``shear={"x": (-45, 45), "y": (-45, 45)}``
to get independent samples per axis.
"""
def __init__(self, scale=None, translate_percent=None, translate_px=None,
rotate=None, shear=None, order=1, cval=0, mode="constant",
fit_output=False, backend="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Affine, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
params = [scale, translate_percent, translate_px, rotate, shear]
if all([p is None for p in params]):
scale = {"x": (0.9, 1.1), "y": (0.9, 1.1)}
translate_percent = {"x": (-0.1, 0.1), "y": (-0.1, 0.1)}
rotate = (-15, 15)
shear = {"x": (-10, 10), "y": (-10, 10)}
else:
scale = scale if scale is not None else 1.0
rotate = rotate if rotate is not None else 0.0
shear = shear if shear is not None else 0.0
assert backend in ["auto", "skimage", "cv2"], (
"Expected 'backend' to be \"auto\", \"skimage\" or \"cv2\", "
"got %s." % (backend,))
self.backend = backend
self.order = _handle_order_arg(order, backend)
self.cval = _handle_cval_arg(cval)
self.mode = _handle_mode_arg(mode)
self.scale = self._handle_scale_arg(scale)
self.translate = self._handle_translate_arg(
translate_px, translate_percent)
self.rotate = iap.handle_continuous_param(
rotate, "rotate", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
self.shear, self._shear_param_type = self._handle_shear_arg(shear)
self.fit_output = fit_output
# Special order, mode and cval parameters for heatmaps and
# segmentation maps. These may either be None or a fixed value.
# Stochastic parameters are currently *not* supported.
# If set to None, the same values as for images will be used.
# That is really not recommended for the cval parameter.
#
# Segmentation map augmentation by default always pads with a
# constant value of 0 (background class id), and always uses nearest
# neighbour interpolation. While other pad modes and BG class ids
# could be used, the interpolation mode has to be NN as any other
# mode would lead to averaging class ids, which makes no sense to do.
self._order_heatmaps = 3
self._order_segmentation_maps = 0
self._mode_heatmaps = "constant"
self._mode_segmentation_maps = "constant"
self._cval_heatmaps = 0
self._cval_segmentation_maps = 0
@classmethod
def _handle_scale_arg(cls, scale):
if isinstance(scale, dict):
assert "x" in scale or "y" in scale, (
"Expected scale dictionary to contain at least key \"x\" or "
"key \"y\". Found neither of them.")
x = scale.get("x", 1.0)
y = scale.get("y", 1.0)
return (
iap.handle_continuous_param(
x, "scale['x']", value_range=(0+1e-4, None),
tuple_to_uniform=True, list_to_choice=True),
iap.handle_continuous_param(
y, "scale['y']", value_range=(0+1e-4, None),
tuple_to_uniform=True, list_to_choice=True)
)
return iap.handle_continuous_param(
scale, "scale", value_range=(0+1e-4, None),
tuple_to_uniform=True, list_to_choice=True)
@classmethod
def _handle_translate_arg(cls, translate_px, translate_percent):
# pylint: disable=no-else-return
if translate_percent is None and translate_px is None:
translate_px = 0
assert translate_percent is None or translate_px is None, (
"Expected either translate_percent or translate_px to be "
"provided, but neither of them was.")
if translate_percent is not None:
# translate by percent
if isinstance(translate_percent, dict):
assert "x" in translate_percent or "y" in translate_percent, (
"Expected translate_percent dictionary to contain at "
"least key \"x\" or key \"y\". Found neither of them.")
x = translate_percent.get("x", 0)
y = translate_percent.get("y", 0)
return (
iap.handle_continuous_param(
x, "translate_percent['x']", value_range=None,
tuple_to_uniform=True, list_to_choice=True),
iap.handle_continuous_param(
y, "translate_percent['y']", value_range=None,
tuple_to_uniform=True, list_to_choice=True),
"percent"
)
return (
iap.handle_continuous_param(
translate_percent, "translate_percent",
value_range=None, tuple_to_uniform=True,
list_to_choice=True),
None,
"percent"
)
else:
# translate by pixels
if isinstance(translate_px, dict):
assert "x" in translate_px or "y" in translate_px, (
"Expected translate_px dictionary to contain at "
"least key \"x\" or key \"y\". Found neither of them.")
x = translate_px.get("x", 0)
y = translate_px.get("y", 0)
return (
iap.handle_discrete_param(
x, "translate_px['x']", value_range=None,
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False),
iap.handle_discrete_param(
y, "translate_px['y']", value_range=None,
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False),
"px"
)
return (
iap.handle_discrete_param(
translate_px, "translate_px", value_range=None,
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False),
None,
"px"
)
# Added in 0.4.0.
@classmethod
def _handle_shear_arg(cls, shear):
# pylint: disable=no-else-return
if isinstance(shear, dict):
assert "x" in shear or "y" in shear, (
"Expected shear dictionary to contain at "
"least key \"x\" or key \"y\". Found neither of them.")
x = shear.get("x", 0)
y = shear.get("y", 0)
return (
iap.handle_continuous_param(
x, "shear['x']", value_range=None,
tuple_to_uniform=True, list_to_choice=True),
iap.handle_continuous_param(
y, "shear['y']", value_range=None,
tuple_to_uniform=True, list_to_choice=True)
), "dict"
else:
param_type = "other"
if ia.is_single_number(shear):
param_type = "single-number"
return iap.handle_continuous_param(
shear, "shear", value_range=None, tuple_to_uniform=True,
list_to_choice=True
), param_type
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
samples = self._draw_samples(batch.nb_rows, random_state)
if batch.images is not None:
batch.images = self._augment_images_by_samples(batch.images,
samples)
if batch.heatmaps is not None:
batch.heatmaps = self._augment_maps_by_samples(
batch.heatmaps, samples, "arr_0to1", self._cval_heatmaps,
self._mode_heatmaps, self._order_heatmaps, "float32")
if batch.segmentation_maps is not None:
batch.segmentation_maps = self._augment_maps_by_samples(
batch.segmentation_maps, samples, "arr",
self._cval_segmentation_maps, self._mode_segmentation_maps,
self._order_segmentation_maps, "int32")
for augm_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
for i, cbaoi in enumerate(augm_value):
matrix, output_shape = samples.to_matrix_cba(
i, cbaoi.shape, self.fit_output)
if (not _is_identity_matrix(matrix)
and not cbaoi.empty
and 0 not in cbaoi.shape[0:2]):
# TODO this is hacky
if augm_name == "bounding_boxes":
# Ensure that 4 points are used for bbs.
# to_keypoints_on_images() does return 4 points,
# to_xy_array() does not.
kpsoi = cbaoi.to_keypoints_on_image()
coords = kpsoi.to_xy_array()
coords_aug = tf.matrix_transform(coords, matrix)
kpsoi = kpsoi.fill_from_xy_array_(coords_aug)
cbaoi = cbaoi.invert_to_keypoints_on_image_(
kpsoi)
else:
coords = cbaoi.to_xy_array()
coords_aug = tf.matrix_transform(coords, matrix)
cbaoi = cbaoi.fill_from_xy_array_(coords_aug)
cbaoi.shape = output_shape
augm_value[i] = cbaoi
return batch
def _augment_images_by_samples(self, images, samples,
image_shapes=None,
return_matrices=False):
if image_shapes is None:
image_shapes = [image.shape for image in images]
input_was_array = ia.is_np_array(images)
input_dtype = None if not input_was_array else images.dtype
result = []
matrices = []
gen = enumerate(
zip(
images, image_shapes, samples.cval, samples.mode, samples.order
)
)
for i, (image, image_shape, cval, mode, order) in gen:
matrix, output_shape = samples.to_matrix(
i, image.shape, image_shape, self.fit_output
)
image_warped = image
if not _is_identity_matrix(matrix):
image_warped = _warp_affine_arr(
image,
matrix,
order=order,
mode=mode,
cval=cval,
output_shape=output_shape,
backend=self.backend
)
result.append(image_warped)
if return_matrices:
matrices.append(matrix)
# the shapes can change due to fit_output, then it may not be possible
# to return an array, even when the input was an array
if input_was_array:
nb_shapes = len({image.shape for image in result})
if nb_shapes == 1:
result = np.array(result, input_dtype)
if return_matrices:
result = (result, matrices)
return result
# Added in 0.4.0.
def _augment_maps_by_samples(self, augmentables, samples,
arr_attr_name, cval, mode, order, cval_dtype):
nb_images = len(augmentables)
samples = samples.copy()
if cval is not None:
samples.cval = np.full((nb_images, 1), cval, dtype=cval_dtype)
if mode is not None:
samples.mode = [mode] * nb_images
if order is not None:
samples.order = [order] * nb_images
arrs = [getattr(augmentable, arr_attr_name)
for augmentable in augmentables]
image_shapes = [augmentable.shape for augmentable in augmentables]
arrs_aug, matrices = self._augment_images_by_samples(
arrs, samples, image_shapes=image_shapes, return_matrices=True)
gen = zip(augmentables, arrs_aug, matrices, samples.order)
for augmentable_i, arr_aug, matrix, order_i in gen:
# skip augmented HM/SM arrs for which the images were not
# augmented due to being zero-sized
if 0 in augmentable_i.shape:
continue
# order=3 matches cubic interpolation and can cause values to go
# outside of the range [0.0, 1.0] not clear whether 4+ also do that
# We don't clip here for Segmentation Maps, because for these
# the value range isn't clearly limited to [0, 1] (and they should
# also never use order=3 to begin with).
# TODO add test for this
if order_i >= 3 and isinstance(augmentable_i, ia.HeatmapsOnImage):
arr_aug = np.clip(arr_aug, 0.0, 1.0, out=arr_aug)
setattr(augmentable_i, arr_attr_name, arr_aug)
if self.fit_output:
_, output_shape_i = _compute_affine_warp_output_shape(
matrix, augmentable_i.shape
)
else:
output_shape_i = augmentable_i.shape
augmentable_i.shape = output_shape_i
return augmentables
def _draw_samples(self, nb_samples, random_state):
rngs = random_state.duplicate(12)
if isinstance(self.scale, tuple):
scale_samples = (
self.scale[0].draw_samples((nb_samples,), random_state=rngs[0]),
self.scale[1].draw_samples((nb_samples,), random_state=rngs[1]),
)
else:
scale_samples = self.scale.draw_samples((nb_samples,),
random_state=rngs[2])
scale_samples = (scale_samples, scale_samples)
if self.translate[1] is not None:
translate_samples = (
self.translate[0].draw_samples((nb_samples,),
random_state=rngs[3]),
self.translate[1].draw_samples((nb_samples,),
random_state=rngs[4]),
)
else:
translate_samples = self.translate[0].draw_samples(
(nb_samples,), random_state=rngs[5])
translate_samples = (translate_samples, translate_samples)
rotate_samples = self.rotate.draw_samples((nb_samples,),
random_state=rngs[6])
if self._shear_param_type == "dict":
shear_samples = (
self.shear[0].draw_samples((nb_samples,), random_state=rngs[7]),
self.shear[1].draw_samples((nb_samples,), random_state=rngs[8])
)
elif self._shear_param_type == "single-number":
# only shear on the x-axis if a single number was given
shear_samples = self.shear.draw_samples((nb_samples,),
random_state=rngs[7])
shear_samples = (shear_samples, np.zeros_like(shear_samples))
else:
shear_samples = self.shear.draw_samples((nb_samples,),
random_state=rngs[7])
shear_samples = (shear_samples, shear_samples)
cval_samples = self.cval.draw_samples((nb_samples, 3),
random_state=rngs[9])
mode_samples = self.mode.draw_samples((nb_samples,),
random_state=rngs[10])
order_samples = self.order.draw_samples((nb_samples,),
random_state=rngs[11])
return _AffineSamplingResult(
scale=scale_samples,
translate=translate_samples,
translate_mode=self.translate[2],
rotate=rotate_samples,
shear=shear_samples,
cval=cval_samples,
mode=mode_samples,
order=order_samples
)
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [
self.scale, self.translate, self.rotate, self.shear, self.order,
self.cval, self.mode, self.backend, self.fit_output
]
class ScaleX(Affine):
"""Apply affine scaling on the x-axis to input data.
This is a wrapper around :class:`Affine`.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.geometric.Affine`.
Parameters
----------
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Analogous to ``scale`` in :class:`Affine`, except that this scale
value only affects the x-axis. No dictionary input is allowed.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
fit_output : bool, optional
See :class:`Affine`.
backend : str, optional
See :class:`Affine`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.ScaleX((0.5, 1.5))
Create an augmenter that scales images along the width to sizes between
``50%`` and ``150%``. This does not change the image shape (i.e. height
and width), only the pixels within the image are remapped and potentially
new ones are filled in.
"""
# Added in 0.4.0.
def __init__(self, scale=(0.5, 1.5), order=1, cval=0, mode="constant",
fit_output=False, backend="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ScaleX, self).__init__(
scale={"x": scale},
order=order,
cval=cval,
mode=mode,
fit_output=fit_output,
backend=backend,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ScaleY(Affine):
"""Apply affine scaling on the y-axis to input data.
This is a wrapper around :class:`Affine`.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.geometric.Affine`.
Parameters
----------
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Analogous to ``scale`` in :class:`Affine`, except that this scale
value only affects the y-axis. No dictionary input is allowed.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
fit_output : bool, optional
See :class:`Affine`.
backend : str, optional
See :class:`Affine`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.ScaleY((0.5, 1.5))
Create an augmenter that scales images along the height to sizes between
``50%`` and ``150%``. This does not change the image shape (i.e. height
and width), only the pixels within the image are remapped and potentially
new ones are filled in.
"""
# Added in 0.4.0.
def __init__(self, scale=(0.5, 1.5), order=1, cval=0, mode="constant",
fit_output=False, backend="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ScaleY, self).__init__(
scale={"y": scale},
order=order,
cval=cval,
mode=mode,
fit_output=fit_output,
backend=backend,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO make Affine more efficient for translation-only transformations
class TranslateX(Affine):
"""Apply affine translation on the x-axis to input data.
This is a wrapper around :class:`Affine`.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.geometric.Affine`.
Parameters
----------
percent : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Analogous to ``translate_percent`` in :class:`Affine`, except that
this translation value only affects the x-axis. No dictionary input
is allowed.
px : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
Analogous to ``translate_px`` in :class:`Affine`, except that
this translation value only affects the x-axis. No dictionary input
is allowed.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
fit_output : bool, optional
See :class:`Affine`.
backend : str, optional
See :class:`Affine`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.TranslateX(px=(-20, 20))
Create an augmenter that translates images along the x-axis by
``-20`` to ``20`` pixels.
>>> aug = iaa.TranslateX(percent=(-0.1, 0.1))
Create an augmenter that translates images along the x-axis by
``-10%`` to ``10%`` (relative to the x-axis size).
"""
# Added in 0.4.0.
def __init__(self, percent=None, px=None, order=1,
cval=0, mode="constant", fit_output=False, backend="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
if percent is None and px is None:
percent = (-0.25, 0.25)
super(TranslateX, self).__init__(
translate_percent=({"x": percent} if percent is not None else None),
translate_px=({"x": px} if px is not None else None),
order=order,
cval=cval,
mode=mode,
fit_output=fit_output,
backend=backend,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO make Affine more efficient for translation-only transformations
class TranslateY(Affine):
"""Apply affine translation on the y-axis to input data.
This is a wrapper around :class:`Affine`.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.geometric.Affine`.
Parameters
----------
percent : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Analogous to ``translate_percent`` in :class:`Affine`, except that
this translation value only affects the y-axis. No dictionary input
is allowed.
px : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
Analogous to ``translate_px`` in :class:`Affine`, except that
this translation value only affects the y-axis. No dictionary input
is allowed.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
fit_output : bool, optional
See :class:`Affine`.
backend : str, optional
See :class:`Affine`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.TranslateY(px=(-20, 20))
Create an augmenter that translates images along the y-axis by
``-20`` to ``20`` pixels.
>>> aug = iaa.TranslateY(percent=(-0.1, 0.1))
Create an augmenter that translates images along the y-axis by
``-10%`` to ``10%`` (relative to the y-axis size).
"""
# Added in 0.4.0.
def __init__(self, percent=None, px=None, order=1,
cval=0, mode="constant", fit_output=False, backend="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
if percent is None and px is None:
percent = (-0.25, 0.25)
super(TranslateY, self).__init__(
translate_percent=({"y": percent} if percent is not None else None),
translate_px=({"y": px} if px is not None else None),
order=order,
cval=cval,
mode=mode,
fit_output=fit_output,
backend=backend,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Rotate(Affine):
"""Apply affine rotation on the y-axis to input data.
This is a wrapper around :class:`Affine`.
It is the same as ``Affine(rotate=)``.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.geometric.Affine`.
Parameters
----------
rotate : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
fit_output : bool, optional
See :class:`Affine`.
backend : str, optional
See :class:`Affine`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Rotate((-45, 45))
Create an augmenter that rotates images by a random value between ``-45``
and ``45`` degress.
"""
# Added in 0.4.0.
def __init__(self, rotate=(-30, 30), order=1, cval=0, mode="constant",
fit_output=False, backend="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Rotate, self).__init__(
rotate=rotate,
order=order,
cval=cval,
mode=mode,
fit_output=fit_output,
backend=backend,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ShearX(Affine):
"""Apply affine shear on the x-axis to input data.
This is a wrapper around :class:`Affine`.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.geometric.Affine`.
Parameters
----------
shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Analogous to ``shear`` in :class:`Affine`, except that this shear
value only affects the x-axis. No dictionary input is allowed.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
fit_output : bool, optional
See :class:`Affine`.
backend : str, optional
See :class:`Affine`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.ShearX((-20, 20))
Create an augmenter that shears images along the x-axis by random amounts
between ``-20`` and ``20`` degrees.
"""
# Added in 0.4.0.
def __init__(self, shear=(-30, 30), order=1, cval=0, mode="constant",
fit_output=False, backend="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ShearX, self).__init__(
shear={"x": shear},
order=order,
cval=cval,
mode=mode,
fit_output=fit_output,
backend=backend,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ShearY(Affine):
"""Apply affine shear on the y-axis to input data.
This is a wrapper around :class:`Affine`.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.geometric.Affine`.
Parameters
----------
shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Analogous to ``shear`` in :class:`Affine`, except that this shear
value only affects the y-axis. No dictionary input is allowed.
order : int or iterable of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :class:`Affine`.
fit_output : bool, optional
See :class:`Affine`.
backend : str, optional
See :class:`Affine`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.ShearY((-20, 20))
Create an augmenter that shears images along the y-axis by random amounts
between ``-20`` and ``20`` degrees.
"""
# Added in 0.4.0.
def __init__(self, shear=(-30, 30), order=1, cval=0, mode="constant",
fit_output=False, backend="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ShearY, self).__init__(
shear={"y": shear},
order=order,
cval=cval,
mode=mode,
fit_output=fit_output,
backend=backend,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class AffineCv2(meta.Augmenter):
"""
**Deprecated.** Augmenter to apply affine transformations to images using
cv2 (i.e. opencv) backend.
.. warning::
This augmenter is deprecated since 0.4.0.
Use ``Affine(..., backend='cv2')`` instead.
Affine transformations
involve:
- Translation ("move" image on the x-/y-axis)
- Rotation
- Scaling ("zoom" in/out)
- Shear (move one side of the image, turning a square into a trapezoid)
All such transformations can create "new" pixels in the image without a
defined content, e.g. if the image is translated to the left, pixels
are created on the right.
A method has to be defined to deal with these pixel values. The
parameters `cval` and `mode` of this class deal with this.
Some transformations involve interpolations between several pixels
of the input image to generate output pixel values. The parameter `order`
deals with the method of interpolation used for this.
Deprecated since 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: ?
* ``uint32``: ?
* ``uint64``: ?
* ``int8``: ?
* ``int16``: ?
* ``int32``: ?
* ``int64``: ?
* ``float16``: ?
* ``float32``: ?
* ``float64``: ?
* ``float128``: ?
* ``bool``: ?
Parameters
----------
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
Scaling factor to use, where ``1.0`` denotes \"no change\" and
``0.5`` is zoomed out to ``50`` percent of the original size.
* If a single number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``. That value will be
used identically for both x- and y-axis.
* If a list, then a random value will be sampled from that list
per image (again, used for both x- and y-axis).
* If a ``StochasticParameter``, then from that parameter a value
will be sampled per image (again, used for both x- and y-axis).
* If a dictionary, then it is expected to have the keys ``x``
and/or ``y``. Each of these keys can have the same values as
described above. Using a dictionary allows to set different
values for the two axis and sampling will then happen
*independently* per axis, resulting in samples that differ
between the axes.
translate_percent : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
Translation as a fraction of the image height/width (x-translation,
y-translation), where ``0`` denotes "no change" and ``0.5`` denotes
"half of the axis size".
* If ``None`` then equivalent to ``0.0`` unless `translate_px` has
a value other than ``None``.
* If a single number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``. That sampled fraction
value will be used identically for both x- and y-axis.
* If a list, then a random value will be sampled from that list
per image (again, used for both x- and y-axis).
* If a ``StochasticParameter``, then from that parameter a value
will be sampled per image (again, used for both x- and y-axis).
* If a dictionary, then it is expected to have the keys ``x``
and/or ``y``. Each of these keys can have the same values as
described above. Using a dictionary allows to set different
values for the two axis and sampling will then happen
*independently* per axis, resulting in samples that differ
between the axes.
translate_px : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
Translation in pixels.
* If ``None`` then equivalent to ``0`` unless `translate_percent`
has a value other than ``None``.
* If a single int, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the discrete interval ``[a..b]``. That number
will be used identically for both x- and y-axis.
* If a list, then a random value will be sampled from that list
per image (again, used for both x- and y-axis).
* If a ``StochasticParameter``, then from that parameter a value
will be sampled per image (again, used for both x- and y-axis).
* If a dictionary, then it is expected to have the keys ``x``
and/or ``y``. Each of these keys can have the same values as
described above. Using a dictionary allows to set different
values for the two axis and sampling will then happen
*independently* per axis, resulting in samples that differ
between the axes.
rotate : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Rotation in degrees (**NOT** radians), i.e. expected value range is
around ``[-360, 360]``. Rotation happens around the *center* of the
image, not the top left corner as in some other frameworks.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]`` and used as the rotation
value.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then this parameter will be used to
sample the rotation value per image.
shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Shear in degrees (**NOT** radians), i.e. expected value range is
around ``[-360, 360]``.
* If a number, then that value will be used for all images.
* If a tuple ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]`` and be used as the
rotation value.
* If a list, then a random value will be sampled from that list
per image.
* If a ``StochasticParameter``, then this parameter will be used
to sample the shear value per image.
order : int or list of int or str or list of str or imaug.ALL or imgaug.parameters.StochasticParameter, optional
Interpolation order to use. Allowed are:
* ``cv2.INTER_NEAREST`` (nearest-neighbor interpolation)
* ``cv2.INTER_LINEAR`` (bilinear interpolation, used by default)
* ``cv2.INTER_CUBIC`` (bicubic interpolation over ``4x4`` pixel
neighborhood)
* ``cv2.INTER_LANCZOS4``
* string ``nearest`` (same as ``cv2.INTER_NEAREST``)
* string ``linear`` (same as ``cv2.INTER_LINEAR``)
* string ``cubic`` (same as ``cv2.INTER_CUBIC``)
* string ``lanczos4`` (same as ``cv2.INTER_LANCZOS``)
``INTER_NEAREST`` (nearest neighbour interpolation) and
``INTER_NEAREST`` (linear interpolation) are the fastest.
* If a single ``int``, then that order will be used for all images.
* If a string, then it must be one of: ``nearest``, ``linear``,
``cubic``, ``lanczos4``.
* If an iterable of ``int``/``str``, then for each image a random
value will be sampled from that iterable (i.e. list of allowed
order values).
* If ``imgaug.ALL``, then equivalant to list ``[cv2.INTER_NEAREST,
cv2.INTER_LINEAR, cv2.INTER_CUBIC, cv2.INTER_LANCZOS4]``.
* If ``StochasticParameter``, then that parameter is queried per
image to sample the order value to use.
cval : number or tuple of number or list of number or imaug.ALL or imgaug.parameters.StochasticParameter, optional
The constant value to use when filling in newly created pixels.
(E.g. translating by 1px to the right will create a new 1px-wide
column of pixels on the left of the image). The value is only used
when `mode=constant`. The expected value range is ``[0, 255]`` for
``uint8`` images. It may be a float value.
* If this is a single number, then that value will be used
(e.g. 0 results in black pixels).
* If a tuple ``(a, b)``, then three values (for three image
channels) will be uniformly sampled per image from the
interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If ``imgaug.ALL`` then equivalent to tuple ``(0, 255)`.
* If a ``StochasticParameter``, a new value will be sampled from
the parameter per image.
mode : int or str or list of str or list of int or imgaug.ALL or imgaug.parameters.StochasticParameter,
optional
Method to use when filling in newly created pixels.
Same meaning as in OpenCV's border mode. Let ``abcdefgh`` be an image's
content and ``|`` be an image boundary after which new pixels are
filled in, then the valid modes and their behaviour are the following:
* ``cv2.BORDER_REPLICATE``: ``aaaaaa|abcdefgh|hhhhhhh``
* ``cv2.BORDER_REFLECT``: ``fedcba|abcdefgh|hgfedcb``
* ``cv2.BORDER_REFLECT_101``: ``gfedcb|abcdefgh|gfedcba``
* ``cv2.BORDER_WRAP``: ``cdefgh|abcdefgh|abcdefg``
* ``cv2.BORDER_CONSTANT``: ``iiiiii|abcdefgh|iiiiiii``,
where ``i`` is the defined cval.
* ``replicate``: Same as ``cv2.BORDER_REPLICATE``.
* ``reflect``: Same as ``cv2.BORDER_REFLECT``.
* ``reflect_101``: Same as ``cv2.BORDER_REFLECT_101``.
* ``wrap``: Same as ``cv2.BORDER_WRAP``.
* ``constant``: Same as ``cv2.BORDER_CONSTANT``.
The datatype of the parameter may be:
* If a single ``int``, then it must be one of the ``cv2.BORDER_*``
constants.
* If a single string, then it must be one of: ``replicate``,
``reflect``, ``reflect_101``, ``wrap``, ``constant``.
* If a list of ``int``/``str``, then per image a random mode will
be picked from that list.
* If ``imgaug.ALL``, then a random mode from all possible modes
will be picked.
* If ``StochasticParameter``, then the mode will be sampled from
that parameter per image, i.e. it must return only the above
mentioned strings.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AffineCv2(scale=2.0)
Zoom in on all images by a factor of ``2``.
>>> aug = iaa.AffineCv2(translate_px=16)
Translate all images on the x- and y-axis by 16 pixels (towards the
bottom right) and fill up any new pixels with zero (black values).
>>> aug = iaa.AffineCv2(translate_percent=0.1)
Translate all images on the x- and y-axis by ``10`` percent of their
width/height (towards the bottom right). The pixel values are computed
per axis based on that axis' size. Fill up any new pixels with zero
(black values).
>>> aug = iaa.AffineCv2(rotate=35)
Rotate all images by ``35`` *degrees*. Fill up any new pixels with zero
(black values).
>>> aug = iaa.AffineCv2(shear=15)
Shear all images by ``15`` *degrees*. Fill up any new pixels with zero
(black values).
>>> aug = iaa.AffineCv2(translate_px=(-16, 16))
Translate all images on the x- and y-axis by a random value
between ``-16`` and ``16`` pixels (to the bottom right) and fill up any new
pixels with zero (black values). The translation value is sampled once
per image and is the same for both axis.
>>> aug = iaa.AffineCv2(translate_px={"x": (-16, 16), "y": (-4, 4)})
Translate all images on the x-axis by a random value
between ``-16`` and ``16`` pixels (to the right) and on the y-axis by a
random value between ``-4`` and ``4`` pixels to the bottom. The sampling
happens independently per axis, so even if both intervals were identical,
the sampled axis-wise values would likely be different.
This also fills up any new pixels with zero (black values).
>>> aug = iaa.AffineCv2(scale=2.0, order=[0, 1])
Same as in the above `scale` example, but uses (randomly) either
nearest neighbour interpolation or linear interpolation. If `order` is
not specified, ``order=1`` would be used by default.
>>> aug = iaa.AffineCv2(translate_px=16, cval=(0, 255))
Same as in the `translate_px` example above, but newly created pixels
are now filled with a random color (sampled once per image and the
same for all newly created pixels within that image).
>>> aug = iaa.AffineCv2(translate_px=16, mode=["constant", "replicate"])
Similar to the previous example, but the newly created pixels are
filled with black pixels in half of all images (mode ``constant`` with
default `cval` being ``0``) and in the other half of all images using
``replicate`` mode, which repeats the color of the spatially closest pixel
of the corresponding image edge.
"""
def __init__(self, scale=1.0, translate_percent=None, translate_px=None,
rotate=0.0, shear=0.0, order=cv2.INTER_LINEAR, cval=0,
mode=cv2.BORDER_CONSTANT,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AffineCv2, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# using a context on __init__ seems to produce no warning,
# so warn manually here
ia.warn_deprecated(
"AffineCv2 is deprecated. "
"Use imgaug.augmenters.geometric.Affine(..., backend='cv2') "
"instead.", stacklevel=4)
available_orders = [cv2.INTER_NEAREST, cv2.INTER_LINEAR,
cv2.INTER_CUBIC, cv2.INTER_LANCZOS4]
available_orders_str = ["nearest", "linear", "cubic", "lanczos4"]
if order == ia.ALL:
self.order = iap.Choice(available_orders)
elif ia.is_single_integer(order):
assert order in available_orders, (
"Expected order's integer value to be in %s, got %d." % (
str(available_orders), order))
self.order = iap.Deterministic(order)
elif ia.is_string(order):
assert order in available_orders_str, (
"Expected order to be in %s, got %s." % (
str(available_orders_str), order))
self.order = iap.Deterministic(order)
elif isinstance(order, list):
valid_types = all(
[ia.is_single_integer(val) or ia.is_string(val)
for val in order])
assert valid_types, (
"Expected order list to only contain integers/strings, got "
"types %s." % (str([type(val) for val in order]),))
valid_orders = all(
[val in available_orders + available_orders_str
for val in order])
assert valid_orders, (
"Expected all order values to be in %s, got %s." % (
available_orders + available_orders_str, str(order),))
self.order = iap.Choice(order)
elif isinstance(order, iap.StochasticParameter):
self.order = order
else:
raise Exception(
"Expected order to be imgaug.ALL, int, string, a list of"
"int/string or StochasticParameter, got %s." % (type(order),))
if cval == ia.ALL:
self.cval = iap.DiscreteUniform(0, 255)
else:
self.cval = iap.handle_discrete_param(
cval, "cval", value_range=(0, 255), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True)
available_modes = [cv2.BORDER_REPLICATE, cv2.BORDER_REFLECT,
cv2.BORDER_REFLECT_101, cv2.BORDER_WRAP,
cv2.BORDER_CONSTANT]
available_modes_str = ["replicate", "reflect", "reflect_101",
"wrap", "constant"]
if mode == ia.ALL:
self.mode = iap.Choice(available_modes)
elif ia.is_single_integer(mode):
assert mode in available_modes, (
"Expected mode to be in %s, got %d." % (
str(available_modes), mode))
self.mode = iap.Deterministic(mode)
elif ia.is_string(mode):
assert mode in available_modes_str, (
"Expected mode to be in %s, got %s." % (
str(available_modes_str), mode))
self.mode = iap.Deterministic(mode)
elif isinstance(mode, list):
all_valid_types = all([
ia.is_single_integer(val) or ia.is_string(val) for val in mode])
assert all_valid_types, (
"Expected mode list to only contain integers/strings, "
"got types %s." % (str([type(val) for val in mode]),))
all_valid_modes = all([
val in available_modes + available_modes_str for val in mode])
assert all_valid_modes, (
"Expected all mode values to be in %s, got %s." % (
str(available_modes + available_modes_str), str(mode)))
self.mode = iap.Choice(mode)
elif isinstance(mode, iap.StochasticParameter):
self.mode = mode
else:
raise Exception(
"Expected mode to be imgaug.ALL, an int, a string, a list of "
"int/strings or StochasticParameter, got %s." % (type(mode),))
# scale
if isinstance(scale, dict):
assert "x" in scale or "y" in scale, (
"Expected scale dictionary to contain at "
"least key \"x\" or key \"y\". Found neither of them.")
x = scale.get("x", 1.0)
y = scale.get("y", 1.0)
self.scale = (
iap.handle_continuous_param(
x, "scale['x']", value_range=(0+1e-4, None),
tuple_to_uniform=True, list_to_choice=True),
iap.handle_continuous_param(
y, "scale['y']", value_range=(0+1e-4, None),
tuple_to_uniform=True, list_to_choice=True)
)
else:
self.scale = iap.handle_continuous_param(
scale, "scale", value_range=(0+1e-4, None),
tuple_to_uniform=True, list_to_choice=True)
# translate
if translate_percent is None and translate_px is None:
translate_px = 0
assert translate_percent is None or translate_px is None, (
"Expected either translate_percent or translate_px to be "
"provided, but neither of them was.")
if translate_percent is not None:
# translate by percent
if isinstance(translate_percent, dict):
assert "x" in translate_percent or "y" in translate_percent, (
"Expected translate_percent dictionary to contain at "
"least key \"x\" or key \"y\". Found neither of them.")
x = translate_percent.get("x", 0)
y = translate_percent.get("y", 0)
self.translate = (
iap.handle_continuous_param(
x, "translate_percent['x']", value_range=None,
tuple_to_uniform=True, list_to_choice=True),
iap.handle_continuous_param(
y, "translate_percent['y']", value_range=None,
tuple_to_uniform=True, list_to_choice=True)
)
else:
self.translate = iap.handle_continuous_param(
translate_percent, "translate_percent", value_range=None,
tuple_to_uniform=True, list_to_choice=True)
else:
# translate by pixels
if isinstance(translate_px, dict):
assert "x" in translate_px or "y" in translate_px, (
"Expected translate_px dictionary to contain at "
"least key \"x\" or key \"y\". Found neither of them.")
x = translate_px.get("x", 0)
y = translate_px.get("y", 0)
self.translate = (
iap.handle_discrete_param(
x, "translate_px['x']", value_range=None,
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False),
iap.handle_discrete_param(
y, "translate_px['y']", value_range=None,
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False)
)
else:
self.translate = iap.handle_discrete_param(
translate_px, "translate_px", value_range=None,
tuple_to_uniform=True, list_to_choice=True,
allow_floats=False)
self.rotate = iap.handle_continuous_param(
rotate, "rotate", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
self.shear = iap.handle_continuous_param(
shear, "shear", value_range=None, tuple_to_uniform=True,
list_to_choice=True)
def _augment_images(self, images, random_state, parents, hooks):
nb_images = len(images)
scale_samples, translate_samples, rotate_samples, shear_samples, \
cval_samples, mode_samples, order_samples = self._draw_samples(
nb_images, random_state)
result = self._augment_images_by_samples(
images, scale_samples, translate_samples, rotate_samples,
shear_samples, cval_samples, mode_samples, order_samples)
return result
@classmethod
def _augment_images_by_samples(cls, images, scale_samples,
translate_samples, rotate_samples,
shear_samples, cval_samples, mode_samples,
order_samples):
# TODO change these to class attributes
order_str_to_int = {
"nearest": cv2.INTER_NEAREST,
"linear": cv2.INTER_LINEAR,
"cubic": cv2.INTER_CUBIC,
"lanczos4": cv2.INTER_LANCZOS4
}
mode_str_to_int = {
"replicate": cv2.BORDER_REPLICATE,
"reflect": cv2.BORDER_REFLECT,
"reflect_101": cv2.BORDER_REFLECT_101,
"wrap": cv2.BORDER_WRAP,
"constant": cv2.BORDER_CONSTANT
}
nb_images = len(images)
result = images
for i in sm.xrange(nb_images):
height, width = images[i].shape[0], images[i].shape[1]
shift_x = width / 2.0 - 0.5
shift_y = height / 2.0 - 0.5
scale_x, scale_y = scale_samples[0][i], scale_samples[1][i]
translate_x = translate_samples[0][i]
translate_y = translate_samples[1][i]
if ia.is_single_float(translate_y):
translate_y_px = int(
np.round(translate_y * images[i].shape[0]))
else:
translate_y_px = translate_y
if ia.is_single_float(translate_x):
translate_x_px = int(
np.round(translate_x * images[i].shape[1]))
else:
translate_x_px = translate_x
rotate = rotate_samples[i]
shear = shear_samples[i]
cval = cval_samples[i]
mode = mode_samples[i]
order = order_samples[i]
mode = (mode
if ia.is_single_integer(mode)
else mode_str_to_int[mode])
order = (order
if ia.is_single_integer(order)
else order_str_to_int[order])
any_change = (
scale_x != 1.0 or scale_y != 1.0
or translate_x_px != 0 or translate_y_px != 0
or rotate != 0 or shear != 0
)
if any_change:
matrix_to_topleft = tf.SimilarityTransform(
translation=[-shift_x, -shift_y])
matrix_transforms = tf.AffineTransform(
scale=(scale_x, scale_y),
translation=(translate_x_px, translate_y_px),
rotation=math.radians(rotate),
shear=math.radians(shear)
)
matrix_to_center = tf.SimilarityTransform(
translation=[shift_x, shift_y])
matrix = (matrix_to_topleft
+ matrix_transforms
+ matrix_to_center)
image_warped = cv2.warpAffine(
_normalize_cv2_input_arr_(images[i]),
matrix.params[:2],
dsize=(width, height),
flags=order,
borderMode=mode,
borderValue=tuple([int(v) for v in cval])
)
# cv2 warp drops last axis if shape is (H, W, 1)
if image_warped.ndim == 2:
image_warped = image_warped[..., np.newaxis]
# warp changes uint8 to float64, making this necessary
result[i] = image_warped
else:
result[i] = images[i]
return result
def _augment_heatmaps(self, heatmaps, random_state, parents, hooks):
nb_images = len(heatmaps)
scale_samples, translate_samples, rotate_samples, shear_samples, \
cval_samples, mode_samples, order_samples = self._draw_samples(
nb_images, random_state)
cval_samples = np.zeros((cval_samples.shape[0], 1), dtype=np.float32)
mode_samples = ["constant"] * len(mode_samples)
arrs = [heatmap_i.arr_0to1 for heatmap_i in heatmaps]
arrs_aug = self._augment_images_by_samples(
arrs, scale_samples, translate_samples, rotate_samples,
shear_samples, cval_samples, mode_samples, order_samples)
for heatmap_i, arr_aug in zip(heatmaps, arrs_aug):
heatmap_i.arr_0to1 = arr_aug
return heatmaps
def _augment_segmentation_maps(self, segmaps, random_state, parents, hooks):
nb_images = len(segmaps)
scale_samples, translate_samples, rotate_samples, shear_samples, \
cval_samples, mode_samples, order_samples = self._draw_samples(
nb_images, random_state)
cval_samples = np.zeros((cval_samples.shape[0], 1), dtype=np.float32)
mode_samples = ["constant"] * len(mode_samples)
order_samples = [0] * len(order_samples)
arrs = [segmaps_i.arr for segmaps_i in segmaps]
arrs_aug = self._augment_images_by_samples(
arrs, scale_samples, translate_samples, rotate_samples,
shear_samples, cval_samples, mode_samples, order_samples)
for segmaps_i, arr_aug in zip(segmaps, arrs_aug):
segmaps_i.arr = arr_aug
return segmaps
def _augment_keypoints(self, keypoints_on_images, random_state, parents,
hooks):
result = []
nb_images = len(keypoints_on_images)
scale_samples, translate_samples, rotate_samples, shear_samples, \
_cval_samples, _mode_samples, _order_samples = self._draw_samples(
nb_images, random_state)
for i, keypoints_on_image in enumerate(keypoints_on_images):
if not keypoints_on_image.keypoints:
# AffineCv2 does not change the image shape, hence we can skip
# all steps below if there are no keypoints
result.append(keypoints_on_image)
continue
height, width = keypoints_on_image.height, keypoints_on_image.width
shift_x = width / 2.0 - 0.5
shift_y = height / 2.0 - 0.5
scale_x, scale_y = scale_samples[0][i], scale_samples[1][i]
translate_x = translate_samples[0][i]
translate_y = translate_samples[1][i]
if ia.is_single_float(translate_y):
translate_y_px = int(
np.round(translate_y * keypoints_on_image.shape[0]))
else:
translate_y_px = translate_y
if ia.is_single_float(translate_x):
translate_x_px = int(
np.round(translate_x * keypoints_on_image.shape[1]))
else:
translate_x_px = translate_x
rotate = rotate_samples[i]
shear = shear_samples[i]
any_change = (
scale_x != 1.0 or scale_y != 1.0
or translate_x_px != 0 or translate_y_px != 0
or rotate != 0 or shear != 0
)
if any_change:
matrix_to_topleft = tf.SimilarityTransform(
translation=[-shift_x, -shift_y])
matrix_transforms = tf.AffineTransform(
scale=(scale_x, scale_y),
translation=(translate_x_px, translate_y_px),
rotation=math.radians(rotate),
shear=math.radians(shear)
)
matrix_to_center = tf.SimilarityTransform(
translation=[shift_x, shift_y])
matrix = (matrix_to_topleft
+ matrix_transforms
+ matrix_to_center)
coords = keypoints_on_image.to_xy_array()
coords_aug = tf.matrix_transform(coords, matrix.params)
kps_new = [kp.deepcopy(x=coords[0], y=coords[1])
for kp, coords
in zip(keypoints_on_image.keypoints, coords_aug)]
result.append(keypoints_on_image.deepcopy(
keypoints=kps_new,
shape=keypoints_on_image.shape
))
else:
result.append(keypoints_on_image)
return result
def _augment_polygons(self, polygons_on_images, random_state, parents,
hooks):
return self._augment_polygons_as_keypoints(
polygons_on_images, random_state, parents, hooks)
def _augment_line_strings(self, line_strings_on_images, random_state,
parents, hooks):
return self._augment_line_strings_as_keypoints(
line_strings_on_images, random_state, parents, hooks)
def _augment_bounding_boxes(self, bounding_boxes_on_images, random_state,
parents, hooks):
return self._augment_bounding_boxes_as_keypoints(
bounding_boxes_on_images, random_state, parents, hooks)
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.scale, self.translate, self.rotate, self.shear,
self.order, self.cval, self.mode]
def _draw_samples(self, nb_samples, random_state):
rngs = random_state.duplicate(11)
if isinstance(self.scale, tuple):
scale_samples = (
self.scale[0].draw_samples((nb_samples,),
random_state=rngs[0]),
self.scale[1].draw_samples((nb_samples,),
random_state=rngs[1]),
)
else:
scale_samples = self.scale.draw_samples((nb_samples,),
random_state=rngs[2])
scale_samples = (scale_samples, scale_samples)
if isinstance(self.translate, tuple):
translate_samples = (
self.translate[0].draw_samples((nb_samples,),
random_state=rngs[3]),
self.translate[1].draw_samples((nb_samples,),
random_state=rngs[4]),
)
else:
translate_samples = self.translate.draw_samples(
(nb_samples,), random_state=rngs[5])
translate_samples = (translate_samples, translate_samples)
valid_dts = iadt._convert_dtype_strs_to_types(
"int32 int64 float32 float64"
)
for i in sm.xrange(2):
assert translate_samples[i].dtype in valid_dts, (
"Expected translate_samples to have any dtype of %s. "
"Got %s." % (str(valid_dts), translate_samples[i].dtype.name,))
rotate_samples = self.rotate.draw_samples((nb_samples,),
random_state=rngs[6])
shear_samples = self.shear.draw_samples((nb_samples,),
random_state=rngs[7])
cval_samples = self.cval.draw_samples((nb_samples, 3),
random_state=rngs[8])
mode_samples = self.mode.draw_samples((nb_samples,),
random_state=rngs[9])
order_samples = self.order.draw_samples((nb_samples,),
random_state=rngs[10])
return (
scale_samples, translate_samples, rotate_samples, shear_samples,
cval_samples, mode_samples, order_samples
)
class _PiecewiseAffineSamplingResult(object):
def __init__(self, nb_rows, nb_cols, jitter, order, cval, mode):
self.nb_rows = nb_rows
self.nb_cols = nb_cols
self.order = order
self.jitter = jitter
self.cval = cval
self.mode = mode
def get_clipped_cval(self, idx, dtype):
min_value, _, max_value = iadt.get_value_range_of_dtype(dtype)
cval = self.cval[idx]
cval = max(min(cval, max_value), min_value)
return cval
class PiecewiseAffine(meta.Augmenter):
"""
Apply affine transformations that differ between local neighbourhoods.
This augmenter places a regular grid of points on an image and randomly
moves the neighbourhood of these point around via affine transformations.
This leads to local distortions.
This is mostly a wrapper around scikit-image's ``PiecewiseAffine``.
See also ``Affine`` for a similar technique.
.. note::
This augmenter is very slow. See :ref:`performance`.
Try to use ``ElasticTransformation`` instead, which is at least 10x
faster.
.. note::
For coordinate-based inputs (keypoints, bounding boxes, polygons,
...), this augmenter still has to perform an image-based augmentation,
which will make it significantly slower for such inputs than other
augmenters. See :ref:`performance`.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested (1)
* ``uint32``: yes; tested (1) (2)
* ``uint64``: no (3)
* ``int8``: yes; tested (1)
* ``int16``: yes; tested (1)
* ``int32``: yes; tested (1) (2)
* ``int64``: no (3)
* ``float16``: yes; tested (1)
* ``float32``: yes; tested (1)
* ``float64``: yes; tested (1)
* ``float128``: no (3)
* ``bool``: yes; tested (1) (4)
- (1) Only tested with `order` set to ``0``.
- (2) scikit-image converts internally to ``float64``, which might
introduce inaccuracies. Tests showed that these inaccuracies
seemed to not be an issue.
- (3) Results too inaccurate.
- (4) Mapped internally to ``float64``.
Parameters
----------
scale : float or tuple of float or imgaug.parameters.StochasticParameter, optional
Each point on the regular grid is moved around via a normal
distribution. This scale factor is equivalent to the normal
distribution's sigma. Note that the jitter (how far each point is
moved in which direction) is multiplied by the height/width of the
image if ``absolute_scale=False`` (default), so this scale can be
the same for different sized images.
Recommended values are in the range ``0.01`` to ``0.05`` (weak to
strong augmentations).
* If a single ``float``, then that value will always be used as
the scale.
* If a tuple ``(a, b)`` of ``float`` s, then a random value will
be uniformly sampled per image from the interval ``[a, b]``.
* If a list, then a random value will be picked from that list
per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
nb_rows : int or tuple of int or imgaug.parameters.StochasticParameter, optional
Number of rows of points that the regular grid should have.
Must be at least ``2``. For large images, you might want to pick a
higher value than ``4``. You might have to then adjust scale to lower
values.
* If a single ``int``, then that value will always be used as the
number of rows.
* If a tuple ``(a, b)``, then a value from the discrete interval
``[a..b]`` will be uniformly sampled per image.
* If a list, then a random value will be picked from that list
per image.
* If a StochasticParameter, then that parameter will be queried to
draw one value per image.
nb_cols : int or tuple of int or imgaug.parameters.StochasticParameter, optional
Number of columns. Analogous to `nb_rows`.
order : int or list of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.geometric.Affine.__init__`.
cval : int or float or tuple of float or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.geometric.Affine.__init__`.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.geometric.Affine.__init__`.
absolute_scale : bool, optional
Take `scale` as an absolute value rather than a relative value.
polygon_recoverer : 'auto' or None or imgaug.augmentables.polygons._ConcavePolygonRecoverer, optional
The class to use to repair invalid polygons.
If ``"auto"``, a new instance of
:class`imgaug.augmentables.polygons._ConcavePolygonRecoverer`
will be created.
If ``None``, no polygon recoverer will be used.
If an object, then that object will be used and must provide a
``recover_from()`` method, similar to
:class:`~imgaug.augmentables.polygons._ConcavePolygonRecoverer`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.PiecewiseAffine(scale=(0.01, 0.05))
Place a regular grid of points on each image and then randomly move each
point around by ``1`` to ``5`` percent (with respect to the image
height/width). Pixels between these points will be moved accordingly.
>>> aug = iaa.PiecewiseAffine(scale=(0.01, 0.05), nb_rows=8, nb_cols=8)
Same as the previous example, but uses a denser grid of ``8x8`` points
(default is ``4x4``). This can be useful for large images.
"""
def __init__(self, scale=(0.0, 0.04), nb_rows=(2, 4), nb_cols=(2, 4),
order=1, cval=0, mode="constant", absolute_scale=False,
polygon_recoverer=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(PiecewiseAffine, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.scale = iap.handle_continuous_param(
scale, "scale", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True)
self.jitter = iap.Normal(loc=0, scale=self.scale)
self.nb_rows = iap.handle_discrete_param(
nb_rows, "nb_rows", value_range=(2, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
self.nb_cols = iap.handle_discrete_param(
nb_cols, "nb_cols", value_range=(2, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
self.order = _handle_order_arg(order, backend="skimage")
self.cval = _handle_cval_arg(cval)
self.mode = _handle_mode_arg(mode)
self.absolute_scale = absolute_scale
self.polygon_recoverer = polygon_recoverer
if polygon_recoverer == "auto":
self.polygon_recoverer = _ConcavePolygonRecoverer()
# Special order, mode and cval parameters for heatmaps and
# segmentation maps. These may either be None or a fixed value.
# Stochastic parameters are currently *not* supported.
# If set to None, the same values as for images will be used.
# That is really not recommended for the cval parameter.
self._order_heatmaps = 3
self._order_segmentation_maps = 0
self._mode_heatmaps = "constant"
self._mode_segmentation_maps = "constant"
self._cval_heatmaps = 0
self._cval_segmentation_maps = 0
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
samples = self._draw_samples(batch.nb_rows, random_state)
if batch.images is not None:
batch.images = self._augment_images_by_samples(batch.images,
samples)
if batch.heatmaps is not None:
batch.heatmaps = self._augment_maps_by_samples(
batch.heatmaps, "arr_0to1", samples, self._cval_heatmaps,
self._mode_heatmaps, self._order_heatmaps)
if batch.segmentation_maps is not None:
batch.segmentation_maps = self._augment_maps_by_samples(
batch.segmentation_maps, "arr", samples,
self._cval_segmentation_maps, self._mode_segmentation_maps,
self._order_segmentation_maps)
# TODO add test for recoverer
if batch.polygons is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
samples=samples)
batch.polygons = self._apply_to_polygons_as_keypoints(
batch.polygons, func, recoverer=self.polygon_recoverer)
for augm_name in ["keypoints", "bounding_boxes", "line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
samples=samples)
cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
setattr(batch, augm_name, cbaois)
return batch
# Added in 0.4.0.
def _augment_images_by_samples(self, images, samples):
iadt.gate_dtypes_strs(
images,
allowed="bool uint8 uint16 uint32 int8 int16 int32 "
"float16 float32 float64",
disallowed="uint64 int64 float128",
augmenter=self
)
result = images
for i, image in enumerate(images):
transformer = self._get_transformer(
image.shape, image.shape, samples.nb_rows[i],
samples.nb_cols[i], samples.jitter[i])
if transformer is not None:
input_dtype = image.dtype
if image.dtype.kind == "b":
image = image.astype(np.float64)
image_warped = tf.warp(
image,
transformer,
order=samples.order[i],
mode=samples.mode[i],
cval=samples.get_clipped_cval(i, image.dtype),
preserve_range=True,
output_shape=images[i].shape
)
if input_dtype.kind == "b":
image_warped = image_warped > 0.5
else:
# warp seems to change everything to float64, including
# uint8, making this necessary
image_warped = iadt.restore_dtypes_(
image_warped, input_dtype)
result[i] = image_warped
return result
# Added in 0.4.0.
def _augment_maps_by_samples(self, augmentables, arr_attr_name, samples,
cval, mode, order):
result = augmentables
for i, augmentable in enumerate(augmentables):
arr = getattr(augmentable, arr_attr_name)
transformer = self._get_transformer(
arr.shape, augmentable.shape, samples.nb_rows[i],
samples.nb_cols[i], samples.jitter[i])
if transformer is not None:
arr_warped = tf.warp(
arr,
transformer,
order=order if order is not None else samples.order[i],
mode=mode if mode is not None else samples.mode[i],
cval=cval if cval is not None else samples.cval[i],
preserve_range=True,
output_shape=arr.shape
)
# skimage converts to float64
arr_warped = arr_warped.astype(arr.dtype)
# TODO not entirely clear whether this breaks the value
# range -- Affine does
# TODO add test for this
# order=3 matches cubic interpolation and can cause values
# to go outside of the range [0.0, 1.0] not clear whether
# 4+ also do that
# We don't modify segmaps here, because they don't have a
# clear value range of [0, 1]
if order >= 3 and isinstance(augmentable, ia.HeatmapsOnImage):
arr_warped = np.clip(arr_warped, 0.0, 1.0, out=arr_warped)
setattr(augmentable, arr_attr_name, arr_warped)
return result
# Added in 0.4.0.
def _augment_keypoints_by_samples(self, kpsois, samples):
# pylint: disable=pointless-string-statement
result = []
for i, kpsoi in enumerate(kpsois):
h, w = kpsoi.shape[0:2]
transformer = self._get_transformer(
kpsoi.shape, kpsoi.shape, samples.nb_rows[i],
samples.nb_cols[i], samples.jitter[i])
if transformer is None or len(kpsoi.keypoints) == 0:
result.append(kpsoi)
else:
# Augmentation routine that only modifies keypoint coordinates
# This is efficient (coordinates of all other locations in the
# image are ignored). The code below should usually work, but
# for some reason augmented coordinates are often wildly off
# for large scale parameters (lots of jitter/distortion).
# The reason for that is unknown.
"""
coords = keypoints_on_images[i].get_coords_array()
coords_aug = transformer.inverse(coords)
result.append(
ia.KeypointsOnImage.from_coords_array(
coords_aug,
shape=keypoints_on_images[i].shape
)
)
"""
# TODO this could be done a little bit more efficient by
# removing first all KPs that are outside of the image
# plane so that no corresponding distance map has to
# be augmented
# Image based augmentation routine. Draws the keypoints on
# the image plane using distance maps (more accurate than
# just marking the points), then augments these images, then
# searches for the new (visual) location of the keypoints.
# Much slower than directly augmenting the coordinates, but
# here the only method that reliably works.
dist_maps = kpsoi.to_distance_maps(inverted=True)
dist_maps_warped = tf.warp(
dist_maps,
transformer,
order=1,
preserve_range=True,
output_shape=(kpsoi.shape[0], kpsoi.shape[1],
len(kpsoi.keypoints))
)
kps_aug = ia.KeypointsOnImage.from_distance_maps(
dist_maps_warped,
inverted=True,
threshold=0.01,
if_not_found_coords={"x": -1, "y": -1},
nb_channels=(
None if len(kpsoi.shape) < 3 else kpsoi.shape[2])
)
for kp, kp_aug in zip(kpsoi.keypoints, kps_aug.keypoints):
# Keypoints that were outside of the image plane before the
# augmentation were replaced with (-1, -1) by default (as
# they can't be drawn on the keypoint images).
within_image = (0 <= kp.x < w and 0 <= kp.y < h)
if within_image:
kp.x = kp_aug.x
kp.y = kp_aug.y
result.append(kpsoi)
return result
def _draw_samples(self, nb_images, random_state):
rss = random_state.duplicate(6)
nb_rows_samples = self.nb_rows.draw_samples((nb_images,),
random_state=rss[-6])
nb_cols_samples = self.nb_cols.draw_samples((nb_images,),
random_state=rss[-5])
order_samples = self.order.draw_samples((nb_images,),
random_state=rss[-4])
cval_samples = self.cval.draw_samples((nb_images,),
random_state=rss[-3])
mode_samples = self.mode.draw_samples((nb_images,),
random_state=rss[-2])
nb_rows_samples = np.clip(nb_rows_samples, 2, None)
nb_cols_samples = np.clip(nb_cols_samples, 2, None)
nb_cells = nb_rows_samples * nb_cols_samples
jitter = self.jitter.draw_samples((int(np.sum(nb_cells)), 2),
random_state=rss[-1])
jitter_by_image = []
counter = 0
for nb_cells_i in nb_cells:
jitter_img = jitter[counter:counter+nb_cells_i, :]
jitter_by_image.append(jitter_img)
counter += nb_cells_i
return _PiecewiseAffineSamplingResult(
nb_rows=nb_rows_samples, nb_cols=nb_cols_samples,
jitter=jitter_by_image,
order=order_samples, cval=cval_samples, mode=mode_samples)
def _get_transformer(self, augmentable_shape, image_shape, nb_rows,
nb_cols, jitter_img):
# get coords on y and x axis of points to move around
# these coordinates are supposed to be at the centers of each cell
# (otherwise the first coordinate would be at (0, 0) and could hardly
# be moved around before leaving the image),
# so we use here (half cell height/width to H/W minus half
# height/width) instead of (0, H/W)
# pylint: disable=no-else-return
y = np.linspace(0, augmentable_shape[0], nb_rows)
x = np.linspace(0, augmentable_shape[1], nb_cols)
# (H, W) and (H, W) for H=rows, W=cols
xx_src, yy_src = np.meshgrid(x, y)
# (1, HW, 2) => (HW, 2) for H=rows, W=cols
points_src = np.dstack([yy_src.flat, xx_src.flat])[0]
any_nonzero = np.any(jitter_img > 0)
if not any_nonzero:
return None
else:
# Without this, jitter gets changed between different augmentables.
# TODO if left out, only one test failed -- should be more
jitter_img = np.copy(jitter_img)
if self.absolute_scale:
if image_shape[0] > 0:
jitter_img[:, 0] = jitter_img[:, 0] / image_shape[0]
else:
jitter_img[:, 0] = 0.0
if image_shape[1] > 0:
jitter_img[:, 1] = jitter_img[:, 1] / image_shape[1]
else:
jitter_img[:, 1] = 0.0
jitter_img[:, 0] = jitter_img[:, 0] * augmentable_shape[0]
jitter_img[:, 1] = jitter_img[:, 1] * augmentable_shape[1]
points_dest = np.copy(points_src)
points_dest[:, 0] = points_dest[:, 0] + jitter_img[:, 0]
points_dest[:, 1] = points_dest[:, 1] + jitter_img[:, 1]
# Restrict all destination points to be inside the image plane.
# This is necessary, as otherwise keypoints could be augmented
# outside of the image plane and these would be replaced by
# (-1, -1), which would not conform with the behaviour of the
# other augmenters.
points_dest[:, 0] = np.clip(points_dest[:, 0],
0, augmentable_shape[0]-1)
points_dest[:, 1] = np.clip(points_dest[:, 1],
0, augmentable_shape[1]-1)
# tf.warp() results in qhull error if the points are identical,
# which is mainly the case if any axis is 0
has_low_axis = any([axis <= 1 for axis in augmentable_shape[0:2]])
has_zero_channels = (
(
augmentable_shape is not None
and len(augmentable_shape) == 3
and augmentable_shape[-1] == 0
)
or
(
image_shape is not None
and len(image_shape) == 3
and image_shape[-1] == 0
)
)
if has_low_axis or has_zero_channels:
return None
else:
matrix = tf.PiecewiseAffineTransform()
matrix.estimate(points_src[:, ::-1], points_dest[:, ::-1])
return matrix
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [
self.scale, self.nb_rows, self.nb_cols, self.order, self.cval,
self.mode, self.absolute_scale]
class _PerspectiveTransformSamplingResult(object):
def __init__(self, matrices, max_heights, max_widths, cvals, modes):
self.matrices = matrices
self.max_heights = max_heights
self.max_widths = max_widths
self.cvals = cvals
self.modes = modes
# TODO add arg for image interpolation
class PerspectiveTransform(meta.Augmenter):
"""
Apply random four point perspective transformations to images.
Each of the four points is placed on the image using a random distance from
its respective corner. The distance is sampled from a normal distribution.
As a result, most transformations don't change the image very much, while
some "focus" on polygons far inside the image.
The results of this augmenter have some similarity with ``Crop``.
Code partially from
http://www.pyimagesearch.com/2014/08/25/4-point-opencv-getperspective-transform-example/
**Supported dtypes**:
if (keep_size=False):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested (3)
* ``int16``: yes; tested
* ``int32``: no (2)
* ``int64``: no (2)
* ``float16``: yes; tested (4)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (1)
* ``bool``: yes; tested (4)
- (1) rejected by opencv
- (2) leads to opencv error: cv2.error: ``OpenCV(3.4.4)
(...)imgwarp.cpp:1805: error: (-215:Assertion failed)
ifunc != 0 in function 'remap'``.
- (3) mapped internally to ``int16``.
- (4) mapped intenally to ``float32``.
if (keep_size=True):
minimum of (
``imgaug.augmenters.geometric.PerspectiveTransform(keep_size=False)``,
:func:`~imgaug.imgaug.imresize_many_images`
)
Parameters
----------
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Standard deviation of the normal distributions. These are used to
sample the random distances of the subimage's corners from the full
image's corners. The sampled values reflect percentage values (with
respect to image height/width). Recommended values are in the range
``0.0`` to ``0.1``.
* If a single number, then that value will always be used as the
scale.
* If a tuple ``(a, b)`` of numbers, then a random value will be
uniformly sampled per image from the interval ``(a, b)``.
* If a list of values, a random value will be picked from the
list per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
keep_size : bool, optional
Whether to resize image's back to their original size after applying
the perspective transform. If set to ``False``, the resulting images
may end up having different shapes and will always be a list, never
an array.
cval : number or tuple of number or list of number or imaug.ALL or imgaug.parameters.StochasticParameter, optional
The constant value used to fill up pixels in the result image that
didn't exist in the input image (e.g. when translating to the left,
some new pixels are created at the right). Such a fill-up with a
constant value only happens, when `mode` is ``constant``.
The expected value range is ``[0, 255]`` for ``uint8`` images.
It may be a float value.
* If this is a single int or float, then that value will be used
(e.g. 0 results in black pixels).
* If a tuple ``(a, b)``, then a random value is uniformly sampled
per image from the interval ``[a, b]``.
* If a list, then a random value will be sampled from that list
per image.
* If ``imgaug.ALL``, then equivalent to tuple ``(0, 255)``.
* If a ``StochasticParameter``, a new value will be sampled from
the parameter per image.
mode : int or str or list of str or list of int or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
Parameter that defines the handling of newly created pixels.
Same meaning as in OpenCV's border mode. Let ``abcdefgh`` be an image's
content and ``|`` be an image boundary, then:
* ``cv2.BORDER_REPLICATE``: ``aaaaaa|abcdefgh|hhhhhhh``
* ``cv2.BORDER_CONSTANT``: ``iiiiii|abcdefgh|iiiiiii``, where
``i`` is the defined cval.
* ``replicate``: Same as ``cv2.BORDER_REPLICATE``.
* ``constant``: Same as ``cv2.BORDER_CONSTANT``.
The datatype of the parameter may be:
* If a single ``int``, then it must be one of ``cv2.BORDER_*``.
* If a single string, then it must be one of: ``replicate``,
``reflect``, ``reflect_101``, ``wrap``, ``constant``.
* If a list of ints/strings, then per image a random mode will be
picked from that list.
* If ``imgaug.ALL``, then a random mode from all possible modes
will be picked per image.
* If ``StochasticParameter``, then the mode will be sampled from
that parameter per image, i.e. it must return only the above
mentioned strings.
fit_output : bool, optional
If ``True``, the image plane size and position will be adjusted
to still capture the whole image after perspective transformation.
(Followed by image resizing if `keep_size` is set to ``True``.)
Otherwise, parts of the transformed image may be outside of the image
plane.
This setting should not be set to ``True`` when using large `scale`
values as it could lead to very large images.
Added in 0.4.0.
polygon_recoverer : 'auto' or None or imgaug.augmentables.polygons._ConcavePolygonRecoverer, optional
The class to use to repair invalid polygons.
If ``"auto"``, a new instance of
:class`imgaug.augmentables.polygons._ConcavePolygonRecoverer`
will be created.
If ``None``, no polygon recoverer will be used.
If an object, then that object will be used and must provide a
``recover_from()`` method, similar to
:class:`~imgaug.augmentables.polygons._ConcavePolygonRecoverer`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.PerspectiveTransform(scale=(0.01, 0.15))
Apply perspective transformations using a random scale between ``0.01``
and ``0.15`` per image, where the scale is roughly a measure of how far
the perspective transformation's corner points may be distanced from the
image's corner points. Higher scale values lead to stronger "zoom-in"
effects (and thereby stronger distortions).
>>> aug = iaa.PerspectiveTransform(scale=(0.01, 0.15), keep_size=False)
Same as in the previous example, but images are not resized back to
the input image size after augmentation. This will lead to smaller
output images.
"""
_BORDER_MODE_STR_TO_INT = {
"replicate": cv2.BORDER_REPLICATE,
"constant": cv2.BORDER_CONSTANT
}
def __init__(self, scale=(0.0, 0.06), cval=0, mode="constant",
keep_size=True, fit_output=False, polygon_recoverer="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(PerspectiveTransform, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.scale = iap.handle_continuous_param(
scale, "scale", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True)
self.jitter = iap.Normal(loc=0, scale=self.scale)
# setting these to 1x1 caused problems for large scales and polygon
# augmentation
# TODO there is now a recoverer for polygons - are these minima still
# needed/sensible?
self.min_width = 2
self.min_height = 2
self.cval = _handle_cval_arg(cval)
self.mode = self._handle_mode_arg(mode)
self.keep_size = keep_size
self.fit_output = fit_output
self.polygon_recoverer = polygon_recoverer
if polygon_recoverer == "auto":
self.polygon_recoverer = _ConcavePolygonRecoverer()
# Special order, mode and cval parameters for heatmaps and
# segmentation maps. These may either be None or a fixed value.
# Stochastic parameters are currently *not* supported.
# If set to None, the same values as for images will be used.
# That is really not recommended for the cval parameter.
self._order_heatmaps = cv2.INTER_LINEAR
self._order_segmentation_maps = cv2.INTER_NEAREST
self._mode_heatmaps = cv2.BORDER_CONSTANT
self._mode_segmentation_maps = cv2.BORDER_CONSTANT
self._cval_heatmaps = 0
self._cval_segmentation_maps = 0
# TODO unify this somehow with the global _handle_mode_arg() that is
# currently used for Affine and PiecewiseAffine
@classmethod
@iap._prefetchable_str
def _handle_mode_arg(cls, mode):
available_modes = [cv2.BORDER_REPLICATE, cv2.BORDER_CONSTANT]
available_modes_str = ["replicate", "constant"]
if mode == ia.ALL:
return iap.Choice(available_modes)
if ia.is_single_integer(mode):
assert mode in available_modes, (
"Expected mode to be in %s, got %d." % (
str(available_modes), mode))
return iap.Deterministic(mode)
if ia.is_string(mode):
assert mode in available_modes_str, (
"Expected mode to be in %s, got %s." % (
str(available_modes_str), mode))
return iap.Deterministic(mode)
if isinstance(mode, list):
valid_types = all([ia.is_single_integer(val) or ia.is_string(val)
for val in mode])
assert valid_types, (
"Expected mode list to only contain integers/strings, got "
"types %s." % (
", ".join([str(type(val)) for val in mode]),))
valid_modes = all([val in available_modes + available_modes_str
for val in mode])
assert valid_modes, (
"Expected all mode values to be in %s, got %s." % (
str(available_modes + available_modes_str), str(mode)))
return iap.Choice(mode)
if isinstance(mode, iap.StochasticParameter):
return mode
raise Exception(
"Expected mode to be imgaug.ALL, an int, a string, a list "
"of int/strings or StochasticParameter, got %s." % (
type(mode),))
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
# Advance once, because below we always use random_state.copy() and
# hence the sampling calls actually don't change random_state's state.
# Without this, every call of the augmenter would produce the same
# results.
random_state.advance_()
samples_images = self._draw_samples(batch.get_rowwise_shapes(),
random_state.copy())
if batch.images is not None:
batch.images = self._augment_images_by_samples(batch.images,
samples_images)
if batch.heatmaps is not None:
samples = self._draw_samples(
[augmentable.arr_0to1.shape
for augmentable in batch.heatmaps],
random_state.copy())
batch.heatmaps = self._augment_maps_by_samples(
batch.heatmaps, "arr_0to1", samples, samples_images,
self._cval_heatmaps, self._mode_heatmaps, self._order_heatmaps)
if batch.segmentation_maps is not None:
samples = self._draw_samples(
[augmentable.arr.shape
for augmentable in batch.segmentation_maps],
random_state.copy())
batch.segmentation_maps = self._augment_maps_by_samples(
batch.segmentation_maps, "arr", samples, samples_images,
self._cval_segmentation_maps, self._mode_segmentation_maps,
self._order_segmentation_maps)
# large scale values cause invalid polygons (unclear why that happens),
# hence the recoverer
# TODO add test for recoverer
if batch.polygons is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
samples_images=samples_images)
batch.polygons = self._apply_to_polygons_as_keypoints(
batch.polygons, func, recoverer=self.polygon_recoverer)
for augm_name in ["keypoints", "bounding_boxes", "line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
samples_images=samples_images)
cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
setattr(batch, augm_name, cbaois)
return batch
# Added in 0.4.0.
def _augment_images_by_samples(self, images, samples):
iadt.gate_dtypes_strs(
images,
allowed="bool uint8 uint16 int8 int16 float16 float32 float64",
disallowed="uint32 uint64 int32 int64 float128",
augmenter=self
)
result = images
if not self.keep_size:
result = list(result)
gen = enumerate(zip(images, samples.matrices, samples.max_heights,
samples.max_widths, samples.cvals, samples.modes))
for i, (image, matrix, max_height, max_width, cval, mode) in gen:
input_dtype = image.dtype
if input_dtype == iadt._INT8_DTYPE:
image = image.astype(np.int16)
elif (input_dtype
in {iadt._BOOL_DTYPE, iadt._FLOAT16_DTYPE}):
image = image.astype(np.float32)
# cv2.warpPerspective only supports <=4 channels and errors
# on axes with size zero
nb_channels = image.shape[2]
has_zero_sized_axis = (image.size == 0)
if has_zero_sized_axis:
warped = image
elif nb_channels <= 4:
warped = cv2.warpPerspective(
_normalize_cv2_input_arr_(image),
matrix,
(max_width, max_height),
borderValue=cval,
borderMode=mode)
if warped.ndim == 2 and images[i].ndim == 3:
warped = np.expand_dims(warped, 2)
else:
# warp each channel on its own
# note that cv2 removes the channel axis in case of (H,W,1)
# inputs
warped = [
cv2.warpPerspective(
_normalize_cv2_input_arr_(image[..., c]),
matrix,
(max_width, max_height),
borderValue=cval[min(c, len(cval)-1)],
borderMode=mode,
flags=cv2.INTER_LINEAR
)
for c in sm.xrange(nb_channels)
]
warped = np.stack(warped, axis=-1)
if self.keep_size and not has_zero_sized_axis:
h, w = image.shape[0:2]
warped = ia.imresize_single_image(warped, (h, w))
if input_dtype.kind == "b":
warped = warped > 0.5
elif warped.dtype != input_dtype:
warped = iadt.restore_dtypes_(warped, input_dtype)
result[i] = warped
return result
# Added in 0.4.0.
def _augment_maps_by_samples(self, augmentables, arr_attr_name,
samples, samples_images, cval, mode, flags):
result = augmentables
# estimate max_heights/max_widths for the underlying images
# this is only necessary if keep_size is False as then the underlying
# image sizes change and we need to update them here
# TODO this was re-used from before _augment_batch_() -- reoptimize
if self.keep_size:
max_heights_imgs = samples.max_heights
max_widths_imgs = samples.max_widths
else:
max_heights_imgs = samples_images.max_heights
max_widths_imgs = samples_images.max_widths
gen = enumerate(zip(augmentables, samples.matrices, samples.max_heights,
samples.max_widths))
for i, (augmentable_i, matrix, max_height, max_width) in gen:
arr = getattr(augmentable_i, arr_attr_name)
mode_i = mode
if mode is None:
mode_i = samples.modes[i]
cval_i = cval
if cval is None:
cval_i = samples.cvals[i]
nb_channels = arr.shape[2]
image_has_zero_sized_axis = (0 in augmentable_i.shape)
map_has_zero_sized_axis = (arr.size == 0)
if not image_has_zero_sized_axis:
if not map_has_zero_sized_axis:
warped = [
cv2.warpPerspective(
_normalize_cv2_input_arr_(arr[..., c]),
matrix,
(max_width, max_height),
borderValue=cval_i,
borderMode=mode_i,
flags=flags
)
for c in sm.xrange(nb_channels)
]
warped = np.stack(warped, axis=-1)
setattr(augmentable_i, arr_attr_name, warped)
if self.keep_size:
h, w = arr.shape[0:2]
augmentable_i = augmentable_i.resize((h, w))
else:
new_shape = (
max_heights_imgs[i], max_widths_imgs[i]
) + augmentable_i.shape[2:]
augmentable_i.shape = new_shape
result[i] = augmentable_i
return result
# Added in 0.4.0.
def _augment_keypoints_by_samples(self, kpsois, samples_images):
result = kpsois
gen = enumerate(zip(kpsois,
samples_images.matrices,
samples_images.max_heights,
samples_images.max_widths))
for i, (kpsoi, matrix, max_height, max_width) in gen:
image_has_zero_sized_axis = (0 in kpsoi.shape)
if not image_has_zero_sized_axis:
shape_orig = kpsoi.shape
shape_new = (max_height, max_width) + kpsoi.shape[2:]
kpsoi.shape = shape_new
if not kpsoi.empty:
kps_arr = kpsoi.to_xy_array()
warped = cv2.perspectiveTransform(
np.array([kps_arr], dtype=np.float32), matrix)
warped = warped[0]
for kp, coords in zip(kpsoi.keypoints, warped):
kp.x = coords[0]
kp.y = coords[1]
if self.keep_size:
kpsoi = kpsoi.on_(shape_orig)
result[i] = kpsoi
return result
# Added in 0.4.0.
def _draw_samples(self, shapes, random_state):
# pylint: disable=invalid-name
matrices = []
max_heights = []
max_widths = []
nb_images = len(shapes)
rngs = random_state.duplicate(3)
cval_samples = self.cval.draw_samples((nb_images, 3),
random_state=rngs[0])
mode_samples = self.mode.draw_samples((nb_images,),
random_state=rngs[1])
jitter = self.jitter.draw_samples((nb_images, 4, 2),
random_state=rngs[2])
# cv2 perspectiveTransform doesn't accept numpy arrays as cval
cval_samples_cv2 = cval_samples.tolist()
# if border modes are represented by strings, convert them to cv2
# border mode integers
if mode_samples.dtype.kind not in ["i", "u"]:
for mode, mapped_mode in self._BORDER_MODE_STR_TO_INT.items():
mode_samples[mode_samples == mode] = mapped_mode
# modify jitter to the four corner point coordinates
# some x/y values have to be modified from `jitter` to `1-jtter`
# for that
# TODO remove the abs() here. it currently only allows to "zoom-in",
# not to "zoom-out"
points = np.mod(np.abs(jitter), 1)
# top left -- no changes needed, just use jitter
# top right
points[:, 1, 0] = 1.0 - points[:, 1, 0] # w = 1.0 - jitter
# bottom right
points[:, 2, 0] = 1.0 - points[:, 2, 0] # w = 1.0 - jitter
points[:, 2, 1] = 1.0 - points[:, 2, 1] # h = 1.0 - jitter
# bottom left
points[:, 3, 1] = 1.0 - points[:, 3, 1] # h = 1.0 - jitter
for shape, points_i in zip(shapes, points):
h, w = shape[0:2]
points_i[:, 0] *= w
points_i[:, 1] *= h
# Obtain a consistent order of the points and unpack them
# individually.
# Warning: don't just do (tl, tr, br, bl) = _order_points(...)
# here, because the reordered points_i is used further below.
points_i = self._order_points(points_i)
(tl, tr, br, bl) = points_i
# compute the width of the new image, which will be the
# maximum distance between bottom-right and bottom-left
# x-coordiates or the top-right and top-left x-coordinates
min_width = None
max_width = None
while min_width is None or min_width < self.min_width:
width_top = np.sqrt(((tr[0]-tl[0])**2) + ((tr[1]-tl[1])**2))
width_bottom = np.sqrt(((br[0]-bl[0])**2) + ((br[1]-bl[1])**2))
max_width = int(max(width_top, width_bottom))
min_width = int(min(width_top, width_bottom))
if min_width < self.min_width:
step_size = (self.min_width - min_width)/2
tl[0] -= step_size
tr[0] += step_size
bl[0] -= step_size
br[0] += step_size
# compute the height of the new image, which will be the
# maximum distance between the top-right and bottom-right
# y-coordinates or the top-left and bottom-left y-coordinates
min_height = None
max_height = None
while min_height is None or min_height < self.min_height:
height_right = np.sqrt(((tr[0]-br[0])**2) + ((tr[1]-br[1])**2))
height_left = np.sqrt(((tl[0]-bl[0])**2) + ((tl[1]-bl[1])**2))
max_height = int(max(height_right, height_left))
min_height = int(min(height_right, height_left))
if min_height < self.min_height:
step_size = (self.min_height - min_height)/2
tl[1] -= step_size
tr[1] -= step_size
bl[1] += step_size
br[1] += step_size
# now that we have the dimensions of the new image, construct
# the set of destination points to obtain a "birds eye view",
# (i.e. top-down view) of the image, again specifying points
# in the top-left, top-right, bottom-right, and bottom-left
# order
# do not use width-1 or height-1 here, as for e.g. width=3, height=2
# the bottom right coordinate is at (3.0, 2.0) and not (2.0, 1.0)
dst = np.array([
[0, 0],
[max_width, 0],
[max_width, max_height],
[0, max_height]
], dtype=np.float32)
# compute the perspective transform matrix and then apply it
m = cv2.getPerspectiveTransform(points_i, dst)
if self.fit_output:
m, max_width, max_height = self._expand_transform(m, (h, w))
matrices.append(m)
max_heights.append(max_height)
max_widths.append(max_width)
mode_samples = mode_samples.astype(np.int32)
return _PerspectiveTransformSamplingResult(
matrices, max_heights, max_widths, cval_samples_cv2,
mode_samples)
@classmethod
def _order_points(cls, pts):
# initialzie a list of coordinates that will be ordered
# such that the first entry in the list is the top-left,
# the second entry is the top-right, the third is the
# bottom-right, and the fourth is the bottom-left
pts_ordered = np.zeros((4, 2), dtype=np.float32)
# the top-left point will have the smallest sum, whereas
# the bottom-right point will have the largest sum
pointwise_sum = pts.sum(axis=1)
pts_ordered[0] = pts[np.argmin(pointwise_sum)]
pts_ordered[2] = pts[np.argmax(pointwise_sum)]
# now, compute the difference between the points, the
# top-right point will have the smallest difference,
# whereas the bottom-left will have the largest difference
diff = np.diff(pts, axis=1)
pts_ordered[1] = pts[np.argmin(diff)]
pts_ordered[3] = pts[np.argmax(diff)]
# return the ordered coordinates
return pts_ordered
# Added in 0.4.0.
@classmethod
def _expand_transform(cls, matrix, shape):
height, width = shape
# do not use width-1 or height-1 here, as for e.g. width=3, height=2
# the bottom right coordinate is at (3.0, 2.0) and not (2.0, 1.0)
rect = np.array([
[0, 0],
[width, 0],
[width, height],
[0, height]], dtype=np.float32)
dst = cv2.perspectiveTransform(np.array([rect]), matrix)[0]
# get min x, y over transformed 4 points
# then modify target points by subtracting these minima
# => shift to (0, 0)
dst -= dst.min(axis=0, keepdims=True)
dst = np.around(dst, decimals=0)
matrix_expanded = cv2.getPerspectiveTransform(rect, dst)
max_width, max_height = dst.max(axis=0)
return matrix_expanded, int(max_width), int(max_height)
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.jitter, self.keep_size, self.cval, self.mode,
self.fit_output]
# TODO add independent sigmas for x/y
# TODO add independent alphas for x/y
# TODO add backend arg
class ElasticTransformation(meta.Augmenter):
"""
Transform images by moving pixels locally around using displacement fields.
The augmenter has the parameters ``alpha`` and ``sigma``. ``alpha``
controls the strength of the displacement: higher values mean that pixels
are moved further. ``sigma`` controls the smoothness of the displacement:
higher values lead to smoother patterns -- as if the image was below water
-- while low values will cause indivdual pixels to be moved very
differently from their neighbours, leading to noisy and pixelated images.
A relation of 10:1 seems to be good for ``alpha`` and ``sigma``, e.g.
``alpha=10`` and ``sigma=1`` or ``alpha=50``, ``sigma=5``. For ``128x128``
a setting of ``alpha=(0, 70.0)``, ``sigma=(4.0, 6.0)`` may be a good
choice and will lead to a water-like effect.
Code here was initially inspired by
https://gist.github.com/chsasank/4d8f68caf01f041a6453e67fb30f8f5a
For a detailed explanation, see ::
Simard, Steinkraus and Platt
Best Practices for Convolutional Neural Networks applied to Visual
Document Analysis
in Proc. of the International Conference on Document Analysis and
Recognition, 2003
.. note::
For coordinate-based inputs (keypoints, bounding boxes, polygons,
...), this augmenter still has to perform an image-based augmentation,
which will make it significantly slower for such inputs than other
augmenters. See :ref:`performance`.
**Supported dtypes**:
* ``uint8``: yes; fully tested (1)
* ``uint16``: yes; tested (1)
* ``uint32``: yes; tested (2)
* ``uint64``: limited; tested (3)
* ``int8``: yes; tested (1) (4) (5)
* ``int16``: yes; tested (4) (6)
* ``int32``: yes; tested (4) (6)
* ``int64``: limited; tested (3)
* ``float16``: yes; tested (1)
* ``float32``: yes; tested (1)
* ``float64``: yes; tested (1)
* ``float128``: no
* ``bool``: yes; tested (1) (7)
- (1) Always handled by ``cv2``.
- (2) Always handled by ``scipy``.
- (3) Only supported for ``order != 0``. Will fail for ``order=0``.
- (4) Mapped internally to ``float64`` when ``order=1``.
- (5) Mapped internally to ``int16`` when ``order>=2``.
- (6) Handled by ``cv2`` when ``order=0`` or ``order=1``, otherwise by
``scipy``.
- (7) Mapped internally to ``float32``.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Strength of the distortion field. Higher values mean that pixels are
moved further with respect to the distortion field's direction.
Should be a value from interval ``[1.0, inf]``. Set this to around
``10 * sigma`` for visible effects.
* If number, then that value will be used for all images.
* If tuple ``(a, b)``, then a random value will be uniformly
sampled per image from the interval ``[a, b]``.
* If a list, then for each image a random value will be sampled
from that list.
* If ``StochasticParameter``, then that parameter will be used to
sample a value per image.
sigma : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Corresponds to the standard deviation of the gaussian kernel used
in the original algorithm. Here, for performance reasons, it denotes
half of an average blur kernel size. (Only for ``sigma<1.5`` is
a gaussian kernel actually used.)
Higher values (for ``128x128`` images around 5.0) lead to more
water-like effects, while lower values (for ``128x128`` images
around ``1.0`` and lower) lead to more noisy, pixelated images. Set
this to around 1/10th of `alpha` for visible effects.
* If number, then that value will be used for all images.
* If tuple ``(a, b)``, then a random value will be uniformly
sampled per image from the interval ``[a, b]``.
* If a list, then for each image a random value will be sampled
from that list.
* If ``StochasticParameter``, then that parameter will be used to
sample a value per image.
order : int or list of int or imaug.ALL or imgaug.parameters.StochasticParameter, optional
Interpolation order to use. Same meaning as in
:func:`scipy.ndimage.map_coordinates` and may take any integer value
in the range ``0`` to ``5``, where orders close to ``0`` are faster.
* If a single int, then that order will be used for all images.
* If a tuple ``(a, b)``, then a random value will be uniformly
sampled per image from the interval ``[a, b]``.
* If a list, then for each image a random value will be sampled
from that list.
* If ``imgaug.ALL``, then equivalant to list
``[0, 1, 2, 3, 4, 5]``.
* If ``StochasticParameter``, then that parameter is queried per
image to sample the order value to use.
cval : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
The constant intensity value used to fill in new pixels.
This value is only used if `mode` is set to ``constant``.
For standard ``uint8`` images (value range ``0`` to ``255``), this
value may also should also be in the range ``0`` to ``255``. It may
be a ``float`` value, even for images with integer dtypes.
* If this is a single number, then that value will be used
(e.g. ``0`` results in black pixels).
* If a tuple ``(a, b)``, then a random value will be uniformly
sampled per image from the interval ``[a, b]``.
* If a list, then a random value will be picked from that list per
image.
* If ``imgaug.ALL``, a value from the discrete range ``[0..255]``
will be sampled per image.
* If a ``StochasticParameter``, a new value will be sampled from
the parameter per image.
mode : str or list of str or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
Parameter that defines the handling of newly created pixels.
May take the same values as in :func:`scipy.ndimage.map_coordinates`,
i.e. ``constant``, ``nearest``, ``reflect`` or ``wrap``.
* If a single string, then that mode will be used for all images.
* If a list of strings, then per image a random mode will be picked
from that list.
* If ``imgaug.ALL``, then a random mode from all possible modes
will be picked.
* If ``StochasticParameter``, then the mode will be sampled from
that parameter per image, i.e. it must return only the above
mentioned strings.
polygon_recoverer : 'auto' or None or imgaug.augmentables.polygons._ConcavePolygonRecoverer, optional
The class to use to repair invalid polygons.
If ``"auto"``, a new instance of
:class`imgaug.augmentables.polygons._ConcavePolygonRecoverer`
will be created.
If ``None``, no polygon recoverer will be used.
If an object, then that object will be used and must provide a
``recover_from()`` method, similar to
:class:`~imgaug.augmentables.polygons._ConcavePolygonRecoverer`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.ElasticTransformation(alpha=50.0, sigma=5.0)
Apply elastic transformations with a strength/alpha of ``50.0`` and
smoothness of ``5.0`` to all images.
>>> aug = iaa.ElasticTransformation(alpha=(0.0, 70.0), sigma=5.0)
Apply elastic transformations with a strength/alpha that comes
from the interval ``[0.0, 70.0]`` (randomly picked per image) and
with a smoothness of ``5.0``.
"""
NB_NEIGHBOURING_KEYPOINTS = 3
NEIGHBOURING_KEYPOINTS_DISTANCE = 1.0
KEYPOINT_AUG_ALPHA_THRESH = 0.05
# even at high alphas we don't augment keypoints if the sigma is too low,
# because then the pixel movements are mostly gaussian noise anyways
KEYPOINT_AUG_SIGMA_THRESH = 1.0
_MAPPING_MODE_SCIPY_CV2 = {
"constant": cv2.BORDER_CONSTANT,
"nearest": cv2.BORDER_REPLICATE,
"reflect": cv2.BORDER_REFLECT_101,
"wrap": cv2.BORDER_WRAP
}
_MAPPING_ORDER_SCIPY_CV2 = {
0: cv2.INTER_NEAREST,
1: cv2.INTER_LINEAR,
2: cv2.INTER_CUBIC,
3: cv2.INTER_CUBIC,
4: cv2.INTER_CUBIC,
5: cv2.INTER_CUBIC
}
def __init__(self, alpha=(1.0, 40.0), sigma=(4.0, 8.0), order=0, cval=0,
mode="constant",
polygon_recoverer="auto",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ElasticTransformation, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.alpha = iap.handle_continuous_param(
alpha, "alpha", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True)
self.sigma = iap.handle_continuous_param(
sigma, "sigma", value_range=(0, None), tuple_to_uniform=True,
list_to_choice=True)
self.order = self._handle_order_arg(order)
self.cval = _handle_cval_arg(cval)
self.mode = self._handle_mode_arg(mode)
self.polygon_recoverer = polygon_recoverer
if polygon_recoverer == "auto":
self.polygon_recoverer = _ConcavePolygonRecoverer()
# Special order, mode and cval parameters for heatmaps and
# segmentation maps. These may either be None or a fixed value.
# Stochastic parameters are currently *not* supported.
# If set to None, the same values as for images will be used.
# That is really not recommended for the cval parameter.
#
self._order_heatmaps = 3
self._order_segmentation_maps = 0
self._mode_heatmaps = "constant"
self._mode_segmentation_maps = "constant"
self._cval_heatmaps = 0.0
self._cval_segmentation_maps = 0
self._last_meshgrid = None
@classmethod
@iap._prefetchable
def _handle_order_arg(cls, order):
if order == ia.ALL:
return iap.Choice([0, 1, 2, 3, 4, 5])
return iap.handle_discrete_param(
order, "order", value_range=(0, 5), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
@classmethod
@iap._prefetchable_str
def _handle_mode_arg(cls, mode):
if mode == ia.ALL:
return iap.Choice(["constant", "nearest", "reflect", "wrap"])
if ia.is_string(mode):
return iap.Deterministic(mode)
if ia.is_iterable(mode):
assert all([ia.is_string(val) for val in mode]), (
"Expected mode list to only contain strings, got "
"types %s." % (
", ".join([str(type(val)) for val in mode]),))
return iap.Choice(mode)
if isinstance(mode, iap.StochasticParameter):
return mode
raise Exception(
"Expected mode to be imgaug.ALL, a string, a list of strings "
"or StochasticParameter, got %s." % (type(mode),))
def _draw_samples(self, nb_images, random_state):
rss = random_state.duplicate(nb_images+5)
alphas = self.alpha.draw_samples((nb_images,), random_state=rss[-5])
sigmas = self.sigma.draw_samples((nb_images,), random_state=rss[-4])
orders = self.order.draw_samples((nb_images,), random_state=rss[-3])
cvals = self.cval.draw_samples((nb_images,), random_state=rss[-2])
modes = self.mode.draw_samples((nb_images,), random_state=rss[-1])
return _ElasticTransformationSamplingResult(
rss[0], alphas, sigmas, orders, cvals, modes)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
# pylint: disable=invalid-name
if batch.images is not None:
iadt.gate_dtypes_strs(
batch.images,
allowed="bool uint8 uint16 uint32 uint64 int8 int16 int32 "
"int64 float16 float32 float64",
disallowed="float128",
augmenter=self
)
shapes = batch.get_rowwise_shapes()
samples = self._draw_samples(len(shapes), random_state)
smgen = _ElasticTfShiftMapGenerator()
shift_maps = smgen.generate(shapes, samples.alphas, samples.sigmas,
samples.random_state)
for i, (shape, (dx, dy)) in enumerate(zip(shapes, shift_maps)):
if batch.images is not None:
batch.images[i] = self._augment_image_by_samples(
batch.images[i], i, samples, dx, dy)
if batch.heatmaps is not None:
batch.heatmaps[i] = self._augment_hm_or_sm_by_samples(
batch.heatmaps[i], i, samples, dx, dy, "arr_0to1",
self._cval_heatmaps, self._mode_heatmaps,
self._order_heatmaps)
if batch.segmentation_maps is not None:
batch.segmentation_maps[i] = self._augment_hm_or_sm_by_samples(
batch.segmentation_maps[i], i, samples, dx, dy, "arr",
self._cval_segmentation_maps, self._mode_segmentation_maps,
self._order_segmentation_maps)
if batch.keypoints is not None:
batch.keypoints[i] = self._augment_kpsoi_by_samples(
batch.keypoints[i], i, samples, dx, dy)
if batch.bounding_boxes is not None:
batch.bounding_boxes[i] = self._augment_bbsoi_by_samples(
batch.bounding_boxes[i], i, samples, dx, dy)
if batch.polygons is not None:
batch.polygons[i] = self._augment_psoi_by_samples(
batch.polygons[i], i, samples, dx, dy)
if batch.line_strings is not None:
batch.line_strings[i] = self._augment_lsoi_by_samples(
batch.line_strings[i], i, samples, dx, dy)
return batch
# Added in 0.4.0.
def _augment_image_by_samples(self, image, row_idx, samples, dx, dy):
# pylint: disable=invalid-name
min_value, _center_value, max_value = \
iadt.get_value_range_of_dtype(image.dtype)
cval = max(min(samples.cvals[row_idx], max_value), min_value)
input_dtype = image.dtype
if image.dtype == iadt._FLOAT16_DTYPE:
image = image.astype(np.float32)
image_aug = self._map_coordinates(
image, dx, dy,
order=samples.orders[row_idx],
cval=cval,
mode=samples.modes[row_idx])
if image.dtype != input_dtype:
image_aug = iadt.restore_dtypes_(image_aug, input_dtype)
return image_aug
# Added in 0.4.0.
def _augment_hm_or_sm_by_samples(self, augmentable, row_idx, samples,
dx, dy, arr_attr_name, cval, mode, order):
# pylint: disable=invalid-name
cval = cval if cval is not None else samples.cvals[row_idx]
mode = mode if mode is not None else samples.modes[row_idx]
order = order if order is not None else samples.orders[row_idx]
# note that we do not have to check for zero-sized axes here,
# because _generate_shift_maps(), _map_coordinates(), .resize()
# and np.clip() are all known to handle arrays with zero-sized axes
arr = getattr(augmentable, arr_attr_name)
if arr.shape[0:2] == augmentable.shape[0:2]:
arr_warped = self._map_coordinates(
arr, dx, dy, order=order, cval=cval, mode=mode)
# interpolation in map_coordinates() can cause some values to
# be below/above 1.0, so we clip here
if order >= 3 and isinstance(augmentable, ia.HeatmapsOnImage):
arr_warped = np.clip(arr_warped, 0.0, 1.0, out=arr_warped)
setattr(augmentable, arr_attr_name, arr_warped)
else:
# Heatmaps/Segmaps do not have the same size as augmented
# images. This may result in indices of moved pixels being
# different. To prevent this, we use the same image size as
# for the base images, but that requires resizing the heatmaps
# temporarily to the image sizes.
height_orig, width_orig = arr.shape[0:2]
augmentable = augmentable.resize(augmentable.shape[0:2])
arr = getattr(augmentable, arr_attr_name)
# TODO will it produce similar results to first downscale the
# shift maps and then remap? That would make the remap
# step take less operations and would also mean that the
# heatmaps wouldnt have to be scaled up anymore. It would
# also simplify the code as this branch could be merged
# with the one above.
arr_warped = self._map_coordinates(
arr, dx, dy, order=order, cval=cval, mode=mode)
# interpolation in map_coordinates() can cause some values to
# be below/above 1.0, so we clip here
if order >= 3 and isinstance(augmentable, ia.HeatmapsOnImage):
arr_warped = np.clip(arr_warped, 0.0, 1.0, out=arr_warped)
setattr(augmentable, arr_attr_name, arr_warped)
augmentable = augmentable.resize((height_orig, width_orig))
return augmentable
# Added in 0.4.0.
def _augment_kpsoi_by_samples(self, kpsoi, row_idx, samples, dx, dy):
# pylint: disable=misplaced-comparison-constant, invalid-name
height, width = kpsoi.shape[0:2]
alpha = samples.alphas[row_idx]
sigma = samples.sigmas[row_idx]
# TODO add test for keypoint alignment when keypoints are empty
# Note: this block must be placed after _generate_shift_maps() to
# keep samples aligned
# Note: we should stop for zero-sized axes early here, event though
# there is a height/width check for each keypoint, because the
# channel number can also be zero
image_has_zero_sized_axes = (0 in kpsoi.shape)
params_below_thresh = (
alpha <= self.KEYPOINT_AUG_ALPHA_THRESH
or sigma <= self.KEYPOINT_AUG_SIGMA_THRESH)
if kpsoi.empty or image_has_zero_sized_axes or params_below_thresh:
# ElasticTransformation does not change the shape, hence we can
# skip the below steps
return kpsoi
for kp in kpsoi.keypoints:
within_image_plane = (0 <= kp.x < width and 0 <= kp.y < height)
if within_image_plane:
kp_neighborhood = kp.generate_similar_points_manhattan(
self.NB_NEIGHBOURING_KEYPOINTS,
self.NEIGHBOURING_KEYPOINTS_DISTANCE,
return_array=True
)
# We can clip here, because we made sure above that the
# keypoint is inside the image plane. Keypoints at the
# bottom row or right columns might be rounded outside
# the image plane, which we prevent here. We reduce
# neighbours to only those within the image plane as only
# for such points we know where to move them.
xx = np.round(kp_neighborhood[:, 0]).astype(np.int32)
yy = np.round(kp_neighborhood[:, 1]).astype(np.int32)
inside_image_mask = np.logical_and(
np.logical_and(0 <= xx, xx < width),
np.logical_and(0 <= yy, yy < height)
)
xx = xx[inside_image_mask]
yy = yy[inside_image_mask]
xxyy = np.concatenate(
[xx[:, np.newaxis], yy[:, np.newaxis]],
axis=1)
xxyy_aug = np.copy(xxyy).astype(np.float32)
xxyy_aug[:, 0] += dx[yy, xx]
xxyy_aug[:, 1] += dy[yy, xx]
med = ia.compute_geometric_median(xxyy_aug)
# uncomment to use average instead of median
# med = np.average(xxyy_aug, 0)
kp.x = med[0]
kp.y = med[1]
return kpsoi
# Added in 0.4.0.
def _augment_psoi_by_samples(self, psoi, row_idx, samples, dx, dy):
# pylint: disable=invalid-name
func = functools.partial(self._augment_kpsoi_by_samples,
row_idx=row_idx, samples=samples, dx=dx, dy=dy)
return self._apply_to_polygons_as_keypoints(
psoi, func, recoverer=self.polygon_recoverer)
# Added in 0.4.0.
def _augment_lsoi_by_samples(self, lsoi, row_idx, samples, dx, dy):
# pylint: disable=invalid-name
func = functools.partial(self._augment_kpsoi_by_samples,
row_idx=row_idx, samples=samples, dx=dx, dy=dy)
return self._apply_to_cbaois_as_keypoints(lsoi, func)
# Added in 0.4.0.
def _augment_bbsoi_by_samples(self, bbsoi, row_idx, samples, dx, dy):
# pylint: disable=invalid-name
func = functools.partial(self._augment_kpsoi_by_samples,
row_idx=row_idx, samples=samples, dx=dx, dy=dy)
return self._apply_to_cbaois_as_keypoints(bbsoi, func)
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.alpha, self.sigma, self.order, self.cval, self.mode]
def _map_coordinates(self, image, dx, dy, order=1, cval=0, mode="constant"):
"""Remap pixels in an image according to x/y shift maps.
**Supported dtypes**:
if (backend="scipy" and order=0):
* ``uint8``: yes
* ``uint16``: yes
* ``uint32``: yes
* ``uint64``: no (1)
* ``int8``: yes
* ``int16``: yes
* ``int32``: yes
* ``int64``: no (2)
* ``float16``: yes
* ``float32``: yes
* ``float64``: yes
* ``float128``: no (3)
* ``bool``: yes
- (1) produces array filled with only 0
- (2) produces array filled with when testing
with
- (3) causes: 'data type no supported'
if (backend="scipy" and order>0):
* ``uint8``: yes (1)
* ``uint16``: yes (1)
* ``uint32``: yes (1)
* ``uint64``: yes (1)
* ``int8``: yes (1)
* ``int16``: yes (1)
* ``int32``: yes (1)
* ``int64``: yes (1)
* ``float16``: yes (1)
* ``float32``: yes (1)
* ``float64``: yes (1)
* ``float128``: no (2)
* ``bool``: yes
- (1) rather loose test, to avoid having to re-compute the
interpolation
- (2) causes: 'data type no supported'
if (backend="cv2" and order=0):
* ``uint8``: yes
* ``uint16``: yes
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes
* ``int16``: yes
* ``int32``: yes
* ``int64``: no (2)
* ``float16``: yes
* ``float32``: yes
* ``float64``: yes
* ``float128``: no (3)
* ``bool``: no (4)
- (1) causes: src data type = 6 is not supported
- (2) silently converts to int32
- (3) causes: src data type = 13 is not supported
- (4) causes: src data type = 0 is not supported
if (backend="cv2" and order=1):
* ``uint8``: yes
* ``uint16``: yes
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: no (2)
* ``int16``: no (2)
* ``int32``: no (2)
* ``int64``: no (2)
* ``float16``: yes
* ``float32``: yes
* ``float64``: yes
* ``float128``: no (3)
* ``bool``: no (4)
- (1) causes: src data type = 6 is not supported
- (2) causes: OpenCV(3.4.5) (...)/imgwarp.cpp:1805:
error: (-215:Assertion failed) ifunc != 0 in function
'remap'
- (3) causes: src data type = 13 is not supported
- (4) causes: src data type = 0 is not supported
if (backend="cv2" and order>=2):
* ``uint8``: yes
* ``uint16``: yes
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: no (2)
* ``int16``: yes
* ``int32``: no (2)
* ``int64``: no (2)
* ``float16``: yes
* ``float32``: yes
* ``float64``: yes
* ``float128``: no (3)
* ``bool``: no (4)
- (1) causes: src data type = 6 is not supported
- (2) causes: OpenCV(3.4.5) (...)/imgwarp.cpp:1805:
error: (-215:Assertion failed) ifunc != 0 in function
'remap'
- (3) causes: src data type = 13 is not supported
- (4) causes: src data type = 0 is not supported
"""
# pylint: disable=invalid-name
if image.size == 0:
return np.copy(image)
if (order == 0
and image.dtype
in {iadt._UINT64_DTYPE, iadt._INT64_DTYPE}):
raise Exception(
"dtypes uint64 and int64 are only supported in "
"ElasticTransformation for order=0, got order=%d with "
"dtype=%s." % (order, image.dtype.name))
input_dtype = image.dtype
if image.dtype.kind == "b":
image = image.astype(np.float32)
elif (order == 1
and image.dtype
in {iadt._INT8_DTYPE, iadt._INT16_DTYPE, iadt._INT32_DTYPE}):
image = image.astype(np.float64)
elif order >= 2 and image.dtype == iadt._INT8_DTYPE:
image = image.astype(np.int16)
elif order >= 2 and image.dtype == iadt._INT32_DTYPE:
image = image.astype(np.float64)
shrt_max = 32767 # maximum of datatype `short`
backend = "cv2"
if order == 0:
bad_dtype_cv2 = image.dtype in iadt._convert_dtype_strs_to_types(
"uint32 uint64 int64 float128 bool"
)
elif order == 1:
bad_dtype_cv2 = image.dtype in iadt._convert_dtype_strs_to_types(
"uint32 uint64 int8 int16 int32 int64 float128 bool"
)
else:
bad_dtype_cv2 = image.dtype in iadt._convert_dtype_strs_to_types(
"uint32 uint64 int8 int32 int64 float128 bool"
)
bad_dx_shape_cv2 = (dx.shape[0] >= shrt_max or dx.shape[1] >= shrt_max)
bad_dy_shape_cv2 = (dy.shape[0] >= shrt_max or dy.shape[1] >= shrt_max)
if bad_dtype_cv2 or bad_dx_shape_cv2 or bad_dy_shape_cv2:
backend = "scipy"
assert image.ndim == 3, (
"Expected 3-dimensional image, got %d dimensions." % (image.ndim,))
h, w, nb_channels = image.shape
last = self._last_meshgrid
if last is not None and last[0].shape == (h, w):
y, x = self._last_meshgrid
else:
y, x = np.meshgrid(
np.arange(h).astype(np.float32),
np.arange(w).astype(np.float32),
indexing="ij"
)
self._last_meshgrid = (y, x)
x_shifted = x - dx
y_shifted = y - dy
if backend == "scipy":
result = np.empty_like(image)
for c in sm.xrange(image.shape[2]):
remapped_flat = ndimage.interpolation.map_coordinates(
image[..., c],
(y_shifted.flatten(), x_shifted.flatten()),
order=order,
cval=cval,
mode=mode
)
remapped = remapped_flat.reshape((h, w))
result[..., c] = remapped
else:
if image.dtype.kind == "f":
cval = float(cval)
else:
cval = int(cval)
border_mode = self._MAPPING_MODE_SCIPY_CV2[mode]
interpolation = self._MAPPING_ORDER_SCIPY_CV2[order]
is_nearest_neighbour = (interpolation == cv2.INTER_NEAREST)
map1, map2 = cv2.convertMaps(
x_shifted, y_shifted, cv2.CV_16SC2,
nninterpolation=is_nearest_neighbour)
# remap only supports up to 4 channels
if nb_channels <= 4:
# dst does not seem to improve performance here
result = cv2.remap(
_normalize_cv2_input_arr_(image),
map1,
map2,
interpolation=interpolation,
borderMode=border_mode,
borderValue=tuple([cval] * nb_channels)
)
if image.ndim == 3 and result.ndim == 2:
result = result[..., np.newaxis]
else:
current_chan_idx = 0
result = []
while current_chan_idx < nb_channels:
channels = image[..., current_chan_idx:current_chan_idx+4]
result_c = cv2.remap(
_normalize_cv2_input_arr_(channels),
map1, map2, interpolation=interpolation,
borderMode=border_mode, borderValue=(cval, cval, cval))
if result_c.ndim == 2:
result_c = result_c[..., np.newaxis]
result.append(result_c)
current_chan_idx += 4
result = np.concatenate(result, axis=2)
if result.dtype != input_dtype:
result = iadt.restore_dtypes_(result, input_dtype)
return result
class _ElasticTransformationSamplingResult(object):
def __init__(self, random_state, alphas, sigmas, orders, cvals, modes):
self.random_state = random_state
self.alphas = alphas
self.sigmas = sigmas
self.orders = orders
self.cvals = cvals
self.modes = modes
class _ElasticTfShiftMapGenerator(object):
"""Class to generate shift/displacement maps for ElasticTransformation.
This class re-uses samples for multiple examples. This minimizes the amount
of sampling that has to be done.
Added in 0.5.0.
"""
# Not really necessary to have this as a class, considering it has no
# attributes. But it makes things easier to read.
# Added in 0.5.0.
def __init__(self):
pass
# Added in 0.5.0.
def generate(self, shapes, alphas, sigmas, random_state):
# We will sample shift maps from [0.0, 1.0] and then shift by -0.5 to
# [-0.5, 0.5]. To bring these maps to [-1.0, 1.0], we have to multiply
# somewhere by 2. It is fastes to multiply the (fewer) alphas, which
# we will have to multiply the shift maps with anyways.
alphas *= 2
# Configuration for each chunk.
# switch dx / dy, flip dx lr, flip dx ud, flip dy lr, flip dy ud
switch = [False, True]
fliplr_dx = [False, True]
flipud_dx = [False, True]
fliplr_dy = [False, True]
flipud_dy = [False, True]
configs = list(
itertools.product(
switch, fliplr_dx, flipud_dx, fliplr_dy, flipud_dy
)
)
areas = [shape[0] * shape[1] for shape in shapes]
nb_chunks = len(configs)
gen = zip(
self._split_chunks(shapes, nb_chunks),
self._split_chunks(areas, nb_chunks),
self._split_chunks(alphas, nb_chunks),
self._split_chunks(sigmas, nb_chunks)
)
# "_c" denotes a chunk here
for shapes_c, areas_c, alphas_c, sigmas_c in gen:
area_max = max(areas_c)
dxdy = random_state.random((2, area_max))
dxdy -= 0.5
dx, dy = dxdy[0, :], dxdy[1, :]
# dx_lr = flip dx left-right, dx_ud = flip dx up-down
# dy_lr, dy_ud analogous
for i, (switch_i, dx_lr, dx_ud, dy_lr, dy_ud) in enumerate(configs):
if i >= len(shapes_c):
break
dx_i, dy_i = (dx, dy) if not switch_i else (dy, dx)
shape_i = shapes_c[i][0:2]
area_i = shape_i[0] * shape_i[1]
if area_i == 0:
yield (
np.zeros(shape_i, dtype=np.float32),
np.zeros(shape_i, dtype=np.float32)
)
else:
dx_i = dx_i[0:area_i].reshape(shape_i)
dy_i = dy_i[0:area_i].reshape(shape_i)
dx_i, dy_i = self._flip(dx_i, dy_i,
(dx_lr, dx_ud, dy_lr, dy_ud))
dx_i, dy_i = self._mul_alpha(dx_i, dy_i, alphas_c[i])
yield self._smoothen_(dx_i, dy_i, sigmas_c[i])
# Added in 0.5.0.
@classmethod
def _flip(cls, dx, dy, flips):
# no measureable benefit from using cv2 here
if flips[0]:
dx = np.fliplr(dx)
if flips[1]:
dx = np.flipud(dx)
if flips[2]:
dy = np.fliplr(dy)
if flips[3]:
dy = np.flipud(dy)
return dx, dy
# Added in 0.5.0.
@classmethod
def _mul_alpha(cls, dx, dy, alpha):
# performance drops for cv2.multiply here
dx = dx * alpha
dy = dy * alpha
return dx, dy
# Added in 0.5.0.
@classmethod
def _smoothen_(cls, dx, dy, sigma):
if sigma < 1.5:
dx = blur_lib.blur_gaussian_(dx, sigma)
dy = blur_lib.blur_gaussian_(dy, sigma)
else:
ksize = int(round(2*sigma))
dx = cv2.blur(dx, (ksize, ksize), dst=dx)
dy = cv2.blur(dy, (ksize, ksize), dst=dy)
return dx, dy
# Added in 0.5.0.
@classmethod
def _split_chunks(cls, iterable, chunk_size):
for i in sm.xrange(0, len(iterable), chunk_size):
yield iterable[i:i+chunk_size]
class Rot90(meta.Augmenter):
"""
Rotate images clockwise by multiples of 90 degrees.
This could also be achieved using ``Affine``, but ``Rot90`` is
significantly more efficient.
**Supported dtypes**:
if (keep_size=False):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
if (keep_size=True):
minimum of (
``imgaug.augmenters.geometric.Rot90(keep_size=False)``,
:func:`~imgaug.imgaug.imresize_many_images`
)
Parameters
----------
k : int or list of int or tuple of int or imaug.ALL or imgaug.parameters.StochasticParameter, optional
How often to rotate clockwise by 90 degrees.
* If a single ``int``, then that value will be used for all images.
* If a tuple ``(a, b)``, then a random value will be uniformly
sampled per image from the discrete interval ``[a..b]``.
* If a list, then for each image a random value will be sampled
from that list.
* If ``imgaug.ALL``, then equivalant to list ``[0, 1, 2, 3]``.
* If ``StochasticParameter``, then that parameter is queried per
image to sample the value to use.
keep_size : bool, optional
After rotation by an odd-valued `k` (e.g. 1 or 3), the resulting image
may have a different height/width than the original image.
If this parameter is set to ``True``, then the rotated
image will be resized to the input image's size. Note that this might
also cause the augmented image to look distorted.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Rot90(1)
Rotate all images by 90 degrees.
Resize these images afterwards to keep the size that they had before
augmentation.
This may cause the images to look distorted.
>>> aug = iaa.Rot90([1, 3])
Rotate all images by 90 or 270 degrees.
Resize these images afterwards to keep the size that they had before
augmentation.
This may cause the images to look distorted.
>>> aug = iaa.Rot90((1, 3))
Rotate all images by 90, 180 or 270 degrees.
Resize these images afterwards to keep the size that they had before
augmentation.
This may cause the images to look distorted.
>>> aug = iaa.Rot90((1, 3), keep_size=False)
Rotate all images by 90, 180 or 270 degrees.
Does not resize to the original image size afterwards, i.e. each image's
size may change.
"""
def __init__(self, k=1, keep_size=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Rot90, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
if k == ia.ALL:
k = [0, 1, 2, 3]
self.k = iap.handle_discrete_param(
k, "k", value_range=None, tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
self.keep_size = keep_size
def _draw_samples(self, nb_images, random_state):
return self.k.draw_samples((nb_images,), random_state=random_state)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
# pylint: disable=invalid-name
ks = self._draw_samples(batch.nb_rows, random_state)
if batch.images is not None:
batch.images = self._augment_arrays_by_samples(
batch.images, ks, self.keep_size, ia.imresize_single_image)
if batch.heatmaps is not None:
batch.heatmaps = self._augment_maps_by_samples(
batch.heatmaps, "arr_0to1", ks)
if batch.segmentation_maps is not None:
batch.segmentation_maps = self._augment_maps_by_samples(
batch.segmentation_maps, "arr", ks)
for augm_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
ks=ks)
cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
setattr(batch, augm_name, cbaois)
return batch
@classmethod
def _augment_arrays_by_samples(cls, arrs, ks, keep_size, resize_func):
# pylint: disable=invalid-name
input_was_array = ia.is_np_array(arrs)
input_dtype = arrs.dtype if input_was_array else None
arrs_aug = []
for arr, k_i in zip(arrs, ks):
# adding axes here rotates clock-wise instead of ccw
arr_aug = np.rot90(arr, k_i, axes=(1, 0))
do_resize = (
keep_size
and arr.shape != arr_aug.shape
and resize_func is not None)
if do_resize:
arr_aug = resize_func(arr_aug, arr.shape[0:2])
arrs_aug.append(arr_aug)
if keep_size and input_was_array:
n_shapes = len({arr.shape for arr in arrs_aug})
if n_shapes == 1:
arrs_aug = np.array(arrs_aug, dtype=input_dtype)
return arrs_aug
# Added in 0.4.0.
def _augment_maps_by_samples(self, augmentables, arr_attr_name, ks):
# pylint: disable=invalid-name
arrs = [getattr(map_i, arr_attr_name) for map_i in augmentables]
arrs_aug = self._augment_arrays_by_samples(
arrs, ks, self.keep_size, None)
maps_aug = []
gen = zip(augmentables, arrs, arrs_aug, ks)
for augmentable_i, arr, arr_aug, k_i in gen:
shape_orig = arr.shape
setattr(augmentable_i, arr_attr_name, arr_aug)
if self.keep_size:
augmentable_i = augmentable_i.resize(shape_orig[0:2])
elif k_i % 2 == 1:
h, w = augmentable_i.shape[0:2]
augmentable_i.shape = tuple(
[w, h] + list(augmentable_i.shape[2:]))
else:
# keep_size was False, but rotated by a multiple of 2,
# hence height and width do not change
pass
maps_aug.append(augmentable_i)
return maps_aug
# Added in 0.4.0.
def _augment_keypoints_by_samples(self, keypoints_on_images, ks):
# pylint: disable=invalid-name
result = []
for kpsoi_i, k_i in zip(keypoints_on_images, ks):
shape_orig = kpsoi_i.shape
if (k_i % 4) == 0:
result.append(kpsoi_i)
else:
k_i = int(k_i) % 4 # this is also correct when k_i is negative
h, w = kpsoi_i.shape[0:2]
h_aug, w_aug = (h, w) if (k_i % 2) == 0 else (w, h)
for kp in kpsoi_i.keypoints:
y, x = kp.y, kp.x
yr, xr = y, x
wr, hr = w, h
for _ in sm.xrange(k_i):
# for int coordinates this would instead be
# xr, yr = (hr - 1) - yr, xr
# here we assume that coordinates are always
# subpixel-accurate
xr, yr = hr - yr, xr
wr, hr = hr, wr
kp.x = xr
kp.y = yr
shape_aug = tuple([h_aug, w_aug] + list(kpsoi_i.shape[2:]))
kpsoi_i.shape = shape_aug
if self.keep_size and (h, w) != (h_aug, w_aug):
kpsoi_i = kpsoi_i.on_(shape_orig)
kpsoi_i.shape = shape_orig
result.append(kpsoi_i)
return result
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.k, self.keep_size]
# TODO semipolar
class WithPolarWarping(meta.Augmenter):
"""Augmenter that applies other augmenters in a polar-transformed space.
This augmenter first transforms an image into a polar representation,
then applies its child augmenter, then transforms back to cartesian
space. The polar representation is still in the image's input dtype
(i.e. ``uint8`` stays ``uint8``) and can be visualized. It can be thought
of as an "unrolled" version of the image, where previously circular lines
appear straight. Hence, applying child augmenters in that space can lead
to circular effects. E.g. replacing rectangular pixel areas in the polar
representation with black pixels will lead to curved black areas in
the cartesian result.
This augmenter can create new pixels in the image. It will fill these
with black pixels. For segmentation maps it will fill with class
id ``0``. For heatmaps it will fill with ``0.0``.
This augmenter is limited to arrays with a height and/or width of
``32767`` or less.
.. warning::
When augmenting coordinates in polar representation, it is possible
that these are shifted outside of the polar image, but are inside the
image plane after transforming back to cartesian representation,
usually on newly created pixels (i.e. black backgrounds).
These coordinates are currently not removed. It is recommended to
not use very strong child transformations when also augmenting
coordinate-based augmentables.
.. warning::
For bounding boxes, this augmenter suffers from the same problem as
affine rotations applied to bounding boxes, i.e. the resulting
bounding boxes can have unintuitive (seemingly wrong) appearance.
This is due to coordinates being "rotated" that are inside the
bounding box, but do not fall on the object and actually are
background.
It is recommended to use this augmenter with caution when augmenting
bounding boxes.
.. warning::
For polygons, this augmenter should not be combined with
augmenters that perform automatic polygon recovery for invalid
polygons, as the polygons will frequently appear broken in polar
representation and their "fixed" version will be very broken in
cartesian representation. Augmenters that perform such polygon
recovery are currently ``PerspectiveTransform``, ``PiecewiseAffine``
and ``ElasticTransformation``.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: no (2)
* ``float16``: yes; tested (3)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (1)
* ``bool``: yes; tested (4)
- (1) OpenCV produces error
``TypeError: Expected cv::UMat for argument 'src'``
- (2) OpenCV produces array of nothing but zeros.
- (3) Mapepd to ``float32``.
- (4) Mapped to ``uint8``.
Parameters
----------
children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
One or more augmenters to apply to images after they were transformed
to polar representation.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.WithPolarWarping(iaa.CropAndPad(percent=(-0.1, 0.1)))
Apply cropping and padding in polar representation, then warp back to
cartesian representation.
>>> aug = iaa.WithPolarWarping(
>>> iaa.Affine(
>>> translate_percent={"x": (-0.2, 0.2), "y": (-0.2, 0.2)},
>>> rotate=(-35, 35),
>>> scale=(0.8, 1.2),
>>> shear={"x": (-15, 15), "y": (-15, 15)}
>>> )
>>> )
Apply affine transformations in polar representation.
>>> aug = iaa.WithPolarWarping(iaa.AveragePooling((2, 8)))
Apply average pooling in polar representation. This leads to circular
bins.
"""
# Added in 0.4.0.
def __init__(self, children,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(WithPolarWarping, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.children = meta.handle_children_list(children, self.name, "then")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is not None:
iadt.gate_dtypes_strs(
batch.images,
allowed="bool uint8 uint16 int8 int16 int32 float16 float32 "
"float64",
disallowed="uint32 uint64 int64 float128",
augmenter=self
)
with batch.propagation_hooks_ctx(self, hooks, parents):
batch, inv_data_bbs = self._convert_bbs_to_polygons_(batch)
inv_data = {}
for column in batch.columns:
func = getattr(self, "_warp_%s_" % (column.name,))
col_aug, inv_data_col = func(column.value)
setattr(batch, column.attr_name, col_aug)
inv_data[column.name] = inv_data_col
batch = self.children.augment_batch_(batch,
parents=parents + [self],
hooks=hooks)
for column in batch.columns:
func = getattr(self, "_invert_warp_%s_" % (column.name,))
col_unaug = func(column.value, inv_data[column.name])
setattr(batch, column.attr_name, col_unaug)
batch = self._invert_convert_bbs_to_polygons_(batch, inv_data_bbs)
return batch
# Added in 0.4.0.
@classmethod
def _convert_bbs_to_polygons_(cls, batch):
batch_contained_polygons = batch.polygons is not None
if batch.bounding_boxes is None:
return batch, (False, batch_contained_polygons)
psois = [bbsoi.to_polygons_on_image() for bbsoi in batch.bounding_boxes]
psois = [psoi.subdivide_(2) for psoi in psois]
# Mark Polygons that are really Bounding Boxes
for psoi in psois:
for polygon in psoi.polygons:
if polygon.label is None:
polygon.label = "$$IMGAUG_BB_AS_POLYGON"
else:
polygon.label = polygon.label + ";$$IMGAUG_BB_AS_POLYGON"
# Merge Fake-Polygons into existing Polygons
if batch.polygons is None:
batch.polygons = psois
else:
for psoi, bbs_as_psoi in zip(batch.polygons, psois):
assert psoi.shape == bbs_as_psoi.shape, (
"Expected polygons and bounding boxes to have the same "
".shape value, got %s and %s." % (psoi.shape,
bbs_as_psoi.shape))
psoi.polygons.extend(bbs_as_psoi.polygons)
batch.bounding_boxes = None
return batch, (True, batch_contained_polygons)
# Added in 0.4.0.
@classmethod
def _invert_convert_bbs_to_polygons_(cls, batch, inv_data):
batch_contained_bbs, batch_contained_polygons = inv_data
if not batch_contained_bbs:
return batch
bbsois = []
for psoi in batch.polygons:
polygons = []
bbs = []
for polygon in psoi.polygons:
is_bb = False
if polygon.label is None:
is_bb = False
elif polygon.label == "$$IMGAUG_BB_AS_POLYGON":
polygon.label = None
is_bb = True
elif polygon.label.endswith(";$$IMGAUG_BB_AS_POLYGON"):
polygon.label = \
polygon.label[:-len(";$$IMGAUG_BB_AS_POLYGON")]
is_bb = True
if is_bb:
bbs.append(polygon.to_bounding_box())
else:
polygons.append(polygon)
psoi.polygons = polygons
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=psoi.shape)
bbsois.append(bbsoi)
batch.bounding_boxes = bbsois
if not batch_contained_polygons:
batch.polygons = None
return batch
# Added in 0.4.0.
@classmethod
def _warp_images_(cls, images):
return cls._warp_arrays(images, False)
# Added in 0.4.0.
@classmethod
def _invert_warp_images_(cls, images_warped, inv_data):
return cls._invert_warp_arrays(images_warped, False, inv_data)
# Added in 0.4.0.
@classmethod
def _warp_heatmaps_(cls, heatmaps):
return cls._warp_maps_(heatmaps, "arr_0to1", False)
# Added in 0.4.0.
@classmethod
def _invert_warp_heatmaps_(cls, heatmaps_warped, inv_data):
return cls._invert_warp_maps_(heatmaps_warped, "arr_0to1", False,
inv_data)
# Added in 0.4.0.
@classmethod
def _warp_segmentation_maps_(cls, segmentation_maps):
return cls._warp_maps_(segmentation_maps, "arr", True)
# Added in 0.4.0.
@classmethod
def _invert_warp_segmentation_maps_(cls, segmentation_maps_warped,
inv_data):
return cls._invert_warp_maps_(segmentation_maps_warped, "arr", True,
inv_data)
# Added in 0.4.0.
@classmethod
def _warp_keypoints_(cls, kpsois):
return cls._warp_cbaois_(kpsois)
# Added in 0.4.0.
@classmethod
def _invert_warp_keypoints_(cls, kpsois_warped, image_shapes_orig):
return cls._invert_warp_cbaois_(kpsois_warped, image_shapes_orig)
# Added in 0.4.0.
@classmethod
def _warp_bounding_boxes_(cls, bbsois): # pylint: disable=useless-return
assert bbsois is None, ("Expected BBs to have been converted "
"to polygons.")
return None
# Added in 0.4.0.
@classmethod
def _invert_warp_bounding_boxes_(cls, bbsois_warped, _image_shapes_orig): # pylint: disable=useless-return
assert bbsois_warped is None, ("Expected BBs to have been converted "
"to polygons.")
return None
# Added in 0.4.0.
@classmethod
def _warp_polygons_(cls, psois):
return cls._warp_cbaois_(psois)
# Added in 0.4.0.
@classmethod
def _invert_warp_polygons_(cls, psois_warped, image_shapes_orig):
return cls._invert_warp_cbaois_(psois_warped, image_shapes_orig)
# Added in 0.4.0.
@classmethod
def _warp_line_strings_(cls, lsois):
return cls._warp_cbaois_(lsois)
# Added in 0.4.0.
@classmethod
def _invert_warp_line_strings_(cls, lsois_warped, image_shapes_orig):
return cls._invert_warp_cbaois_(lsois_warped, image_shapes_orig)
# Added in 0.4.0.
@classmethod
def _warp_arrays(cls, arrays, interpolation_nearest):
if arrays is None:
return None, None
flags = cv2.WARP_FILL_OUTLIERS + cv2.WARP_POLAR_LINEAR
if interpolation_nearest:
flags += cv2.INTER_NEAREST
arrays_warped = []
shapes_orig = []
for arr in arrays:
if 0 in arr.shape:
arrays_warped.append(arr)
shapes_orig.append(arr.shape)
continue
input_dtype = arr.dtype
if input_dtype.kind == "b":
arr = arr.astype(np.uint8) * 255
elif input_dtype == iadt._FLOAT16_DTYPE:
arr = arr.astype(np.float32)
height, width = arr.shape[0:2]
# remap limitation, see docs for warpPolar()
assert height <= 32767 and width <= 32767, (
"WithPolarWarping._warp_arrays() can currently only handle "
"arrays with axis sizes below 32767, but got shape %s. This "
"is an OpenCV limitation." % (arr.shape,))
dest_size = (0, 0)
center_xy = (width/2, height/2)
max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
if arr.ndim == 3 and arr.shape[-1] > 512:
arr_warped = np.stack(
[cv2.warpPolar(_normalize_cv2_input_arr_(arr[..., c_idx]),
dest_size, center_xy, max_radius, flags)
for c_idx in np.arange(arr.shape[-1])],
axis=-1)
else:
arr_warped = cv2.warpPolar(_normalize_cv2_input_arr_(arr),
dest_size, center_xy, max_radius,
flags)
if arr_warped.ndim == 2 and arr.ndim == 3:
arr_warped = arr_warped[:, :, np.newaxis]
if input_dtype.kind == "b":
arr_warped = (arr_warped > 128)
elif input_dtype == iadt._FLOAT16_DTYPE:
arr_warped = arr_warped.astype(np.float16)
arrays_warped.append(arr_warped)
shapes_orig.append(arr.shape)
return arrays_warped, shapes_orig
# Added in 0.4.0.
@classmethod
def _invert_warp_arrays(cls, arrays_warped, interpolation_nearest,
inv_data):
shapes_orig = inv_data
if arrays_warped is None:
return None
flags = (cv2.WARP_FILL_OUTLIERS + cv2.WARP_POLAR_LINEAR
+ cv2.WARP_INVERSE_MAP)
if interpolation_nearest:
flags += cv2.INTER_NEAREST
# TODO this does per iteration almost the same as _warp_arrays()
# make DRY
arrays_inv = []
for arr_warped, shape_orig in zip(arrays_warped, shapes_orig):
if 0 in arr_warped.shape:
arrays_inv.append(arr_warped)
continue
input_dtype = arr_warped.dtype
if input_dtype.kind == "b":
arr_warped = arr_warped.astype(np.uint8) * 255
elif input_dtype == iadt._FLOAT16_DTYPE:
arr_warped = arr_warped.astype(np.float32)
height, width = shape_orig[0:2]
# remap limitation, see docs for warpPolar()
assert (arr_warped.shape[0] <= 32767
and arr_warped.shape[1] <= 32767), (
"WithPolarWarping._warp_arrays() can currently only "
"handle arrays with axis sizes below 32767, but got "
"shape %s. This is an OpenCV limitation." % (
arr_warped.shape,))
dest_size = (width, height)
center_xy = (width/2, height/2)
max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
if arr_warped.ndim == 3 and arr_warped.shape[-1] > 512:
arr_inv = np.stack(
[cv2.warpPolar(
_normalize_cv2_input_arr_(arr_warped[..., c_idx]),
dest_size, center_xy, max_radius, flags)
for c_idx in np.arange(arr_warped.shape[-1])],
axis=-1)
else:
arr_inv = cv2.warpPolar(
_normalize_cv2_input_arr_(arr_warped),
dest_size, center_xy, max_radius, flags)
if arr_inv.ndim == 2 and arr_warped.ndim == 3:
arr_inv = arr_inv[:, :, np.newaxis]
if input_dtype.kind == "b":
arr_inv = (arr_inv > 128)
elif input_dtype == iadt._FLOAT16_DTYPE:
arr_inv = arr_inv.astype(np.float16)
arrays_inv.append(arr_inv)
return arrays_inv
# Added in 0.4.0.
@classmethod
def _warp_maps_(cls, maps, arr_attr_name, interpolation_nearest):
if maps is None:
return None, None
skipped = [False] * len(maps)
arrays = []
shapes_imgs_orig = []
for i, map_i in enumerate(maps):
if 0 in map_i.shape:
skipped[i] = True
arrays.append(np.zeros((0, 0), dtype=np.int32))
shapes_imgs_orig.append(map_i.shape)
else:
arrays.append(getattr(map_i, arr_attr_name))
shapes_imgs_orig.append(map_i.shape)
arrays_warped, warparr_inv_data = cls._warp_arrays(
arrays, interpolation_nearest)
shapes_imgs_warped = cls._warp_shape_tuples(shapes_imgs_orig)
for i, map_i in enumerate(maps):
if not skipped[i]:
map_i.shape = shapes_imgs_warped[i]
setattr(map_i, arr_attr_name, arrays_warped[i])
return maps, (shapes_imgs_orig, warparr_inv_data, skipped)
# Added in 0.4.0.
@classmethod
def _invert_warp_maps_(cls, maps_warped, arr_attr_name,
interpolation_nearest, invert_data):
if maps_warped is None:
return None
shapes_imgs_orig, warparr_inv_data, skipped = invert_data
arrays_warped = []
for i, map_warped in enumerate(maps_warped):
if skipped[i]:
arrays_warped.append(np.zeros((0, 0), dtype=np.int32))
else:
arrays_warped.append(getattr(map_warped, arr_attr_name))
arrays_inv = cls._invert_warp_arrays(arrays_warped,
interpolation_nearest,
warparr_inv_data)
for i, map_i in enumerate(maps_warped):
if not skipped[i]:
map_i.shape = shapes_imgs_orig[i]
setattr(map_i, arr_attr_name, arrays_inv[i])
return maps_warped
# Added in 0.4.0.
@classmethod
def _warp_coords(cls, coords, image_shapes):
if coords is None:
return None, None
image_shapes_warped = cls._warp_shape_tuples(image_shapes)
flags = cv2.WARP_POLAR_LINEAR
coords_warped = []
for coords_i, shape, shape_warped in zip(coords, image_shapes,
image_shapes_warped):
if 0 in shape:
coords_warped.append(coords_i)
continue
height, width = shape[0:2]
dest_size = (shape_warped[1], shape_warped[0])
center_xy = (width/2, height/2)
max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
coords_i_warped = cls.warpPolarCoords(
coords_i, dest_size, center_xy, max_radius, flags)
coords_warped.append(coords_i_warped)
return coords_warped, image_shapes
# Added in 0.4.0.
@classmethod
def _invert_warp_coords(cls, coords_warped, image_shapes_after_aug,
inv_data):
image_shapes_orig = inv_data
if coords_warped is None:
return None
flags = cv2.WARP_POLAR_LINEAR + cv2.WARP_INVERSE_MAP
coords_inv = []
gen = enumerate(zip(coords_warped, image_shapes_orig))
for i, (coords_i_warped, shape_orig) in gen:
if 0 in shape_orig:
coords_inv.append(coords_i_warped)
continue
shape_warped = image_shapes_after_aug[i]
height, width = shape_orig[0:2]
dest_size = (shape_warped[1], shape_warped[0])
center_xy = (width/2, height/2)
max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
coords_i_inv = cls.warpPolarCoords(coords_i_warped,
dest_size, center_xy,
max_radius, flags)
coords_inv.append(coords_i_inv)
return coords_inv
# Added in 0.4.0.
@classmethod
def _warp_cbaois_(cls, cbaois):
if cbaois is None:
return None, None
coords = [cbaoi.to_xy_array() for cbaoi in cbaois]
image_shapes = [cbaoi.shape for cbaoi in cbaois]
image_shapes_warped = cls._warp_shape_tuples(image_shapes)
coords_warped, inv_data = cls._warp_coords(coords, image_shapes)
for i, (cbaoi, coords_i_warped) in enumerate(zip(cbaois,
coords_warped)):
cbaoi = cbaoi.fill_from_xy_array_(coords_i_warped)
cbaoi.shape = image_shapes_warped[i]
cbaois[i] = cbaoi
return cbaois, inv_data
# Added in 0.4.0.
@classmethod
def _invert_warp_cbaois_(cls, cbaois_warped, image_shapes_orig):
if cbaois_warped is None:
return None
coords = [cbaoi.to_xy_array() for cbaoi in cbaois_warped]
image_shapes_after_aug = [cbaoi.shape for cbaoi in cbaois_warped]
coords_warped = cls._invert_warp_coords(coords, image_shapes_after_aug,
image_shapes_orig)
cbaois = cbaois_warped
for i, (cbaoi, coords_i_warped) in enumerate(zip(cbaois,
coords_warped)):
cbaoi = cbaoi.fill_from_xy_array_(coords_i_warped)
cbaoi.shape = image_shapes_orig[i]
cbaois[i] = cbaoi
return cbaois
# Added in 0.4.0.
@classmethod
def _warp_shape_tuples(cls, shapes):
# pylint: disable=invalid-name
pi = np.pi
result = []
for shape in shapes:
if 0 in shape:
result.append(shape)
continue
height, width = shape[0:2]
max_radius = np.sqrt((height/2.0)**2.0 + (width/2.0)**2.0)
# np.round() is here a replacement for cvRound(). It is not fully
# clear whether the two functions behave exactly identical in all
# situations.
# See
# https://github.com/opencv/opencv/blob/master/
# modules/core/include/opencv2/core/fast_math.hpp
# for OpenCV's implementation.
width = int(np.round(max_radius))
height = int(np.round(max_radius * pi))
result.append(tuple([height, width] + list(shape[2:])))
return result
# Added in 0.4.0.
@classmethod
def warpPolarCoords(cls, src, dsize, center, maxRadius, flags):
# See
# https://docs.opencv.org/3.4.8/da/d54/group__imgproc__transform.html
# for the equations
# or also
# https://github.com/opencv/opencv/blob/master/modules/imgproc/src/
# imgwarp.cpp
#
# pylint: disable=invalid-name, no-else-return
assert dsize[0] > 0
assert dsize[1] > 0
dsize_width = dsize[0]
dsize_height = dsize[1]
center_x = center[0]
center_y = center[1]
if np.logical_and(flags, cv2.WARP_INVERSE_MAP):
rho = src[:, 0]
phi = src[:, 1]
Kangle = dsize_height / (2*np.pi)
angleRad = phi / Kangle
if np.bitwise_and(flags, cv2.WARP_POLAR_LOG):
Klog = dsize_width / np.log(maxRadius)
magnitude = np.exp(rho / Klog)
else:
Klin = dsize_width / maxRadius
magnitude = rho / Klin
x = center_x + magnitude * np.cos(angleRad)
y = center_y + magnitude * np.sin(angleRad)
x = x[:, np.newaxis]
y = y[:, np.newaxis]
return np.concatenate([x, y], axis=1)
else:
x = src[:, 0]
y = src[:, 1]
Kangle = dsize_height / (2*np.pi)
Klin = dsize_width / maxRadius
I_x, I_y = (x - center_x, y - center_y)
magnitude_I, angle_I = cv2.cartToPolar(I_x, I_y)
phi = Kangle * angle_I
# TODO add semilog support here
rho = Klin * magnitude_I
return np.concatenate([rho, phi], axis=1)
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return []
# Added in 0.4.0.
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [self.children]
# Added in 0.4.0.
def _to_deterministic(self):
aug = self.copy()
aug.children = aug.children.to_deterministic()
aug.deterministic = True
aug.random_state = self.random_state.derive_rng_()
return aug
# Added in 0.4.0.
def __str__(self):
pattern = (
"%s("
"name=%s, children=%s, deterministic=%s"
")")
return pattern % (self.__class__.__name__, self.name,
self.children, self.deterministic)
class Jigsaw(meta.Augmenter):
"""Move cells within images similar to jigsaw patterns.
.. note::
This augmenter will by default pad images until their height is a
multiple of `nb_rows`. Analogous for `nb_cols`.
.. note::
This augmenter will resize heatmaps and segmentation maps to the
image size, then apply similar padding as for the corresponding images
and resize back to the original map size. That also means that images
may change in shape (due to padding), but heatmaps/segmaps will not
change. For heatmaps/segmaps, this deviates from pad augmenters that
will change images and heatmaps/segmaps in corresponding ways and then
keep the heatmaps/segmaps at the new size.
.. warning::
This augmenter currently only supports augmentation of images,
heatmaps, segmentation maps and keypoints. Other augmentables,
i.e. bounding boxes, polygons and line strings, will result in errors.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.geometric.apply_jigsaw`.
Parameters
----------
nb_rows : int or list of int or tuple of int or imgaug.parameters.StochasticParameter, optional
How many rows the jigsaw pattern should have.
* If a single ``int``, then that value will be used for all images.
* If a tuple ``(a, b)``, then a random value will be uniformly
sampled per image from the discrete interval ``[a..b]``.
* If a list, then for each image a random value will be sampled
from that list.
* If ``StochasticParameter``, then that parameter is queried per
image to sample the value to use.
nb_cols : int or list of int or tuple of int or imgaug.parameters.StochasticParameter, optional
How many cols the jigsaw pattern should have.
* If a single ``int``, then that value will be used for all images.
* If a tuple ``(a, b)``, then a random value will be uniformly
sampled per image from the discrete interval ``[a..b]``.
* If a list, then for each image a random value will be sampled
from that list.
* If ``StochasticParameter``, then that parameter is queried per
image to sample the value to use.
max_steps : int or list of int or tuple of int or imgaug.parameters.StochasticParameter, optional
How many steps each jigsaw cell may be moved.
* If a single ``int``, then that value will be used for all images.
* If a tuple ``(a, b)``, then a random value will be uniformly
sampled per image from the discrete interval ``[a..b]``.
* If a list, then for each image a random value will be sampled
from that list.
* If ``StochasticParameter``, then that parameter is queried per
image to sample the value to use.
allow_pad : bool, optional
Whether to allow automatically padding images until they are evenly
divisible by ``nb_rows`` and ``nb_cols``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Jigsaw(nb_rows=10, nb_cols=10)
Create a jigsaw augmenter that splits images into ``10x10`` cells
and shifts them around by ``0`` to ``2`` steps (default setting).
>>> aug = iaa.Jigsaw(nb_rows=(1, 4), nb_cols=(1, 4))
Create a jigsaw augmenter that splits each image into ``1`` to ``4``
cells along each axis.
>>> aug = iaa.Jigsaw(nb_rows=10, nb_cols=10, max_steps=(1, 5))
Create a jigsaw augmenter that moves the cells in each image by a random
amount between ``1`` and ``5`` times (decided per image). Some images will
be barely changed, some will be fairly distorted.
"""
# Added in 0.4.0.
def __init__(self, nb_rows=(3, 10), nb_cols=(3, 10), max_steps=1,
allow_pad=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Jigsaw, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.nb_rows = iap.handle_discrete_param(
nb_rows, "nb_rows", value_range=(1, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
self.nb_cols = iap.handle_discrete_param(
nb_cols, "nb_cols", value_range=(1, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
self.max_steps = iap.handle_discrete_param(
max_steps, "max_steps", value_range=(0, None),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
self.allow_pad = allow_pad
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
samples = self._draw_samples(batch, random_state)
# We resize here heatmaps/segmaps early to the image size in order to
# avoid problems where the jigsaw cells don't fit perfectly into
# the heatmap/segmap arrays or there are minor padding-related
# differences.
# TODO This step could most likely be avoided.
# TODO add something like
# 'with batch.maps_resized_to_image_sizes(): ...'
batch, maps_shapes_orig = self._resize_maps(batch)
if self.allow_pad:
# this is a bit more difficult than one might expect, because we
# (a) might have different numbers of rows/cols per image
# (b) might have different shapes per image
# (c) have non-image data that also requires padding
# TODO enable support for stochastic parameters in
# PadToMultiplesOf, then we can simple use two
# DeterministicLists here to generate rowwise values
for i in np.arange(len(samples.destinations)):
padder = size_lib.CenterPadToMultiplesOf(
width_multiple=samples.nb_cols[i],
height_multiple=samples.nb_rows[i],
seed=random_state
)
row = batch.subselect_rows_by_indices([i])
row = padder.augment_batch_(row, parents=parents + [self],
hooks=hooks)
batch = batch.invert_subselect_rows_by_indices_([i], row)
if batch.images is not None:
for i, image in enumerate(batch.images):
image[...] = apply_jigsaw(image, samples.destinations[i])
if batch.heatmaps is not None:
for i, heatmap in enumerate(batch.heatmaps):
heatmap.arr_0to1 = apply_jigsaw(heatmap.arr_0to1,
samples.destinations[i])
if batch.segmentation_maps is not None:
for i, segmap in enumerate(batch.segmentation_maps):
segmap.arr = apply_jigsaw(segmap.arr, samples.destinations[i])
if batch.keypoints is not None:
for i, kpsoi in enumerate(batch.keypoints):
xy = kpsoi.to_xy_array()
xy[...] = apply_jigsaw_to_coords(xy,
samples.destinations[i],
image_shape=kpsoi.shape)
kpsoi.fill_from_xy_array_(xy)
has_other_cbaoi = any([getattr(batch, attr_name) is not None
for attr_name
in ["bounding_boxes", "polygons",
"line_strings"]])
if has_other_cbaoi:
raise NotImplementedError(
"Jigsaw currently only supports augmentation of images, "
"heatmaps, segmentation maps and keypoints. "
"Explicitly not supported are: bounding boxes, polygons "
"and line strings.")
# We don't crop back to the original size, partly because it is
# rather cumbersome to implement, partly because the padded
# borders might have been moved into the inner parts of the image
batch = self._invert_resize_maps(batch, maps_shapes_orig)
return batch
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
nb_images = batch.nb_rows
nb_rows = self.nb_rows.draw_samples((nb_images,),
random_state=random_state)
nb_cols = self.nb_cols.draw_samples((nb_images,),
random_state=random_state)
max_steps = self.max_steps.draw_samples((nb_images,),
random_state=random_state)
destinations = []
for i in np.arange(nb_images):
destinations.append(
generate_jigsaw_destinations(
nb_rows[i], nb_cols[i], max_steps[i],
seed=random_state)
)
samples = _JigsawSamples(nb_rows, nb_cols, max_steps, destinations)
return samples
# Added in 0.4.0.
@classmethod
def _resize_maps(cls, batch):
# skip computation of rowwise shapes
if batch.heatmaps is None and batch.segmentation_maps is None:
return batch, (None, None)
image_shapes = batch.get_rowwise_shapes()
batch.heatmaps, heatmaps_shapes_orig = cls._resize_maps_single_list(
batch.heatmaps, "arr_0to1", image_shapes)
batch.segmentation_maps, sm_shapes_orig = cls._resize_maps_single_list(
batch.segmentation_maps, "arr", image_shapes)
return batch, (heatmaps_shapes_orig, sm_shapes_orig)
# Added in 0.4.0.
@classmethod
def _resize_maps_single_list(cls, augmentables, arr_attr_name,
image_shapes):
if augmentables is None:
return None, None
shapes_orig = []
augms_resized = []
for augmentable, image_shape in zip(augmentables, image_shapes):
shape_orig = getattr(augmentable, arr_attr_name).shape
augm_rs = augmentable.resize(image_shape[0:2])
augms_resized.append(augm_rs)
shapes_orig.append(shape_orig)
return augms_resized, shapes_orig
# Added in 0.4.0.
@classmethod
def _invert_resize_maps(cls, batch, shapes_orig):
batch.heatmaps = cls._invert_resize_maps_single_list(
batch.heatmaps, shapes_orig[0])
batch.segmentation_maps = cls._invert_resize_maps_single_list(
batch.segmentation_maps, shapes_orig[1])
return batch
# Added in 0.4.0.
@classmethod
def _invert_resize_maps_single_list(cls, augmentables, shapes_orig):
if shapes_orig is None:
return None
augms_resized = []
for augmentable, shape_orig in zip(augmentables, shapes_orig):
augms_resized.append(augmentable.resize(shape_orig[0:2]))
return augms_resized
# Added in 0.4.0.
def get_parameters(self):
return [self.nb_rows, self.nb_cols, self.max_steps, self.allow_pad]
# Added in 0.4.0.
class _JigsawSamples(object):
# Added in 0.4.0.
def __init__(self, nb_rows, nb_cols, max_steps, destinations):
self.nb_rows = nb_rows
self.nb_cols = nb_cols
self.max_steps = max_steps
self.destinations = destinations
================================================
FILE: imgaug/augmenters/imgcorruptlike.py
================================================
"""
Augmenters that wrap methods from ``imagecorruptions`` package.
See `https://github.com/bethgelab/imagecorruptions`_ for the package.
The package is derived from `https://github.com/hendrycks/robustness`_.
The corresponding `paper `_ is::
Hendrycks, Dan and Dietterich, Thomas G.
Benchmarking Neural Network Robustness to Common Corruptions and
Surface Variations
with the `newer version `_ being::
Hendrycks, Dan and Dietterich, Thomas G.
Benchmarking Neural Network Robustness to Common Corruptions and
Perturbations
List of augmenters:
* :class:`GaussianNoise`
* :class:`ShotNoise`
* :class:`ImpulseNoise`
* :class:`SpeckleNoise`
* :class:`GaussianBlur`
* :class:`GlassBlur`
* :class:`DefocusBlur`
* :class:`MotionBlur`
* :class:`ZoomBlur`
* :class:`Fog`
* :class:`Frost`
* :class:`Snow`
* :class:`Spatter`
* :class:`Contrast`
* :class:`Brightness`
* :class:`Saturate`
* :class:`JpegCompression`
* :class:`Pixelate`
* :class:`ElasticTransform`
.. note::
The functions provided here have identical outputs to the ones in
``imagecorruptions`` when called using the ``corrupt()`` function of
that package. E.g. the outputs are always ``uint8`` and not
``float32`` or ``float64``.
Example usage::
>>> # Skip the doctests in this file as the imagecorruptions package is
>>> # not available in all python versions that are otherwise supported
>>> # by imgaug.
>>> # doctest: +SKIP
>>> import imgaug as ia
>>> import imgaug.augmenters as iaa
>>> import numpy as np
>>> image = np.zeros((64, 64, 3), dtype=np.uint8)
>>> names, funcs = iaa.imgcorruptlike.get_corruption_names("validation")
>>> for name, func in zip(names, funcs):
>>> image_aug = func(image, severity=5, seed=1)
>>> image_aug = ia.draw_text(image_aug, x=20, y=20, text=name)
>>> ia.imshow(image_aug)
Use e.g. ``iaa.imgcorruptlike.GaussianNoise(severity=2)(images=...)`` to
create and apply a specific augmenter.
Added in 0.4.0.
"""
from __future__ import print_function, division, absolute_import
import warnings
import six.moves as sm
import numpy as np
import skimage.filters
import imgaug as ia
from ..imgaug import _numbajit
from .. import dtypes as iadt
from .. import random as iarandom
from .. import parameters as iap
from . import meta
# TODO add optional dependency
_MISSING_PACKAGE_ERROR_MSG = (
"Could not import package `imagecorruptions`. This is an optional "
"dependency of imgaug and must be installed manually in order "
"to use augmenters from `imgaug.augmenters.imgcorrupt`. "
"Use e.g. `pip install imagecorruptions` to install it. See also "
"https://github.com/bethgelab/imagecorruptions for the repository "
"of the package."
)
# Added in 0.4.0.
def _clipped_zoom_no_scipy_warning(img, zoom_factor):
from scipy.ndimage import zoom as scizoom
with warnings.catch_warnings():
warnings.filterwarnings("ignore", ".*output shape of zoom.*")
# clipping along the width dimension:
ch0 = int(np.ceil(img.shape[0] / float(zoom_factor)))
top0 = (img.shape[0] - ch0) // 2
# clipping along the height dimension:
ch1 = int(np.ceil(img.shape[1] / float(zoom_factor)))
top1 = (img.shape[1] - ch1) // 2
img = scizoom(img[top0:top0 + ch0, top1:top1 + ch1],
(zoom_factor, zoom_factor, 1), order=1)
return img
def _call_imgcorrupt_func(fname, seed, convert_to_pil, *args, **kwargs):
"""Apply an ``imagecorruptions`` function.
The dtype support below is basically a placeholder to which the
augmentation functions can point to decrease the amount of documentation.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; indirectly tested (1)
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
- (1) Tested by comparison with function in ``imagecorruptions``
package.
"""
# import imagecorruptions, note that it is an optional dependency
try:
# imagecorruptions sets its own warnings filter rule via
# warnings.simplefilter(). That rule is the in effect for the whole
# program and not just the module. So to prevent that here
# we use catch_warnings(), which uintuitively does not by default
# catch warnings but saves and restores the warnings filter settings.
with warnings.catch_warnings():
import imagecorruptions.corruptions as corruptions
except ImportError:
raise ImportError(_MISSING_PACKAGE_ERROR_MSG)
# Monkeypatch clip_zoom() as that causes warnings in some scipy versions,
# and the implementation here suppresses these warnings. They suppress
# all UserWarnings on a module level instead, which seems very exhaustive.
corruptions.clipped_zoom = _clipped_zoom_no_scipy_warning
image = args[0]
iadt.allow_only_uint8({image.dtype})
input_shape = image.shape
height, width = input_shape[0:2]
assert height >= 32 and width >= 32, (
"Expected the provided image to have a width and height of at least "
"32 pixels, as that is the lower limit that the wrapped "
"imagecorruptions functions use. Got shape %s." % (image.shape,))
ndim = image.ndim
assert ndim == 2 or (ndim == 3 and (image.shape[2] in [1, 3])), (
"Expected input image to have shape (height, width) or "
"(height, width, 1) or (height, width, 3). Got shape %s." % (
image.shape,))
if ndim == 2:
image = image[..., np.newaxis]
if image.shape[-1] == 1:
image = np.tile(image, (1, 1, 3))
if convert_to_pil:
import PIL.Image
image = PIL.Image.fromarray(image)
with iarandom.temporary_numpy_seed(seed):
if ia.is_callable(fname):
image_aug = fname(image, *args[1:], **kwargs)
else:
image_aug = getattr(corruptions, fname)(image, *args[1:], **kwargs)
if convert_to_pil:
image_aug = np.asarray(image_aug)
if ndim == 2:
image_aug = image_aug[:, :, 0]
elif input_shape[-1] == 1:
image_aug = image_aug[:, :, 0:1]
# this cast is done at the end of imagecorruptions.__init__.corrupt()
image_aug = np.uint8(image_aug)
return image_aug
def get_corruption_names(subset="common"):
"""Get a named subset of image corruption functions.
.. note::
This function returns the augmentation names (as strings) *and* the
corresponding augmentation functions, while ``get_corruption_names()``
in ``imagecorruptions`` only returns the augmentation names.
Added in 0.4.0.
Parameters
----------
subset : {'common', 'validation', 'all'}, optional.
Name of the subset of image corruption functions.
Returns
-------
list of str
Names of the corruption methods, e.g. "gaussian_noise".
list of callable
Function corresponding to the name. Is one of the
``apply_*()`` functions in this module. Apply e.g.
via ``func(image, severity=2, seed=123)``.
"""
# import imagecorruptions, note that it is an optional dependency
try:
# imagecorruptions sets its own warnings filter rule via
# warnings.simplefilter(). That rule is the in effect for the whole
# program and not just the module. So to prevent that here
# we use catch_warnings(), which uintuitively does not by default
# catch warnings but saves and restores the warnings filter settings.
with warnings.catch_warnings():
import imagecorruptions
except ImportError:
raise ImportError(_MISSING_PACKAGE_ERROR_MSG)
cnames = imagecorruptions.get_corruption_names(subset)
funcs = [globals()["apply_%s" % (cname,)] for cname in cnames]
return cnames, funcs
# ----------------------------------------------------------------------------
# Corruption functions
# ----------------------------------------------------------------------------
# These functions could easily be created dynamically, especially templating
# the docstrings would save many lines of code. It is intentionally not done
# here for the same reasons as in case of the augmenters. See the comment
# further below at the start of the augmenter section for details.
def apply_gaussian_noise(x, severity=1, seed=None):
"""Apply ``gaussian_noise`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("gaussian_noise", seed, False, x, severity)
def apply_shot_noise(x, severity=1, seed=None):
"""Apply ``shot_noise`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("shot_noise", seed, False, x, severity)
def apply_impulse_noise(x, severity=1, seed=None):
"""Apply ``impulse_noise`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("impulse_noise", seed, False, x, severity)
def apply_speckle_noise(x, severity=1, seed=None):
"""Apply ``speckle_noise`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("speckle_noise", seed, False, x, severity)
def apply_gaussian_blur(x, severity=1, seed=None):
"""Apply ``gaussian_blur`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("gaussian_blur", seed, False, x, severity)
def apply_glass_blur(x, severity=1, seed=None):
"""Apply ``glass_blur`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func(_apply_glass_blur_imgaug, seed, False, x,
severity)
# Added in 0.4.0.
def _apply_glass_blur_imgaug(x, severity=1):
# false positive on x_shape[0]
# invalid name for dx, dy
# pylint: disable=unsubscriptable-object, invalid-name
# original function implementation from
# https://github.com/bethgelab/imagecorruptions/blob/master/imagecorruptions/corruptions.py
# this is an improved (i.e. faster) version
# sigma, max_delta, iterations
c = [
(0.7, 1, 2),
(0.9, 2, 1),
(1, 2, 3),
(1.1, 3, 2),
(1.5, 4, 2)
][severity - 1]
sigma, max_delta, iterations = c
x = (
skimage.filters.gaussian(
np.array(x) / 255., sigma=sigma, multichannel=True
) * 255
).astype(np.uint)
x_shape = x.shape
dxxdyy = np.random.randint(
-max_delta,
max_delta,
size=(
iterations,
x_shape[0] - 2*max_delta,
x_shape[1] - 2*max_delta,
2
)
)
x = _apply_glass_blur_imgaug_loop(
x, iterations, max_delta, dxxdyy
)
return np.clip(
skimage.filters.gaussian(x / 255., sigma=sigma, multichannel=True),
0, 1
) * 255
# Added in 0.5.0.
@_numbajit(nopython=True, nogil=True, cache=True)
def _apply_glass_blur_imgaug_loop(
x, iterations, max_delta, dxxdyy
):
x_shape = x.shape
nb_height = x_shape[0] - 2 * max_delta
nb_width = x_shape[1] - 2 * max_delta
# locally shuffle pixels
for i in sm.xrange(iterations):
for j in sm.xrange(nb_height):
for k in sm.xrange(nb_width):
h = x_shape[0] - max_delta - j
w = x_shape[1] - max_delta - k
dx, dy = dxxdyy[i, j, k]
h_prime, w_prime = h + dy, w + dx
# swap
x[h, w], x[h_prime, w_prime] = x[h_prime, w_prime], x[h, w]
return x
def apply_defocus_blur(x, severity=1, seed=None):
"""Apply ``defocus_blur`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("defocus_blur", seed, False, x, severity)
def apply_motion_blur(x, severity=1, seed=None):
"""Apply ``motion_blur`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("motion_blur", seed, False, x, severity)
def apply_zoom_blur(x, severity=1, seed=None):
"""Apply ``zoom_blur`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("zoom_blur", seed, False, x, severity)
def apply_fog(x, severity=1, seed=None):
"""Apply ``fog`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("fog", seed, False, x, severity)
def apply_frost(x, severity=1, seed=None):
"""Apply ``frost`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("frost", seed, False, x, severity)
def apply_snow(x, severity=1, seed=None):
"""Apply ``snow`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("snow", seed, False, x, severity)
def apply_spatter(x, severity=1, seed=None):
"""Apply ``spatter`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("spatter", seed, True, x, severity)
def apply_contrast(x, severity=1, seed=None):
"""Apply ``contrast`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("contrast", seed, False, x, severity)
def apply_brightness(x, severity=1, seed=None):
"""Apply ``brightness`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("brightness", seed, False, x, severity)
def apply_saturate(x, severity=1, seed=None):
"""Apply ``saturate`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("saturate", seed, False, x, severity)
def apply_jpeg_compression(x, severity=1, seed=None):
"""Apply ``jpeg_compression`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("jpeg_compression", seed, True, x, severity)
def apply_pixelate(x, severity=1, seed=None):
"""Apply ``pixelate`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
x : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("pixelate", seed, True, x, severity)
def apply_elastic_transform(image, severity=1, seed=None):
"""Apply ``elastic_transform`` from ``imagecorruptions``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike._call_imgcorrupt_func`.
Parameters
----------
image : ndarray
Image array.
Expected to have shape ``(H,W)``, ``(H,W,1)`` or ``(H,W,3)`` with
dtype ``uint8`` and a minimum height/width of ``32``.
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int, optional
Seed for the random number generation to use.
Returns
-------
ndarray
Corrupted image.
"""
return _call_imgcorrupt_func("elastic_transform", seed, False, image,
severity)
# ----------------------------------------------------------------------------
# Augmenters
# ----------------------------------------------------------------------------
# The augmenter definitions below are almost identical and mainly differ in
# the names and functions used. It would be fairly trivial to write a
# function that would create these augmenters dynamically (and one is listed
# below as a comment). The downside is that in these cases the documentation
# would also be generated dynamically, which leads to numerous problems:
# (1) users couldn't easily read the documentation while scrolling through
# the code file, (2) IDEs might not be able to use it for code suggestions,
# (3) tools like pylint can't detect and validate it, (4) the imgaug-doc
# tools to parse dtype support don't work with dynamically generated
# documentation (and neither with dynamically generated classes).
# Even though it's by far more code, it seems like the better choice overall
# to just write it out.
# Example function to dynamically generate augmenters, kept for possible
# future uses:
# def _create_augmenter(class_name, func_name):
# func = globals()["apply_%s" % (func_name,)]
#
# def __init__(self, severity=1, name=None, deterministic=False,
# random_state=None):
# super(self.__class__, self).__init__(
# func, severity, name=name, deterministic=deterministic,
# random_state=random_state)
#
# augmenter_class = type(class_name,
# (_ImgcorruptAugmenterBase,),
# {"__init__": __init__})
#
# augmenter_class.__doc__ = """
# Wrapper around ``imagecorruptions.corruptions.%s``.
#
# **Supported dtypes**:
#
# See :func:`~imgaug.augmenters.imgcorruptlike.apply_%s`.
#
# Parameters
# ----------
# severity : int, optional
# Strength of the corruption, with valid values being
# ``1 <= severity <= 5``.
#
# name : None or str, optional
# See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
#
# deterministic : bool, optional
# See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
#
# random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
# See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
#
# Examples
# --------
# >>> import imgaug.augmenters as iaa
# >>> aug = iaa.%s(severity=2)
#
# Create an augmenter around ``imagecorruptions.corruptions.%s``. Apply it to
# images using e.g. ``aug(images=[image1, image2, ...])``.
#
# """ % (func_name, func_name, class_name, func_name)
#
# return augmenter_class
# Added in 0.4.0.
class _ImgcorruptAugmenterBase(meta.Augmenter):
def __init__(self, func, severity=1,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(_ImgcorruptAugmenterBase, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.func = func
self.severity = iap.handle_discrete_param(
severity, "severity", value_range=(1, 5), tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
severities, seeds = self._draw_samples(len(batch.images),
random_state=random_state)
for image, severity, seed in zip(batch.images, severities, seeds):
image[...] = self.func(image, severity=severity, seed=seed)
return batch
# Added in 0.4.0.
def _draw_samples(self, nb_rows, random_state):
severities = self.severity.draw_samples((nb_rows,),
random_state=random_state)
seeds = random_state.generate_seeds_(nb_rows)
return severities, seeds
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.severity]
class GaussianNoise(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.gaussian_noise``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_gaussian_noise`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.GaussianNoise(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.gaussian_noise``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(GaussianNoise, self).__init__(
apply_gaussian_noise, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ShotNoise(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.shot_noise``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_shot_noise`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.ShotNoise(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.shot_noise``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ShotNoise, self).__init__(
apply_shot_noise, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ImpulseNoise(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.impulse_noise``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_impulse_noise`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.ImpulseNoise(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.impulse_noise``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ImpulseNoise, self).__init__(
apply_impulse_noise, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class SpeckleNoise(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.speckle_noise``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_speckle_noise`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.SpeckleNoise(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.speckle_noise``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(SpeckleNoise, self).__init__(
apply_speckle_noise, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class GaussianBlur(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.gaussian_blur``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_gaussian_blur`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.GaussianBlur(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.gaussian_blur``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(GaussianBlur, self).__init__(
apply_gaussian_blur, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class GlassBlur(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.glass_blur``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_glass_blur`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.GlassBlur(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.glass_blur``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(GlassBlur, self).__init__(
apply_glass_blur, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class DefocusBlur(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.defocus_blur``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_defocus_blur`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.DefocusBlur(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.defocus_blur``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(DefocusBlur, self).__init__(
apply_defocus_blur, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class MotionBlur(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.motion_blur``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_motion_blur`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.MotionBlur(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.motion_blur``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MotionBlur, self).__init__(
apply_motion_blur, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ZoomBlur(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.zoom_blur``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_zoom_blur`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.ZoomBlur(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.zoom_blur``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ZoomBlur, self).__init__(
apply_zoom_blur, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Fog(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.fog``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_fog`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.Fog(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.fog``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Fog, self).__init__(
apply_fog, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Frost(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.frost``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_frost`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.Frost(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.frost``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Frost, self).__init__(
apply_frost, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Snow(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.snow``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_snow`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.Snow(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.snow``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Snow, self).__init__(
apply_snow, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Spatter(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.spatter``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_spatter`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.Spatter(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.spatter``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Spatter, self).__init__(
apply_spatter, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Contrast(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.contrast``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_contrast`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.Contrast(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.contrast``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Contrast, self).__init__(
apply_contrast, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Brightness(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.brightness``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_brightness`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.Brightness(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.brightness``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Brightness, self).__init__(
apply_brightness, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Saturate(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.saturate``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_saturate`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.Saturate(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.saturate``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Saturate, self).__init__(
apply_saturate, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class JpegCompression(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.jpeg_compression``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_jpeg_compression`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.JpegCompression(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.jpeg_compression``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(JpegCompression, self).__init__(
apply_jpeg_compression, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Pixelate(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.pixelate``.
.. note::
This augmenter only affects images. Other data is not changed.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_pixelate`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.Pixelate(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.pixelate``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Pixelate, self).__init__(
apply_pixelate, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class ElasticTransform(_ImgcorruptAugmenterBase):
"""
Wrapper around ``imagecorruptions.corruptions.elastic_transform``.
.. warning::
This augmenter can currently only transform image-data.
Batches containing heatmaps, segmentation maps and
coordinate-based augmentables will be rejected with an error.
Use :class:`~imgaug.augmenters.geometric.ElasticTransformation` if
you have to transform such inputs.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.imgcorruptlike.apply_elastic_transform`.
Parameters
----------
severity : int, optional
Strength of the corruption, with valid values being
``1 <= severity <= 5``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> # doctest: +SKIP
>>> import imgaug.augmenters as iaa
>>> aug = iaa.imgcorruptlike.ElasticTransform(severity=2)
Create an augmenter around
``imagecorruptions.corruptions.elastic_transform``.
Apply it to images using e.g. ``aug(images=[image1, image2, ...])``.
"""
# Added in 0.4.0.
def __init__(self, severity=(1, 5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ElasticTransform, self).__init__(
apply_elastic_transform, severity,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
cols = batch.get_column_names()
assert len(cols) == 0 or (len(cols) == 1 and "images" in cols), (
"imgcorruptlike.ElasticTransform can currently only process image "
"data. Got a batch containing: %s. Use "
"imgaug.augmenters.geometric.ElasticTransformation for "
"batches containing non-image data." % (", ".join(cols),))
return super(ElasticTransform, self)._augment_batch_(
batch, random_state, parents, hooks)
================================================
FILE: imgaug/augmenters/meta.py
================================================
"""
Augmenters that don't apply augmentations themselves, but are needed
for meta usage.
List of augmenters:
* :class:`Augmenter` (base class for all augmenters)
* :class:`Sequential`
* :class:`SomeOf`
* :class:`OneOf`
* :class:`Sometimes`
* :class:`WithChannels`
* :class:`Identity`
* :class:`Noop`
* :class:`Lambda`
* :class:`AssertLambda`
* :class:`AssertShape`
* :class:`ChannelShuffle`
Note: :class:`~imgaug.augmenters.color.WithColorspace` is in ``color.py``.
"""
from __future__ import print_function, division, absolute_import
from abc import ABCMeta, abstractmethod
import copy as copy_module
import re
import itertools
import functools
import sys
import numpy as np
import six
import six.moves as sm
import imgaug as ia
from imgaug.augmentables.batches import (Batch, UnnormalizedBatch,
_BatchInAugmentation)
from .. import parameters as iap
from .. import random as iarandom
from . import base as iabase
@ia.deprecated("imgaug.dtypes.clip_")
def clip_augmented_image_(image, min_value, max_value):
"""Clip image in-place."""
return clip_augmented_images_(image, min_value, max_value)
@ia.deprecated("imgaug.dtypes.clip_")
def clip_augmented_image(image, min_value, max_value):
"""Clip image."""
return clip_augmented_images(image, min_value, max_value)
@ia.deprecated("imgaug.dtypes.clip_")
def clip_augmented_images_(images, min_value, max_value):
"""Clip images in-place."""
if ia.is_np_array(images):
return np.clip(images, min_value, max_value, out=images)
return [np.clip(image, min_value, max_value, out=image)
for image in images]
@ia.deprecated("imgaug.dtypes.clip_")
def clip_augmented_images(images, min_value, max_value):
"""Clip images."""
if ia.is_np_array(images):
images = np.copy(images)
else:
images = [np.copy(image) for image in images]
return clip_augmented_images_(images, min_value, max_value)
def handle_children_list(lst, augmenter_name, lst_name, default="sequential"):
"""Normalize an augmenter list provided by a user."""
if lst is None:
if default == "sequential":
return Sequential([], name="%s-%s" % (augmenter_name, lst_name))
return default
if isinstance(lst, Augmenter):
if ia.is_iterable(lst):
# TODO why was this assert added here? seems to make no sense
only_augmenters = all([isinstance(child, Augmenter)
for child in lst])
assert only_augmenters, (
"Expected all children to be augmenters, got types %s." % (
", ".join([str(type(v)) for v in lst])))
return lst
return Sequential(lst, name="%s-%s" % (augmenter_name, lst_name))
if ia.is_iterable(lst):
if len(lst) == 0 and default != "sequential":
return default
only_augmenters = all([isinstance(child, Augmenter)
for child in lst])
assert only_augmenters, (
"Expected all children to be augmenters, got types %s." % (
", ".join([str(type(v)) for v in lst])))
return Sequential(lst, name="%s-%s" % (augmenter_name, lst_name))
raise Exception(
"Expected None, Augmenter or list/tuple as children list %s "
"for augmenter with name %s, got %s." % (
lst_name, augmenter_name, type(lst),))
def reduce_to_nonempty(objs):
"""Remove from a list all objects that don't follow ``obj.empty==True``."""
objs_reduced = []
ids = []
for i, obj in enumerate(objs):
assert hasattr(obj, "empty"), (
"Expected object with property 'empty'. Got type %s." % (
type(obj),))
if not obj.empty:
objs_reduced.append(obj)
ids.append(i)
return objs_reduced, ids
def invert_reduce_to_nonempty(objs, ids, objs_reduced):
"""Inverse of :func:`reduce_to_nonempty`."""
objs_inv = list(objs)
for idx, obj_from_reduced in zip(ids, objs_reduced):
objs_inv[idx] = obj_from_reduced
return objs_inv
def estimate_max_number_of_channels(images):
"""Compute the maximum number of image channels among a list of images."""
if ia.is_np_array(images):
assert images.ndim == 4, (
"Expected 'images' to be 4-dimensional if provided as array. "
"Got %d dimensions." % (images.ndim,))
return images.shape[3]
assert ia.is_iterable(images), (
"Expected 'images' to be an array or iterable, got %s." % (
type(images),))
if len(images) == 0:
return None
channels = [el.shape[2] if len(el.shape) >= 3 else 1 for el in images]
return max(channels)
def copy_arrays(arrays):
"""Copy the arrays of a single input array or list of input arrays."""
if ia.is_np_array(arrays):
return np.copy(arrays)
return [np.copy(array) for array in arrays]
def _add_channel_axis(arrs):
if ia.is_np_array(arrs):
if arrs.ndim == 3: # (N,H,W)
return arrs[..., np.newaxis] # (N,H,W) -> (N,H,W,1)
return arrs
return [
arr[..., np.newaxis] # (H,W) -> (H,W,1)
if arr.ndim == 2
else arr
for arr in arrs
]
def _remove_added_channel_axis(arrs_added, arrs_orig):
if ia.is_np_array(arrs_orig):
if arrs_orig.ndim == 3: # (N,H,W)
if ia.is_np_array(arrs_added):
return arrs_added[..., 0] # (N,H,W,1) -> (N,H,W)
# (N,H,W) -> (N,H,W,1) -> -> list of (H,W,1)
return [arr[..., 0] for arr in arrs_added]
return arrs_added
return [
arr_added[..., 0]
if arr_orig.ndim == 2
else arr_added # (H,W,1) -> (H,W)
for arr_added, arr_orig
in zip(arrs_added, arrs_orig)
]
class _maybe_deterministic_ctx(object): # pylint: disable=invalid-name
"""Context that resets an RNG to its initial state upon exit.
This allows to execute some sampling functions and leave the code block
with the used RNG in the same state as before.
Parameters
----------
random_state : imgaug.random.RNG or imgaug.augmenters.meta.Augmenter
The RNG to reset. If this is an augmenter, then the augmenter's
RNG will be used.
deterministic : None or bool
Whether to reset the RNG upon exit (``True``) or not (``False``).
Allowed to be ``None`` iff `random_state` was an augmenter, in which
case that augmenter's ``deterministic`` attribute will be used.
"""
def __init__(self, random_state, deterministic=None):
if deterministic is None:
augmenter = random_state
self.random_state = augmenter.random_state
self.deterministic = augmenter.deterministic
else:
assert deterministic is not None, (
"Expected boolean as `deterministic`, got None.")
self.random_state = random_state
self.deterministic = deterministic
self.old_state = None
def __enter__(self):
if self.deterministic:
self.old_state = self.random_state.state
def __exit__(self, exception_type, exception_value, exception_traceback):
if self.old_state is not None:
self.random_state.state = self.old_state
@six.add_metaclass(ABCMeta)
class Augmenter(object):
"""
Base class for Augmenter objects.
All augmenters derive from this class.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Seed to use for this augmenter's random number generator (RNG) or
alternatively an RNG itself. Setting this parameter allows to
control/influence the random number sampling of this specific
augmenter without affecting other augmenters. Usually, there is no
need to set this parameter.
* If ``None``: The global RNG is used (shared by all
augmenters).
* If ``int``: The value will be used as a seed for a new
:class:`~imgaug.random.RNG` instance.
* If :class:`~imgaug.random.RNG`: The ``RNG`` instance will be
used without changes.
* If :class:`~imgaug.random.Generator`: A new
:class:`~imgaug.random.RNG` instance will be
created, containing that generator.
* If :class:`~imgaug.random.bit_generator.BitGenerator`: Will
be wrapped in a :class:`~imgaug.random.Generator`. Then
similar behaviour to :class:`~imgaug.random.Generator`
parameters.
* If :class:`~imgaug.random.SeedSequence`: Will
be wrapped in a new bit generator and
:class:`~imgaug.random.Generator`. Then
similar behaviour to :class:`~imgaug.random.Generator`
parameters.
* If :class:`~imgaug.random.RandomState`: Similar behaviour to
:class:`~imgaug.random.Generator`. Outdated in numpy 1.17+.
If a new bit generator has to be created, it will be an instance
of :class:`numpy.random.SFC64`.
Added in 0.4.0.
name : None or str, optional
Name given to the Augmenter instance. This name is used when
converting the instance to a string, e.g. for ``print`` statements.
It is also used for ``find``, ``remove`` or similar operations
on augmenters with children.
If ``None``, ``UnnamedX`` will be used as the name, where ``X``
is the Augmenter's class name.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
"""
def __init__(self, seed=None, name=None,
random_state="deprecated",
deterministic="deprecated"):
"""Create a new Augmenter instance."""
super(Augmenter, self).__init__()
assert name is None or ia.is_string(name), (
"Expected name to be None or string-like, got %s." % (
type(name),))
if name is None:
self.name = "Unnamed%s" % (self.__class__.__name__,)
else:
self.name = name
if deterministic != "deprecated":
ia.warn_deprecated(
"The parameter `deterministic` is deprecated "
"in `imgaug.augmenters.meta.Augmenter`. Use "
"`.to_deterministic()` to switch into deterministic mode.",
stacklevel=4)
assert ia.is_single_bool(deterministic), (
"Expected deterministic to be a boolean, got %s." % (
type(deterministic),))
else:
deterministic = False
self.deterministic = deterministic
if random_state != "deprecated":
assert seed is None, "Cannot set both `seed` and `random_state`."
seed = random_state
if deterministic and seed is None:
# Usually if None is provided, the global RNG will be used.
# In case of deterministic mode we most likely rather want a local
# RNG, which is here created.
self.random_state = iarandom.RNG.create_pseudo_random_()
else:
# self.random_state = iarandom.normalize_rng_(random_state)
self.random_state = iarandom.RNG.create_if_not_rng_(seed)
self.activated = True
def augment_batches(self, batches, hooks=None, background=False):
"""Augment multiple batches.
In contrast to other ``augment_*`` method, this one **yields**
batches instead of returning a full list. This is more suited
for most training loops.
This method also also supports augmentation on multiple cpu cores,
activated via the `background` flag. If the `background` flag
is activated, an instance of :class:`~imgaug.multicore.Pool` will
be spawned using all available logical CPU cores and an
``output_buffer_size`` of ``C*10``, where ``C`` is the number of
logical CPU cores. I.e. a maximum of ``C*10`` batches will be somewhere
in the augmentation pipeline (or waiting to be retrieved by downstream
functions) before this method temporarily stops the loading of new
batches from `batches`.
Parameters
----------
batches : imgaug.augmentables.batches.Batch or imgaug.augmentables.batches.UnnormalizedBatch or iterable of imgaug.augmentables.batches.Batch or iterable of imgaug.augmentables.batches.UnnormalizedBatch
A single batch or a list of batches to augment.
hooks : None or imgaug.HooksImages, optional
HooksImages object to dynamically interfere with the augmentation
process.
background : bool, optional
Whether to augment the batches in background processes.
If ``True``, hooks can currently not be used as that would require
pickling functions.
Note that multicore augmentation distributes the batches onto
different CPU cores. It does *not* split the data *within* batches.
It is therefore *not* sensible to use ``background=True`` to
augment a single batch. Only use it for multiple batches.
Note also that multicore augmentation needs some time to start. It
is therefore not recommended to use it for very few batches.
Yields
-------
imgaug.augmentables.batches.Batch or imgaug.augmentables.batches.UnnormalizedBatch or iterable of imgaug.augmentables.batches.Batch or iterable of imgaug.augmentables.batches.UnnormalizedBatch
Augmented batches.
"""
if isinstance(batches, (Batch, UnnormalizedBatch)):
batches = [batches]
assert (
(ia.is_iterable(batches)
and not ia.is_np_array(batches)
and not ia.is_string(batches))
or ia.is_generator(batches)), (
"Expected either (a) an iterable that is not an array or a "
"string or (b) a generator. Got: %s" % (type(batches),))
if background:
assert hooks is None, (
"Hooks can not be used when background augmentation is "
"activated.")
def _normalize_batch(idx, batch):
if isinstance(batch, Batch):
batch_copy = batch.deepcopy()
batch_copy.data = (idx, batch_copy.data)
batch_normalized = batch_copy
batch_orig_dt = "imgaug.Batch"
elif isinstance(batch, UnnormalizedBatch):
batch_copy = batch.to_normalized_batch()
batch_copy.data = (idx, batch_copy.data)
batch_normalized = batch_copy
batch_orig_dt = "imgaug.UnnormalizedBatch"
elif ia.is_np_array(batch):
assert batch.ndim in (3, 4), (
"Expected numpy array to have shape (N, H, W) or "
"(N, H, W, C), got %s." % (batch.shape,))
batch_normalized = Batch(images=batch, data=(idx,))
batch_orig_dt = "numpy_array"
elif isinstance(batch, list):
if len(batch) == 0:
batch_normalized = Batch(data=(idx,))
batch_orig_dt = "empty_list"
elif ia.is_np_array(batch[0]):
batch_normalized = Batch(images=batch, data=(idx,))
batch_orig_dt = "list_of_numpy_arrays"
elif isinstance(batch[0], ia.HeatmapsOnImage):
batch_normalized = Batch(heatmaps=batch, data=(idx,))
batch_orig_dt = "list_of_imgaug.HeatmapsOnImage"
elif isinstance(batch[0], ia.SegmentationMapsOnImage):
batch_normalized = Batch(segmentation_maps=batch,
data=(idx,))
batch_orig_dt = "list_of_imgaug.SegmentationMapsOnImage"
elif isinstance(batch[0], ia.KeypointsOnImage):
batch_normalized = Batch(keypoints=batch, data=(idx,))
batch_orig_dt = "list_of_imgaug.KeypointsOnImage"
elif isinstance(batch[0], ia.BoundingBoxesOnImage):
batch_normalized = Batch(bounding_boxes=batch, data=(idx,))
batch_orig_dt = "list_of_imgaug.BoundingBoxesOnImage"
elif isinstance(batch[0], ia.PolygonsOnImage):
batch_normalized = Batch(polygons=batch, data=(idx,))
batch_orig_dt = "list_of_imgaug.PolygonsOnImage"
else:
raise Exception(
"Unknown datatype in batch[0]. Expected numpy array "
"or imgaug.HeatmapsOnImage or "
"imgaug.SegmentationMapsOnImage or "
"imgaug.KeypointsOnImage or "
"imgaug.BoundingBoxesOnImage, "
"or imgaug.PolygonsOnImage, "
"got %s." % (type(batch[0]),))
else:
raise Exception(
"Unknown datatype of batch. Expected imgaug.Batch or "
"imgaug.UnnormalizedBatch or "
"numpy array or list of (numpy array or "
"imgaug.HeatmapsOnImage or "
"imgaug.SegmentationMapsOnImage "
"or imgaug.KeypointsOnImage or "
"imgaug.BoundingBoxesOnImage or "
"imgaug.PolygonsOnImage). Got %s." % (type(batch),))
if batch_orig_dt not in ["imgaug.Batch",
"imgaug.UnnormalizedBatch"]:
ia.warn_deprecated(
"Received an input in augment_batches() that was not an "
"instance of imgaug.augmentables.batches.Batch "
"or imgaug.augmentables.batches.UnnormalizedBatch, but "
"instead %s. This is deprecated. Use augment() for such "
"data or wrap it in a Batch instance." % (
batch_orig_dt,))
return batch_normalized, batch_orig_dt
# unnormalization of non-Batch/UnnormalizedBatch is for legacy support
def _unnormalize_batch(batch_aug, batch_orig, batch_orig_dt):
if batch_orig_dt == "imgaug.Batch":
batch_unnormalized = batch_aug
# change (i, .data) back to just .data
batch_unnormalized.data = batch_unnormalized.data[1]
elif batch_orig_dt == "imgaug.UnnormalizedBatch":
# change (i, .data) back to just .data
batch_aug.data = batch_aug.data[1]
batch_unnormalized = \
batch_orig.fill_from_augmented_normalized_batch(batch_aug)
elif batch_orig_dt == "numpy_array":
batch_unnormalized = batch_aug.images_aug
elif batch_orig_dt == "empty_list":
batch_unnormalized = []
elif batch_orig_dt == "list_of_numpy_arrays":
batch_unnormalized = batch_aug.images_aug
elif batch_orig_dt == "list_of_imgaug.HeatmapsOnImage":
batch_unnormalized = batch_aug.heatmaps_aug
elif batch_orig_dt == "list_of_imgaug.SegmentationMapsOnImage":
batch_unnormalized = batch_aug.segmentation_maps_aug
elif batch_orig_dt == "list_of_imgaug.KeypointsOnImage":
batch_unnormalized = batch_aug.keypoints_aug
elif batch_orig_dt == "list_of_imgaug.BoundingBoxesOnImage":
batch_unnormalized = batch_aug.bounding_boxes_aug
else: # only option left
assert batch_orig_dt == "list_of_imgaug.PolygonsOnImage", (
"Got an unexpected type %s." % (type(batch_orig_dt),))
batch_unnormalized = batch_aug.polygons_aug
return batch_unnormalized
if not background:
# singlecore augmentation
for idx, batch in enumerate(batches):
batch_normalized, batch_orig_dt = _normalize_batch(idx, batch)
batch_normalized = self.augment_batch_(
batch_normalized, hooks=hooks)
batch_unnormalized = _unnormalize_batch(
batch_normalized, batch, batch_orig_dt)
yield batch_unnormalized
else:
# multicore augmentation
import imgaug.multicore as multicore
id_to_batch_orig = dict()
def load_batches():
for idx, batch in enumerate(batches):
batch_normalized, batch_orig_dt = _normalize_batch(
idx, batch)
id_to_batch_orig[idx] = (batch, batch_orig_dt)
yield batch_normalized
with multicore.Pool(self) as pool:
# pylint:disable=protected-access
# note that pool.processes is None here
output_buffer_size = pool.pool._processes * 10
for batch_aug in pool.imap_batches(
load_batches(), output_buffer_size=output_buffer_size):
idx = batch_aug.data[0]
assert idx in id_to_batch_orig, (
"Got idx %d from Pool, which is not known." % (
idx))
batch_orig, batch_orig_dt = id_to_batch_orig[idx]
batch_unnormalized = _unnormalize_batch(
batch_aug, batch_orig, batch_orig_dt)
del id_to_batch_orig[idx]
yield batch_unnormalized
# we deprecate here so that users switch to `augment_batch_()` and in the
# future we can add a `parents` parameter here without having to consider
# that a breaking change
@ia.deprecated("augment_batch_()",
comment="`augment_batch()` was renamed to "
"`augment_batch_()` as it changes all `*_unaug` "
"attributes of batches in-place. Note that "
"`augment_batch_()` has now a `parents` parameter. "
"Calls of the style `augment_batch(batch, hooks)` "
"must be changed to "
"`augment_batch(batch, hooks=hooks)`.")
def augment_batch(self, batch, hooks=None):
"""Augment a single batch.
Deprecated since 0.4.0.
"""
# We call augment_batch_() directly here without copy, because this
# method never copies. Would make sense to add a copy here if the
# method is un-deprecated at some point.
return self.augment_batch_(batch, hooks=hooks)
# TODO add more tests
def augment_batch_(self, batch, parents=None, hooks=None):
"""
Augment a single batch in-place.
Added in 0.4.0.
Parameters
----------
batch : imgaug.augmentables.batches.Batch or imgaug.augmentables.batches.UnnormalizedBatch or imgaug.augmentables.batch._BatchInAugmentation
A single batch to augment.
If :class:`imgaug.augmentables.batches.UnnormalizedBatch`
or :class:`imgaug.augmentables.batches.Batch`, then the ``*_aug``
attributes may be modified in-place, while the ``*_unaug``
attributes will not be modified.
If :class:`imgaug.augmentables.batches._BatchInAugmentation`,
then all attributes may be modified in-place.
parents : None or list of imgaug.augmenters.Augmenter, optional
Parent augmenters that have previously been called before the
call to this function. Usually you can leave this parameter as
``None``. It is set automatically for child augmenters.
hooks : None or imgaug.HooksImages, optional
HooksImages object to dynamically interfere with the augmentation
process.
Returns
-------
imgaug.augmentables.batches.Batch or imgaug.augmentables.batches.UnnormalizedBatch
Augmented batch.
"""
# this chain of if/elses would be more beautiful if it was
# (1st) UnnormalizedBatch, (2nd) Batch, (3rd) BatchInAugmenation.
# We check for _BatchInAugmentation first as it is expected to be the
# most common input (due to child calls).
batch_unnorm = None
batch_norm = None
if isinstance(batch, _BatchInAugmentation):
batch_inaug = batch
elif isinstance(batch, UnnormalizedBatch):
batch_unnorm = batch
batch_norm = batch.to_normalized_batch()
batch_inaug = batch_norm.to_batch_in_augmentation()
elif isinstance(batch, Batch):
batch_norm = batch
batch_inaug = batch_norm.to_batch_in_augmentation()
else:
raise ValueError(
"Expected UnnormalizedBatch, Batch or _BatchInAugmentation, "
"got %s." % (type(batch).__name__,))
columns = batch_inaug.columns
# hooks preprocess
if hooks is not None:
for column in columns:
value = hooks.preprocess(
column.value, augmenter=self, parents=parents)
setattr(batch_inaug, column.attr_name, value)
# refresh so that values are updated for later functions
columns = batch_inaug.columns
# set augmentables to None if this augmenter is deactivated or hooks
# demands it
set_to_none = []
if not self.activated:
for column in columns:
set_to_none.append(column)
setattr(batch_inaug, column.attr_name, None)
elif hooks is not None:
for column in columns:
activated = hooks.is_activated(
column.value, augmenter=self, parents=parents,
default=self.activated)
if not activated:
set_to_none.append(column)
setattr(batch_inaug, column.attr_name, None)
# If _augment_batch_() follows legacy-style and ends up calling
# _augment_images() and similar methods, we don't need the
# deterministic context here. But if there is a custom implementation
# of _augment_batch_(), then we should have this here. It causes very
# little overhead.
with _maybe_deterministic_ctx(self):
if not batch_inaug.empty:
pf_enabled = not self.deterministic
with iap.toggled_prefetching(pf_enabled):
batch_inaug = self._augment_batch_(
batch_inaug,
random_state=self.random_state,
parents=parents if parents is not None else [],
hooks=hooks)
# revert augmentables being set to None for non-activated augmenters
for column in set_to_none:
setattr(batch_inaug, column.attr_name, column.value)
# hooks postprocess
if hooks is not None:
# refresh as contents may have been changed in _augment_batch_()
columns = batch_inaug.columns
for column in columns:
augm_value = hooks.postprocess(
column.value, augmenter=self, parents=parents)
setattr(batch_inaug, column.attr_name, augm_value)
if batch_unnorm is not None:
batch_norm = batch_norm.fill_from_batch_in_augmentation_(
batch_inaug)
batch_unnorm = batch_unnorm.fill_from_augmented_normalized_batch_(
batch_norm)
return batch_unnorm
if batch_norm is not None:
batch_norm = batch_norm.fill_from_batch_in_augmentation_(
batch_inaug)
return batch_norm
return batch_inaug
def _augment_batch_(self, batch, random_state, parents, hooks):
"""Augment a single batch in-place.
This is the internal version of :func:`Augmenter.augment_batch_`.
It is called from :func:`Augmenter.augment_batch_` and should usually
not be called directly.
This method may transform the batches in-place.
This method does not have to care about determinism or the
Augmenter instance's ``random_state`` variable. The parameter
``random_state`` takes care of both of these.
Added in 0.4.0.
Parameters
----------
batch : imgaug.augmentables.batches._BatchInAugmentation
The normalized batch to augment. May be changed in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_batch_`.
hooks : imgaug.imgaug.HooksImages or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_batch_`.
Returns
-------
imgaug.augmentables.batches._BatchInAugmentation
The augmented batch.
"""
# The code below covers the case of older augmenters that still have
# _augment_images(), _augment_keypoints(), ... methods that augment
# each input type on its own (including re-sampling from random
# variables). The code block can be safely overwritten by a method
# augmenting a whole batch of data in one step.
columns = batch.columns
multiple_columns = len(columns) > 1
# For multi-column data (e.g. images + BBs) we need deterministic mode
# within this batch, otherwise the datatypes within this batch would
# get different samples.
deterministic = self.deterministic or multiple_columns
# set attribute batch.T_aug with result of self.augment_T() for each
# batch.T_unaug (that had any content)
for column in columns:
with _maybe_deterministic_ctx(random_state, deterministic):
pf_enabled = not self.deterministic
with iap.toggled_prefetching(pf_enabled):
value = getattr(self, "_augment_" + column.name)(
column.value, random_state=random_state,
parents=parents, hooks=hooks)
setattr(batch, column.attr_name, value)
# If the augmenter was alread in deterministic mode, we can expect
# that to_deterministic() was called, which advances the RNG. But
# if it wasn't and we had to auto-switch for the batch, there was not
# advancement yet.
if multiple_columns and not self.deterministic:
random_state.advance_()
return batch
def augment_image(self, image, hooks=None):
"""Augment a single image.
Parameters
----------
image : (H,W,C) ndarray or (H,W) ndarray
The image to augment.
Channel-axis is optional, but expected to be the last axis if
present. In most cases, this array should be of dtype ``uint8``,
which is supported by all augmenters. Support for other dtypes
varies by augmenter -- see the respective augmenter-specific
documentation for more details.
hooks : None or imgaug.HooksImages, optional
HooksImages object to dynamically interfere with the augmentation
process.
Returns
-------
ndarray
The corresponding augmented image.
"""
assert ia.is_np_array(image), (
"Expected to get a single numpy array of shape (H,W) or (H,W,C) "
"for `image`. Got instead type %d. Use `augment_images(images)` "
"to augment a list of multiple images." % (
type(image).__name__),)
assert image.ndim in [2, 3], (
"Expected image to have shape (height, width, [channels]), "
"got shape %s." % (image.shape,))
iabase._warn_on_suspicious_single_image_shape(image)
return self.augment_images([image], hooks=hooks)[0]
def augment_images(self, images, parents=None, hooks=None):
"""Augment a batch of images.
Parameters
----------
images : (N,H,W,C) ndarray or (N,H,W) ndarray or list of (H,W,C) ndarray or list of (H,W) ndarray
Images to augment.
The input can be a list of numpy arrays or a single array. Each
array is expected to have shape ``(H, W, C)`` or ``(H, W)``,
where ``H`` is the height, ``W`` is the width and ``C`` are the
channels. The number of channels may differ between images.
If a list is provided, the height, width and channels may differ
between images within the provided batch.
In most cases, the image array(s) should be of dtype ``uint8``,
which is supported by all augmenters. Support for other dtypes
varies by augmenter -- see the respective augmenter-specific
documentation for more details.
parents : None or list of imgaug.augmenters.Augmenter, optional
Parent augmenters that have previously been called before the
call to this function. Usually you can leave this parameter as
``None``. It is set automatically for child augmenters.
hooks : None or imgaug.imgaug.HooksImages, optional
:class:`~imgaug.imgaug.HooksImages` object to dynamically
interfere with the augmentation process.
Returns
-------
ndarray or list
Corresponding augmented images.
If the input was an ``ndarray``, the output is also an ``ndarray``,
unless the used augmentations have led to different output image
sizes (as can happen in e.g. cropping).
Examples
--------
>>> import imgaug.augmenters as iaa
>>> import numpy as np
>>> aug = iaa.GaussianBlur((0.0, 3.0))
>>> # create empty example images
>>> images = np.zeros((2, 64, 64, 3), dtype=np.uint8)
>>> images_aug = aug.augment_images(images)
Create ``2`` empty (i.e. black) example numpy images and apply
gaussian blurring to them.
"""
iabase._warn_on_suspicious_multi_image_shapes(images)
return self.augment_batch_(
UnnormalizedBatch(images=images),
parents=parents,
hooks=hooks
).images_aug
def _augment_images(self, images, random_state, parents, hooks):
"""Augment a batch of images in-place.
This is the internal version of :func:`Augmenter.augment_images`.
It is called from :func:`Augmenter.augment_images` and should usually
not be called directly.
It has to be implemented by every augmenter.
This method may transform the images in-place.
This method does not have to care about determinism or the
Augmenter instance's ``random_state`` variable. The parameter
``random_state`` takes care of both of these.
.. note::
This method exists mostly for legacy-support.
Overwriting :func:`~imgaug.augmenters.meta.Augmenter._augment_batch`
is now the preferred way of implementing custom augmentation
routines.
Parameters
----------
images : (N,H,W,C) ndarray or list of (H,W,C) ndarray
Images to augment.
They may be changed in-place.
Either a list of ``(H, W, C)`` arrays or a single ``(N, H, W, C)``
array, where ``N`` is the number of images, ``H`` is the height of
images, ``W`` is the width of images and ``C`` is the number of
channels of images. In the case of a list as input, ``H``, ``W``
and ``C`` may change per image.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_images`.
hooks : imgaug.imgaug.HooksImages or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_images`.
Returns
----------
(N,H,W,C) ndarray or list of (H,W,C) ndarray
The augmented images.
"""
return images
def augment_heatmaps(self, heatmaps, parents=None, hooks=None):
"""Augment a batch of heatmaps.
Parameters
----------
heatmaps : imgaug.augmentables.heatmaps.HeatmapsOnImage or list of imgaug.augmentables.heatmaps.HeatmapsOnImage
Heatmap(s) to augment. Either a single heatmap or a list of
heatmaps.
parents : None or list of imgaug.augmenters.meta.Augmenter, optional
Parent augmenters that have previously been called before the
call to this function. Usually you can leave this parameter as
``None``.
It is set automatically for child augmenters.
hooks : None or imaug.imgaug.HooksHeatmaps, optional
:class:`~imgaug.imgaug.HooksHeatmaps` object to dynamically
interfere with the augmentation process.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage or list of imgaug.augmentables.heatmaps.HeatmapsOnImage
Corresponding augmented heatmap(s).
"""
return self.augment_batch_(
UnnormalizedBatch(heatmaps=heatmaps), parents=parents, hooks=hooks
).heatmaps_aug
def _augment_heatmaps(self, heatmaps, random_state, parents, hooks):
"""Augment a batch of heatmaps in-place.
This is the internal version of :func:`Augmenter.augment_heatmaps`.
It is called from :func:`Augmenter.augment_heatmaps` and should
usually not be called directly.
This method may augment heatmaps in-place.
This method does not have to care about determinism or the
Augmenter instance's ``random_state`` variable. The parameter
``random_state`` takes care of both of these.
.. note::
This method exists mostly for legacy-support.
Overwriting :func:`~imgaug.augmenters.meta.Augmenter._augment_batch`
is now the preferred way of implementing custom augmentation
routines.
Parameters
----------
heatmaps : list of imgaug.augmentables.heatmaps.HeatmapsOnImage
Heatmaps to augment. They may be changed in-place.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_heatmaps`.
hooks : imgaug.imgaug.HooksHeatmaps or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_heatmaps`.
Returns
----------
images : list of imgaug.augmentables.heatmaps.HeatmapsOnImage
The augmented heatmaps.
"""
return heatmaps
def augment_segmentation_maps(self, segmaps, parents=None, hooks=None):
"""Augment a batch of segmentation maps.
Parameters
----------
segmaps : imgaug.augmentables.segmaps.SegmentationMapsOnImage or list of imgaug.augmentables.segmaps.SegmentationMapsOnImage
Segmentation map(s) to augment. Either a single segmentation map
or a list of segmentation maps.
parents : None or list of imgaug.augmenters.meta.Augmenter, optional
Parent augmenters that have previously been called before the
call to this function. Usually you can leave this parameter as
``None``. It is set automatically for child augmenters.
hooks : None or imgaug.HooksHeatmaps, optional
:class:`~imgaug.imgaug.HooksHeatmaps` object to dynamically
interfere with the augmentation process.
Returns
-------
imgaug.augmentables.segmaps.SegmentationMapsOnImage or list of imgaug.augmentables.segmaps.SegmentationMapsOnImage
Corresponding augmented segmentation map(s).
"""
return self.augment_batch_(
UnnormalizedBatch(segmentation_maps=segmaps),
parents=parents,
hooks=hooks
).segmentation_maps_aug
def _augment_segmentation_maps(self, segmaps, random_state, parents, hooks):
"""Augment a batch of segmentation in-place.
This is the internal version of
:func:`Augmenter.augment_segmentation_maps`.
It is called from :func:`Augmenter.augment_segmentation_maps` and
should usually not be called directly.
This method may augment segmentation maps in-place.
This method does not have to care about determinism or the
Augmenter instance's ``random_state`` variable. The parameter
``random_state`` takes care of both of these.
.. note::
This method exists mostly for legacy-support.
Overwriting :func:`~imgaug.augmenters.meta.Augmenter._augment_batch`
is now the preferred way of implementing custom augmentation
routines.
Parameters
----------
segmaps : list of imgaug.augmentables.segmaps.SegmentationMapsOnImage
Segmentation maps to augment. They may be changed in-place.
parents : list of imgaug.augmenters.meta.Augmenter
See
:func:`~imgaug.augmenters.meta.Augmenter.augment_segmentation_maps`.
hooks : imgaug.imgaug.HooksHeatmaps or None
See
:func:`~imgaug.augmenters.meta.Augmenter.augment_segmentation_maps`.
Returns
----------
images : list of imgaug.augmentables.segmaps.SegmentationMapsOnImage
The augmented segmentation maps.
"""
return segmaps
def augment_keypoints(self, keypoints_on_images, parents=None, hooks=None):
"""Augment a batch of keypoints/landmarks.
This is the corresponding function to :func:`Augmenter.augment_images`,
just for keypoints/landmarks (i.e. points on images).
Usually you will want to call :func:`Augmenter.augment_images` with
a list of images, e.g. ``augment_images([A, B, C])`` and then
``augment_keypoints()`` with the corresponding list of keypoints on
these images, e.g. ``augment_keypoints([Ak, Bk, Ck])``, where ``Ak``
are the keypoints on image ``A``.
Make sure to first convert the augmenter(s) to deterministic states
before augmenting images and their corresponding keypoints,
e.g. by
>>> import imgaug.augmenters as iaa
>>> from imgaug.augmentables.kps import Keypoint
>>> from imgaug.augmentables.kps import KeypointsOnImage
>>> A = B = C = np.zeros((10, 10), dtype=np.uint8)
>>> Ak = Bk = Ck = KeypointsOnImage([Keypoint(2, 2)], (10, 10))
>>> seq = iaa.Fliplr(0.5)
>>> seq_det = seq.to_deterministic()
>>> imgs_aug = seq_det.augment_images([A, B, C])
>>> kps_aug = seq_det.augment_keypoints([Ak, Bk, Ck])
Otherwise, different random values will be sampled for the image
and keypoint augmentations, resulting in different augmentations (e.g.
images might be rotated by ``30deg`` and keypoints by ``-10deg``).
Also make sure to call :func:`Augmenter.to_deterministic` again for
each new batch, otherwise you would augment all batches in the same
way.
Note that there is also :func:`Augmenter.augment`, which automatically
handles the random state alignment.
Parameters
----------
keypoints_on_images : imgaug.augmentables.kps.KeypointsOnImage or list of imgaug.augmentables.kps.KeypointsOnImage
The keypoints/landmarks to augment.
Either a single instance of
:class:`~imgaug.augmentables.kps.KeypointsOnImage` or a list of
such instances. Each instance must contain the keypoints of a
single image.
parents : None or list of imgaug.augmenters.meta.Augmenter, optional
Parent augmenters that have previously been called before the
call to this function. Usually you can leave this parameter as
``None``. It is set automatically for child augmenters.
hooks : None or imgaug.imgaug.HooksKeypoints, optional
:class:`~imgaug.imgaug.HooksKeypoints` object to dynamically
interfere with the augmentation process.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage or list of imgaug.augmentables.kps.KeypointsOnImage
Augmented keypoints.
"""
return self.augment_batch_(
UnnormalizedBatch(keypoints=keypoints_on_images),
parents=parents,
hooks=hooks
).keypoints_aug
def _augment_keypoints(self, keypoints_on_images, random_state, parents,
hooks):
"""Augment a batch of keypoints in-place.
This is the internal version of :func:`Augmenter.augment_keypoints`.
It is called from :func:`Augmenter.augment_keypoints` and should
usually not be called directly.
This method may transform the keypoints in-place.
This method does not have to care about determinism or the
Augmenter instance's ``random_state`` variable. The parameter
``random_state`` takes care of both of these.
.. note::
This method exists mostly for legacy-support.
Overwriting :func:`~imgaug.augmenters.meta.Augmenter._augment_batch`
is now the preferred way of implementing custom augmentation
routines.
Parameters
----------
keypoints_on_images : list of imgaug.augmentables.kps.KeypointsOnImage
Keypoints to augment. They may be changed in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_keypoints`.
hooks : imgaug.imgaug.HooksKeypoints or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_keypoints`.
Returns
----------
list of imgaug.augmentables.kps.KeypointsOnImage
The augmented keypoints.
"""
return keypoints_on_images
def augment_bounding_boxes(self, bounding_boxes_on_images, parents=None,
hooks=None):
"""Augment a batch of bounding boxes.
This is the corresponding function to
:func:`Augmenter.augment_images`, just for bounding boxes.
Usually you will want to call :func:`Augmenter.augment_images` with
a list of images, e.g. ``augment_images([A, B, C])`` and then
``augment_bounding_boxes()`` with the corresponding list of bounding
boxes on these images, e.g.
``augment_bounding_boxes([Abb, Bbb, Cbb])``, where ``Abb`` are the
bounding boxes on image ``A``.
Make sure to first convert the augmenter(s) to deterministic states
before augmenting images and their corresponding bounding boxes,
e.g. by
>>> import imgaug.augmenters as iaa
>>> from imgaug.augmentables.bbs import BoundingBox
>>> from imgaug.augmentables.bbs import BoundingBoxesOnImage
>>> A = B = C = np.ones((10, 10), dtype=np.uint8)
>>> Abb = Bbb = Cbb = BoundingBoxesOnImage([
>>> BoundingBox(1, 1, 9, 9)], (10, 10))
>>> seq = iaa.Fliplr(0.5)
>>> seq_det = seq.to_deterministic()
>>> imgs_aug = seq_det.augment_images([A, B, C])
>>> bbs_aug = seq_det.augment_bounding_boxes([Abb, Bbb, Cbb])
Otherwise, different random values will be sampled for the image
and bounding box augmentations, resulting in different augmentations
(e.g. images might be rotated by ``30deg`` and bounding boxes by
``-10deg``). Also make sure to call :func:`Augmenter.to_deterministic`
again for each new batch, otherwise you would augment all batches in
the same way.
Note that there is also :func:`Augmenter.augment`, which automatically
handles the random state alignment.
Parameters
----------
bounding_boxes_on_images : imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.bbs.BoundingBoxesOnImage
The bounding boxes to augment.
Either a single instance of
:class:`~imgaug.augmentables.bbs.BoundingBoxesOnImage` or a list of
such instances, with each one of them containing the bounding
boxes of a single image.
parents : None or list of imgaug.augmenters.meta.Augmenter, optional
Parent augmenters that have previously been called before the
call to this function. Usually you can leave this parameter as
``None``. It is set automatically for child augmenters.
hooks : None or imgaug.imgaug.HooksKeypoints, optional
:class:`~imgaug.imgaug.HooksKeypoints` object to dynamically
interfere with the augmentation process.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.bbs.BoundingBoxesOnImage
Augmented bounding boxes.
"""
return self.augment_batch_(
UnnormalizedBatch(bounding_boxes=bounding_boxes_on_images),
parents=parents,
hooks=hooks
).bounding_boxes_aug
def augment_polygons(self, polygons_on_images, parents=None, hooks=None):
"""Augment a batch of polygons.
This is the corresponding function to :func:`Augmenter.augment_images`,
just for polygons.
Usually you will want to call :func:`Augmenter.augment_images`` with
a list of images, e.g. ``augment_images([A, B, C])`` and then
``augment_polygons()`` with the corresponding list of polygons on these
images, e.g. ``augment_polygons([A_poly, B_poly, C_poly])``, where
``A_poly`` are the polygons on image ``A``.
Make sure to first convert the augmenter(s) to deterministic states
before augmenting images and their corresponding polygons,
e.g. by
>>> import imgaug.augmenters as iaa
>>> from imgaug.augmentables.polys import Polygon, PolygonsOnImage
>>> A = B = C = np.ones((10, 10), dtype=np.uint8)
>>> Apoly = Bpoly = Cpoly = PolygonsOnImage(
>>> [Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])],
>>> shape=(10, 10))
>>> seq = iaa.Fliplr(0.5)
>>> seq_det = seq.to_deterministic()
>>> imgs_aug = seq_det.augment_images([A, B, C])
>>> polys_aug = seq_det.augment_polygons([Apoly, Bpoly, Cpoly])
Otherwise, different random values will be sampled for the image
and polygon augmentations, resulting in different augmentations
(e.g. images might be rotated by ``30deg`` and polygons by
``-10deg``). Also make sure to call ``to_deterministic()`` again for
each new batch, otherwise you would augment all batches in the same
way.
Note that there is also :func:`Augmenter.augment`, which automatically
handles the random state alignment.
Parameters
----------
polygons_on_images : imgaug.augmentables.polys.PolygonsOnImage or list of imgaug.augmentables.polys.PolygonsOnImage
The polygons to augment.
Either a single instance of
:class:`~imgaug.augmentables.polys.PolygonsOnImage` or a list of
such instances, with each one of them containing the polygons of
a single image.
parents : None or list of imgaug.augmenters.meta.Augmenter, optional
Parent augmenters that have previously been called before the
call to this function. Usually you can leave this parameter as
``None``. It is set automatically for child augmenters.
hooks : None or imgaug.imgaug.HooksKeypoints, optional
:class:`~imgaug.imgaug.HooksKeypoints` object to dynamically
interfere with the augmentation process.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage or list of imgaug.augmentables.polys.PolygonsOnImage
Augmented polygons.
"""
return self.augment_batch_(
UnnormalizedBatch(polygons=polygons_on_images),
parents=parents,
hooks=hooks
).polygons_aug
def augment_line_strings(self, line_strings_on_images, parents=None,
hooks=None):
"""Augment a batch of line strings.
This is the corresponding function to
:func:`Augmenter.augment_images``, just for line strings.
Usually you will want to call :func:`Augmenter.augment_images` with
a list of images, e.g. ``augment_images([A, B, C])`` and then
``augment_line_strings()`` with the corresponding list of line
strings on these images, e.g.
``augment_line_strings([A_line, B_line, C_line])``, where ``A_line``
are the line strings on image ``A``.
Make sure to first convert the augmenter(s) to deterministic states
before augmenting images and their corresponding line strings,
e.g. by
>>> import imgaug.augmenters as iaa
>>> from imgaug.augmentables.lines import LineString
>>> from imgaug.augmentables.lines import LineStringsOnImage
>>> A = B = C = np.ones((10, 10), dtype=np.uint8)
>>> A_line = B_line = C_line = LineStringsOnImage(
>>> [LineString([(0, 0), (1, 0), (1, 1), (0, 1)])],
>>> shape=(10, 10))
>>> seq = iaa.Fliplr(0.5)
>>> seq_det = seq.to_deterministic()
>>> imgs_aug = seq_det.augment_images([A, B, C])
>>> lines_aug = seq_det.augment_line_strings([A_line, B_line, C_line])
Otherwise, different random values will be sampled for the image
and line string augmentations, resulting in different augmentations
(e.g. images might be rotated by ``30deg`` and line strings by
``-10deg``). Also make sure to call ``to_deterministic()`` again for
each new batch, otherwise you would augment all batches in the same
way.
Note that there is also :func:`Augmenter.augment`, which automatically
handles the random state alignment.
Parameters
----------
line_strings_on_images : imgaug.augmentables.lines.LineStringsOnImage or list of imgaug.augmentables.lines.LineStringsOnImage
The line strings to augment.
Either a single instance of
:class:`~imgaug.augmentables.lines.LineStringsOnImage` or a list of
such instances, with each one of them containing the line strings
of a single image.
parents : None or list of imgaug.augmenters.meta.Augmenter, optional
Parent augmenters that have previously been called before the
call to this function. Usually you can leave this parameter as None.
It is set automatically for child augmenters.
hooks : None or imgaug.imgaug.HooksKeypoints, optional
:class:`~imgaug.imgaug.HooksKeypoints` object to dynamically
interfere with the augmentation process.
Returns
-------
imgaug.augmentables.lines.LineStringsOnImage or list of imgaug.augmentables.lines.LineStringsOnImage
Augmented line strings.
"""
return self.augment_batch_(
UnnormalizedBatch(line_strings=line_strings_on_images),
parents=parents,
hooks=hooks
).line_strings_aug
def _augment_bounding_boxes(self, bounding_boxes_on_images, random_state,
parents, hooks):
"""Augment a batch of bounding boxes on images in-place.
This is the internal version of
:func:`Augmenter.augment_bounding_boxes`.
It is called from :func:`Augmenter.augment_bounding_boxes` and should
usually not be called directly.
This method may transform the bounding boxes in-place.
This method does not have to care about determinism or the
Augmenter instance's ``random_state`` variable. The parameter
``random_state`` takes care of both of these.
.. note::
This method exists mostly for legacy-support.
Overwriting :func:`~imgaug.augmenters.meta.Augmenter._augment_batch`
is now the preferred way of implementing custom augmentation
routines.
Added in 0.4.0.
Parameters
----------
bounding_boxes_on_images : list of imgaug.augmentables.bbs.BoundingBoxesOnImage
Polygons to augment. They may be changed in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_bounding_boxes`.
hooks : imgaug.imgaug.HooksKeypoints or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_bounding_boxes`.
Returns
-------
list of imgaug.augmentables.bbs.BoundingBoxesOnImage
The augmented bounding boxes.
"""
return self._augment_cbaois_as_keypoints(
bounding_boxes_on_images,
random_state=random_state,
parents=parents,
hooks=hooks
)
def _augment_polygons(self, polygons_on_images, random_state, parents,
hooks):
"""Augment a batch of polygons on images in-place.
This is the internal version of :func:`Augmenter.augment_polygons`.
It is called from :func:`Augmenter.augment_polygons` and should
usually not be called directly.
This method may transform the polygons in-place.
This method does not have to care about determinism or the
Augmenter instance's ``random_state`` variable. The parameter
``random_state`` takes care of both of these.
.. note::
This method exists mostly for legacy-support.
Overwriting :func:`~imgaug.augmenters.meta.Augmenter._augment_batch`
is now the preferred way of implementing custom augmentation
routines.
Parameters
----------
polygons_on_images : list of imgaug.augmentables.polys.PolygonsOnImage
Polygons to augment. They may be changed in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_polygons`.
hooks : imgaug.imgaug.HooksKeypoints or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_polygons`.
Returns
-------
list of imgaug.augmentables.polys.PolygonsOnImage
The augmented polygons.
"""
return self._augment_cbaois_as_keypoints(
polygons_on_images,
random_state=random_state,
parents=parents,
hooks=hooks
)
def _augment_line_strings(self, line_strings_on_images, random_state,
parents, hooks):
"""Augment a batch of line strings in-place.
This is the internal version of
:func:`Augmenter.augment_line_strings`.
It is called from :func:`Augmenter.augment_line_strings` and should
usually not be called directly.
This method may transform the line strings in-place.
This method does not have to care about determinism or the
Augmenter instance's ``random_state`` variable. The parameter
``random_state`` takes care of both of these.
.. note::
This method exists mostly for legacy-support.
Overwriting :func:`~imgaug.augmenters.meta.Augmenter._augment_batch`
is now the preferred way of implementing custom augmentation
routines.
Parameters
----------
line_strings_on_images : list of imgaug.augmentables.lines.LineStringsOnImage
Line strings to augment. They may be changed in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_line_strings`.
hooks : imgaug.imgaug.HooksKeypoints or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_line_strings`.
Returns
-------
list of imgaug.augmentables.lines.LineStringsOnImage
The augmented line strings.
"""
return self._augment_cbaois_as_keypoints(
line_strings_on_images,
random_state=random_state,
parents=parents,
hooks=hooks
)
def _augment_bounding_boxes_as_keypoints(self, bounding_boxes_on_images,
random_state, parents, hooks):
"""
Augment BBs by applying keypoint augmentation to their corners.
Added in 0.4.0.
Parameters
----------
bounding_boxes_on_images : list of imgaug.augmentables.bbs.BoundingBoxesOnImages or imgaug.augmentables.bbs.BoundingBoxesOnImages
Bounding boxes to augment. They may be changed in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_polygons`.
hooks : imgaug.imgaug.HooksKeypoints or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_polygons`.
Returns
-------
list of imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage
The augmented bounding boxes.
"""
return self._augment_cbaois_as_keypoints(bounding_boxes_on_images,
random_state=random_state,
parents=parents,
hooks=hooks)
def _augment_polygons_as_keypoints(self, polygons_on_images, random_state,
parents, hooks, recoverer=None):
"""
Augment polygons by applying keypoint augmentation to their vertices.
.. warning::
This method calls
:func:`~imgaug.augmenters.meta.Augmenter._augment_keypoints` and
expects it to do keypoint augmentation. The default for that
method is to do nothing. It must therefore be overwritten,
otherwise the polygon augmentation will also do nothing.
Parameters
----------
polygons_on_images : list of imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.polys.PolygonsOnImage
Polygons to augment. They may be changed in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_polygons`.
hooks : imgaug.imgaug.HooksKeypoints or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_polygons`.
recoverer : None or imgaug.augmentables.polys._ConcavePolygonRecoverer
An instance used to repair invalid polygons after augmentation.
Must offer the method
``recover_from(new_exterior, old_polygon, random_state=0)``.
If ``None`` then invalid polygons are not repaired.
Returns
-------
list of imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.polys.PolygonsOnImage
The augmented polygons.
"""
func = functools.partial(self._augment_keypoints,
random_state=random_state,
parents=parents,
hooks=hooks)
return self._apply_to_polygons_as_keypoints(polygons_on_images, func,
recoverer, random_state)
def _augment_line_strings_as_keypoints(self, line_strings_on_images,
random_state, parents, hooks):
"""
Augment BBs by applying keypoint augmentation to their corners.
Parameters
----------
line_strings_on_images : list of imgaug.augmentables.lines.LineStringsOnImages or imgaug.augmentables.lines.LineStringsOnImages
Line strings to augment. They may be changed in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_polygons`.
hooks : imgaug.imgaug.HooksKeypoints or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_polygons`.
Returns
-------
list of imgaug.augmentables.lines.LineStringsOnImages or imgaug.augmentables.lines.LineStringsOnImages
The augmented line strings.
"""
return self._augment_cbaois_as_keypoints(line_strings_on_images,
random_state=random_state,
parents=parents,
hooks=hooks)
def _augment_cbaois_as_keypoints(
self, cbaois, random_state, parents, hooks):
"""
Augment bounding boxes by applying KP augmentation to their corners.
Added in 0.4.0.
Parameters
----------
cbaois : list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.polys.PolygonsOnImage or list of imgaug.augmentables.lines.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.lines.LineStringsOnImage
Coordinate-based augmentables to augment. They may be changed
in-place.
random_state : imgaug.random.RNG
The random state to use for all sampling tasks during the
augmentation.
parents : list of imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.augment_batch`.
hooks : imgaug.imgaug.HooksKeypoints or None
See :func:`~imgaug.augmenters.meta.Augmenter.augment_batch`.
Returns
-------
list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.polys.PolygonsOnImage or list of imgaug.augmentables.lines.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.lines.LineStringsOnImage
The augmented coordinate-based augmentables.
"""
func = functools.partial(self._augment_keypoints,
random_state=random_state,
parents=parents,
hooks=hooks)
return self._apply_to_cbaois_as_keypoints(cbaois, func)
@classmethod
def _apply_to_polygons_as_keypoints(cls, polygons_on_images, func,
recoverer=None, random_state=None):
"""
Apply a callback to polygons in keypoint-representation.
Added in 0.4.0.
Parameters
----------
polygons_on_images : list of imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.polys.PolygonsOnImage
Polygons to augment. They may be changed in-place.
func : callable
The function to apply. Receives a list of
:class:`~imgaug.augmentables.kps.KeypointsOnImage` instances as its
only parameter.
recoverer : None or imgaug.augmentables.polys._ConcavePolygonRecoverer
An instance used to repair invalid polygons after augmentation.
Must offer the method
``recover_from(new_exterior, old_polygon, random_state=0)``.
If ``None`` then invalid polygons are not repaired.
random_state : None or imgaug.random.RNG
The random state to use for the recoverer.
Returns
-------
list of imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.polys.PolygonsOnImage
The augmented polygons.
"""
from ..augmentables.polys import recover_psois_
psois_orig = None
if recoverer is not None:
if isinstance(polygons_on_images, list):
psois_orig = [psoi.deepcopy() for psoi in polygons_on_images]
else:
psois_orig = polygons_on_images.deepcopy()
psois = cls._apply_to_cbaois_as_keypoints(polygons_on_images, func)
if recoverer is None:
return psois
# Its not really necessary to create an RNG copy for the recoverer
# here, as the augmentation of the polygons is already finished and
# used the same samples as the image augmentation. The recoverer might
# advance the RNG state, but the next call to e.g. augment() will then
# still use the same (advanced) RNG state for images and polygons.
# We copy here anyways as it seems cleaner.
random_state_recoverer = (random_state.copy()
if random_state is not None else None)
psois = recover_psois_(psois, psois_orig, recoverer,
random_state_recoverer)
return psois
@classmethod
def _apply_to_cbaois_as_keypoints(cls, cbaois, func):
"""
Augment bounding boxes by applying KP augmentation to their corners.
Added in 0.4.0.
Parameters
----------
cbaois : list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.polys.PolygonsOnImage or list of imgaug.augmentables.lines.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.lines.LineStringsOnImage
Coordinate-based augmentables to augment. They may be changed
in-place.
func : callable
The function to apply. Receives a list of
:class:`~imgaug.augmentables.kps.KeypointsOnImage` instances as its
only parameter.
Returns
-------
list of imgaug.augmentables.bbs.BoundingBoxesOnImage or list of imgaug.augmentables.polys.PolygonsOnImage or list of imgaug.augmentables.lines.LineStringsOnImage or imgaug.augmentables.bbs.BoundingBoxesOnImage or imgaug.augmentables.polys.PolygonsOnImage or imgaug.augmentables.lines.LineStringsOnImage
The augmented coordinate-based augmentables.
"""
from ..augmentables.utils import (convert_cbaois_to_kpsois,
invert_convert_cbaois_to_kpsois_)
kpsois = convert_cbaois_to_kpsois(cbaois)
kpsois_aug = func(kpsois)
return invert_convert_cbaois_to_kpsois_(cbaois, kpsois_aug)
def augment(self, return_batch=False, hooks=None, **kwargs):
"""Augment a batch.
This method is a wrapper around
:class:`~imgaug.augmentables.batches.UnnormalizedBatch` and
:func:`~imgaug.augmenters.meta.Augmenter.augment_batch`. Hence, it
supports the same datatypes as
:class:`~imgaug.augmentables.batches.UnnormalizedBatch`.
If `return_batch` was set to ``False`` (the default), the method will
return a tuple of augmentables. It will return the same types of
augmentables (but in augmented form) as input into the method. This
behaviour is partly specific to the python version:
* In **python 3.6+** (if ``return_batch=False``):
* Any number of augmentables may be provided as input.
* None of the provided named arguments *has to be* `image` or
`images` (but of coarse you *may* provide them).
* The return order matches the order of the named arguments, e.g.
``x_aug, y_aug, z_aug = augment(X=x, Y=y, Z=z)``.
* In **python <3.6** (if ``return_batch=False``):
* One or two augmentables may be used as input, not more than that.
* One of the input arguments has to be `image` or `images`.
* The augmented images are *always* returned first, independent
of the input argument order, e.g.
``a_aug, b_aug = augment(b=b, images=a)``. This also means
that the output of the function can only be one of the
following three cases: a batch, list/array of images,
tuple of images and something (like images + segmentation maps).
If `return_batch` was set to ``True``, an instance of
:class:`~imgaug.augmentables.batches.UnnormalizedBatch` will be
returned. The output is the same for all python version and any
number or combination of augmentables may be provided.
So, to keep code downward compatible for python <3.6, use one of the
following three options:
* Use ``batch = augment(images=X, ..., return_batch=True)``.
* Call ``images = augment(images=X)``.
* Call ``images, other = augment(images=X, =Y)``.
All augmentables must be provided as named arguments.
E.g. ``augment()`` will crash, but ``augment(images=)``
will work.
Parameters
----------
image : None or (H,W,C) ndarray or (H,W) ndarray, optional
The image to augment. Only this or `images` can be set, not both.
If `return_batch` is ``False`` and the python version is below 3.6,
either this or `images` **must** be provided.
images : None or (N,H,W,C) ndarray or (N,H,W) ndarray or iterable of (H,W,C) ndarray or iterable of (H,W) ndarray, optional
The images to augment. Only this or `image` can be set, not both.
If `return_batch` is ``False`` and the python version is below 3.6,
either this or `image` **must** be provided.
heatmaps : None or (N,H,W,C) ndarray or imgaug.augmentables.heatmaps.HeatmapsOnImage or iterable of (H,W,C) ndarray or iterable of imgaug.augmentables.heatmaps.HeatmapsOnImage, optional
The heatmaps to augment.
If anything else than
:class:`~imgaug.augmentables.heatmaps.HeatmapsOnImage`, then the
number of heatmaps must match the number of images provided via
parameter `images`. The number is contained either in ``N`` or the
first iterable's size.
segmentation_maps : None or (N,H,W) ndarray or imgaug.augmentables.segmaps.SegmentationMapsOnImage or iterable of (H,W) ndarray or iterable of imgaug.augmentables.segmaps.SegmentationMapsOnImage, optional
The segmentation maps to augment.
If anything else than
:class:`~imgaug.augmentables.segmaps.SegmentationMapsOnImage`, then
the number of segmaps must match the number of images provided via
parameter `images`. The number is contained either in ``N`` or the
first iterable's size.
keypoints : None or list of (N,K,2) ndarray or tuple of number or imgaug.augmentables.kps.Keypoint or iterable of (K,2) ndarray or iterable of tuple of number or iterable of imgaug.augmentables.kps.Keypoint or iterable of imgaug.augmentables.kps.KeypointOnImage or iterable of iterable of tuple of number or iterable of iterable of imgaug.augmentables.kps.Keypoint, optional
The keypoints to augment.
If a tuple (or iterable(s) of tuple), then iterpreted as ``(x,y)``
coordinates and must hence contain two numbers.
A single tuple represents a single coordinate on one image, an
iterable of tuples the coordinates on one image and an iterable of
iterable of tuples the coordinates on several images. Analogous if
:class:`~imgaug.augmentables.kps.Keypoint` instances are used
instead of tuples.
If an ndarray, then ``N`` denotes the number of images and ``K``
the number of keypoints on each image.
If anything else than
:class:`~imgaug.augmentables.kps.KeypointsOnImage` is provided, then
the number of keypoint groups must match the number of images
provided via parameter `images`. The number is contained e.g. in
``N`` or in case of "iterable of iterable of tuples" in the first
iterable's size.
bounding_boxes : None or (N,B,4) ndarray or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage or iterable of (B,4) ndarray or iterable of tuple of number or iterable of imgaug.augmentables.bbs.BoundingBox or iterable of imgaug.augmentables.bbs.BoundingBoxesOnImage or iterable of iterable of tuple of number or iterable of iterable imgaug.augmentables.bbs.BoundingBox, optional
The bounding boxes to augment.
This is analogous to the `keypoints` parameter. However, each
tuple -- and also the last index in case of arrays -- has size
``4``, denoting the bounding box coordinates ``x1``, ``y1``,
``x2`` and ``y2``.
polygons : None or (N,#polys,#points,2) ndarray or imgaug.augmentables.polys.Polygon or imgaug.augmentables.polys.PolygonsOnImage or iterable of (#polys,#points,2) ndarray or iterable of tuple of number or iterable of imgaug.augmentables.kps.Keypoint or iterable of imgaug.augmentables.polys.Polygon or iterable of imgaug.augmentables.polys.PolygonsOnImage or iterable of iterable of (#points,2) ndarray or iterable of iterable of tuple of number or iterable of iterable of imgaug.augmentables.kps.Keypoint or iterable of iterable of imgaug.augmentables.polys.Polygon or iterable of iterable of iterable of tuple of number or iterable of iterable of iterable of tuple of imgaug.augmentables.kps.Keypoint, optional
The polygons to augment.
This is similar to the `keypoints` parameter. However, each polygon
may be made up of several ``(x,y) ``coordinates (three or more are
required for valid polygons).
The following datatypes will be interpreted as a single polygon on
a single image:
* ``imgaug.augmentables.polys.Polygon``
* ``iterable of tuple of number``
* ``iterable of imgaug.augmentables.kps.Keypoint``
The following datatypes will be interpreted as multiple polygons
on a single image:
* ``imgaug.augmentables.polys.PolygonsOnImage``
* ``iterable of imgaug.augmentables.polys.Polygon``
* ``iterable of iterable of tuple of number``
* ``iterable of iterable of imgaug.augmentables.kps.Keypoint``
* ``iterable of iterable of imgaug.augmentables.polys.Polygon``
The following datatypes will be interpreted as multiple polygons on
multiple images:
* ``(N,#polys,#points,2) ndarray``
* ``iterable of (#polys,#points,2) ndarray``
* ``iterable of iterable of (#points,2) ndarray``
* ``iterable of iterable of iterable of tuple of number``
* ``iterable of iterable of iterable of tuple of imgaug.augmentables.kps.Keypoint``
line_strings : None or (N,#lines,#points,2) ndarray or imgaug.augmentables.lines.LineString or imgaug.augmentables.lines.LineStringOnImage or iterable of (#polys,#points,2) ndarray or iterable of tuple of number or iterable of imgaug.augmentables.kps.Keypoint or iterable of imgaug.augmentables.lines.LineString or iterable of imgaug.augmentables.lines.LineStringOnImage or iterable of iterable of (#points,2) ndarray or iterable of iterable of tuple of number or iterable of iterable of imgaug.augmentables.kps.Keypoint or iterable of iterable of imgaug.augmentables.lines.LineString or iterable of iterable of iterable of tuple of number or iterable of iterable of iterable of tuple of imgaug.augmentables.kps.Keypoint, optional
The line strings to augment.
See `polygons`, which behaves similarly.
return_batch : bool, optional
Whether to return an instance of
:class:`~imgaug.augmentables.batches.UnnormalizedBatch`. If the
python version is below 3.6 and more than two augmentables were
provided (e.g. images, keypoints and polygons), then this must be
set to ``True``. Otherwise an error will be raised.
hooks : None or imgaug.imgaug.HooksImages, optional
Hooks object to dynamically interfere with the augmentation process.
Returns
-------
tuple or imgaug.augmentables.batches.UnnormalizedBatch
If `return_batch` was set to ``True``, a instance of
``UnnormalizedBatch`` will be returned.
If `return_batch` was set to ``False``, a tuple of augmentables
will be returned, e.g. ``(augmented images, augmented keypoints)``.
The datatypes match the input datatypes of the corresponding named
arguments. In python <3.6, augmented images are always the first
entry in the returned tuple. In python 3.6+ the order matches the
order of the named arguments.
Examples
--------
>>> import numpy as np
>>> import imgaug as ia
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Affine(rotate=(-25, 25))
>>> image = np.zeros((64, 64, 3), dtype=np.uint8)
>>> keypoints = [(10, 20), (30, 32)] # (x,y) coordinates
>>> images_aug, keypoints_aug = aug.augment(
>>> image=image, keypoints=keypoints)
Create a single image and a set of two keypoints on it, then
augment both by applying a random rotation between ``-25`` deg and
``+25`` deg. The sampled rotation value is automatically aligned
between image and keypoints. Note that in python <3.6, augmented
images will always be returned first, independent of the order of
the named input arguments. So
``keypoints_aug, images_aug = aug.augment(keypoints=keypoints,
image=image)`` would **not** be correct (but in python 3.6+ it would
be).
>>> import numpy as np
>>> import imgaug as ia
>>> import imgaug.augmenters as iaa
>>> from imgaug.augmentables.bbs import BoundingBox
>>> aug = iaa.Affine(rotate=(-25, 25))
>>> images = [np.zeros((64, 64, 3), dtype=np.uint8),
>>> np.zeros((32, 32, 3), dtype=np.uint8)]
>>> keypoints = [[(10, 20), (30, 32)], # KPs on first image
>>> [(22, 10), (12, 14)]] # KPs on second image
>>> bbs = [
>>> [BoundingBox(x1=5, y1=5, x2=50, y2=45)],
>>> [BoundingBox(x1=4, y1=6, x2=10, y2=15),
>>> BoundingBox(x1=8, y1=9, x2=16, y2=30)]
>>> ] # one BB on first image, two BBs on second image
>>> batch_aug = aug.augment(
>>> images=images, keypoints=keypoints, bounding_boxes=bbs,
>>> return_batch=True)
Create two images of size ``64x64`` and ``32x32``, two sets of
keypoints (each containing two keypoints) and two sets of bounding
boxes (the first containing one bounding box, the second two bounding
boxes). These augmentables are then augmented by applying random
rotations between ``-25`` deg and ``+25`` deg to them. The rotation
values are sampled by image and aligned between all augmentables on
the same image. The method finally returns an instance of
:class:`~imgaug.augmentables.batches.UnnormalizedBatch` from which the
augmented data can be retrieved via ``batch_aug.images_aug``,
``batch_aug.keypoints_aug``, and ``batch_aug.bounding_boxes_aug``.
In python 3.6+, `return_batch` can be kept at ``False`` and the
augmented data can be retrieved as
``images_aug, keypoints_aug, bbs_aug = augment(...)``.
"""
assert ia.is_single_bool(return_batch), (
"Expected boolean as argument for 'return_batch', got type %s. "
"Call augment() only with named arguments, e.g. "
"augment(images=)." % (str(type(return_batch)),))
expected_keys = ["images", "heatmaps", "segmentation_maps",
"keypoints", "bounding_boxes", "polygons",
"line_strings"]
expected_keys_call = ["image"] + expected_keys
# at least one augmentable provided?
assert any([key in kwargs for key in expected_keys_call]), (
"Expected augment() to be called with one of the following named "
"arguments: %s. Got none of these." % (
", ".join(expected_keys_call),))
# all keys in kwargs actually known?
unknown_args = [key for key in kwargs if key not in expected_keys_call]
assert len(unknown_args) == 0, (
"Got the following unknown keyword argument(s) in augment(): %s" % (
", ".join(unknown_args)
))
# normalize image=... input to images=...
# this is not done by Batch.to_normalized_batch()
if "image" in kwargs:
assert "images" not in kwargs, (
"You may only provide the argument 'image' OR 'images' to "
"augment(), not both of them.")
images = [kwargs["image"]]
iabase._warn_on_suspicious_single_image_shape(images[0])
else:
images = kwargs.get("images", None)
iabase._warn_on_suspicious_multi_image_shapes(images)
# Decide whether to return the final tuple in the order of the kwargs
# keys or the default order based on python version. Only 3.6+ uses
# an ordered dict implementation for kwargs.
order = "standard"
nb_keys = len(list(kwargs.keys()))
vinfo = sys.version_info
is_py36_or_newer = vinfo[0] > 3 or (vinfo[0] == 3 and vinfo[1] >= 6)
if is_py36_or_newer:
order = "kwargs_keys"
elif not return_batch and nb_keys > 2:
raise ValueError(
"Requested more than two outputs in augment(), but detected "
"python version is below 3.6. More than two outputs are only "
"supported for 3.6+ as earlier python versions offer no way "
"to retrieve the order of the provided named arguments. To "
"still use more than two outputs, add 'return_batch=True' as "
"an argument and retrieve the outputs manually from the "
"returned UnnormalizedBatch instance, e.g. via "
"'batch.images_aug' to get augmented images."
)
elif not return_batch and nb_keys == 2 and images is None:
raise ValueError(
"Requested two outputs from augment() that were not 'images', "
"but detected python version is below 3.6. For security "
"reasons, only single-output requests or requests with two "
"outputs of which one is 'images' are allowed in <3.6. "
"'images' will then always be returned first. If you don't "
"want this, use 'return_batch=True' mode in augment(), which "
"returns a single UnnormalizedBatch instance instead and "
"supports any combination of outputs."
)
# augment batch
batch = UnnormalizedBatch(
images=images,
heatmaps=kwargs.get("heatmaps", None),
segmentation_maps=kwargs.get("segmentation_maps", None),
keypoints=kwargs.get("keypoints", None),
bounding_boxes=kwargs.get("bounding_boxes", None),
polygons=kwargs.get("polygons", None),
line_strings=kwargs.get("line_strings", None)
)
batch_aug = self.augment_batch_(batch, hooks=hooks)
# return either batch or tuple of augmentables, depending on what
# was requested by user
if return_batch:
return batch_aug
result = []
if order == "kwargs_keys":
for key in kwargs:
if key == "image":
attr = getattr(batch_aug, "images_aug")
result.append(attr[0])
else:
result.append(getattr(batch_aug, "%s_aug" % (key,)))
else:
for key in expected_keys:
if key == "images" and "image" in kwargs:
attr = getattr(batch_aug, "images_aug")
result.append(attr[0])
elif key in kwargs:
result.append(getattr(batch_aug, "%s_aug" % (key,)))
if len(result) == 1:
return result[0]
return tuple(result)
def __call__(self, *args, **kwargs):
"""Alias for :func:`~imgaug.augmenters.meta.Augmenter.augment`."""
return self.augment(*args, **kwargs)
def pool(self, processes=None, maxtasksperchild=None, seed=None):
"""Create a pool used for multicore augmentation.
Parameters
----------
processes : None or int, optional
Same as in :func:`~imgaug.multicore.Pool.__init__`.
The number of background workers. If ``None``, the number of the
machine's CPU cores will be used (this counts hyperthreads as CPU
cores). If this is set to a negative value ``p``, then
``P - abs(p)`` will be used, where ``P`` is the number of CPU
cores. E.g. ``-1`` would use all cores except one (this is useful
to e.g. reserve one core to feed batches to the GPU).
maxtasksperchild : None or int, optional
Same as for :func:`~imgaug.multicore.Pool.__init__`.
The number of tasks done per worker process before the process
is killed and restarted. If ``None``, worker processes will not
be automatically restarted.
seed : None or int, optional
Same as for :func:`~imgaug.multicore.Pool.__init__`.
The seed to use for child processes. If ``None``, a random seed
will be used.
Returns
-------
imgaug.multicore.Pool
Pool for multicore augmentation.
Examples
--------
>>> import numpy as np
>>> import imgaug as ia
>>> import imgaug.augmenters as iaa
>>> from imgaug.augmentables.batches import Batch
>>>
>>> aug = iaa.Add(1)
>>> images = np.zeros((16, 128, 128, 3), dtype=np.uint8)
>>> batches = [Batch(images=np.copy(images)) for _ in range(100)]
>>> with aug.pool(processes=-1, seed=2) as pool:
>>> batches_aug = pool.map_batches(batches, chunksize=8)
>>> print(np.sum(batches_aug[0].images_aug[0]))
49152
Create ``100`` batches of empty images. Each batch contains
``16`` images of size ``128x128``. The batches are then augmented on
all CPU cores except one (``processes=-1``). After augmentation, the
sum of pixel values from the first augmented image is printed.
>>> import numpy as np
>>> import imgaug as ia
>>> import imgaug.augmenters as iaa
>>> from imgaug.augmentables.batches import Batch
>>>
>>> aug = iaa.Add(1)
>>> images = np.zeros((16, 128, 128, 3), dtype=np.uint8)
>>> def generate_batches():
>>> for _ in range(100):
>>> yield Batch(images=np.copy(images))
>>>
>>> with aug.pool(processes=-1, seed=2) as pool:
>>> batches_aug = pool.imap_batches(generate_batches(), chunksize=8)
>>> batch_aug = next(batches_aug)
>>> print(np.sum(batch_aug.images_aug[0]))
49152
Same as above. This time, a generator is used to generate batches
of images. Again, the first augmented image's sum of pixels is printed.
"""
import imgaug.multicore as multicore
return multicore.Pool(self, processes=processes,
maxtasksperchild=maxtasksperchild, seed=seed)
# TODO most of the code of this function could be replaced with
# ia.draw_grid()
# TODO add parameter for handling multiple images ((a) next to each other
# in each row or (b) multiply row count by number of images and put
# each one in a new row)
# TODO "images" parameter deviates from augment_images (3d array is here
# treated as one 3d image, in augment_images as (N, H, W))
# TODO according to the docstring, this can handle (H,W) images, but not
# (H,W,1)
def draw_grid(self, images, rows, cols):
"""Augment images and draw the results as a single grid-like image.
This method applies this augmenter to the provided images and returns
a grid image of the results. Each cell in the grid contains a single
augmented version of an input image.
If multiple input images are provided, the row count is multiplied by
the number of images and each image gets its own row.
E.g. for ``images = [A, B]``, ``rows=2``, ``cols=3``::
A A A
B B B
A A A
B B B
for ``images = [A]``, ``rows=2``, ``cols=3``::
A A A
A A A
Parameters
-------
images : (N,H,W,3) ndarray or (H,W,3) ndarray or (H,W) ndarray or list of (H,W,3) ndarray or list of (H,W) ndarray
List of images to augment and draw in the grid.
If a list, then each element is expected to have shape ``(H, W)``
or ``(H, W, 3)``. If a single array, then it is expected to have
shape ``(N, H, W, 3)`` or ``(H, W, 3)`` or ``(H, W)``.
rows : int
Number of rows in the grid.
If ``N`` input images are given, this value will automatically be
multiplied by ``N`` to create rows for each image.
cols : int
Number of columns in the grid.
Returns
-------
(Hg, Wg, 3) ndarray
The generated grid image with augmented versions of the input
images. Here, ``Hg`` and ``Wg`` reference the output size of the
grid, and *not* the sizes of the input images.
"""
if ia.is_np_array(images):
if len(images.shape) == 4:
images = [images[i] for i in range(images.shape[0])]
elif len(images.shape) == 3:
images = [images]
elif len(images.shape) == 2:
images = [images[:, :, np.newaxis]]
else:
raise Exception(
"Unexpected images shape, expected 2-, 3- or "
"4-dimensional array, got shape %s." % (images.shape,))
else:
assert isinstance(images, list), (
"Expected 'images' to be an ndarray or list of ndarrays. "
"Got %s." % (type(images),))
for i, image in enumerate(images):
if len(image.shape) == 3:
continue
if len(image.shape) == 2:
images[i] = image[:, :, np.newaxis]
else:
raise Exception(
"Unexpected image shape at index %d, expected 2- or "
"3-dimensional array, got shape %s." % (
i, image.shape,))
det = self if self.deterministic else self.to_deterministic()
augs = []
for image in images:
augs.append(det.augment_images([image] * (rows * cols)))
augs_flat = list(itertools.chain(*augs))
cell_height = max([image.shape[0] for image in augs_flat])
cell_width = max([image.shape[1] for image in augs_flat])
width = cell_width * cols
height = cell_height * (rows * len(images))
grid = np.zeros((height, width, 3), dtype=augs[0][0].dtype)
for row_idx in range(rows):
for img_idx, image in enumerate(images):
for col_idx in range(cols):
image_aug = augs[img_idx][(row_idx * cols) + col_idx]
cell_y1 = cell_height * (row_idx * len(images) + img_idx)
cell_y2 = cell_y1 + image_aug.shape[0]
cell_x1 = cell_width * col_idx
cell_x2 = cell_x1 + image_aug.shape[1]
grid[cell_y1:cell_y2, cell_x1:cell_x2, :] = image_aug
return grid
# TODO test for 2D images
# TODO test with C = 1
def show_grid(self, images, rows, cols):
"""Augment images and plot the results as a single grid-like image.
This calls :func:`~imgaug.augmenters.meta.Augmenter.draw_grid` and
simply shows the results. See that method for details.
Parameters
----------
images : (N,H,W,3) ndarray or (H,W,3) ndarray or (H,W) ndarray or list of (H,W,3) ndarray or list of (H,W) ndarray
List of images to augment and draw in the grid.
If a list, then each element is expected to have shape ``(H, W)``
or ``(H, W, 3)``. If a single array, then it is expected to have
shape ``(N, H, W, 3)`` or ``(H, W, 3)`` or ``(H, W)``.
rows : int
Number of rows in the grid.
If ``N`` input images are given, this value will automatically be
multiplied by ``N`` to create rows for each image.
cols : int
Number of columns in the grid.
"""
grid = self.draw_grid(images, rows, cols)
ia.imshow(grid)
def to_deterministic(self, n=None):
"""Convert this augmenter from a stochastic to a deterministic one.
A stochastic augmenter samples pseudo-random values for each parameter,
image and batch.
A deterministic augmenter also samples new values for each parameter
and image, but not batch. Instead, for consecutive batches it will
sample the same values (provided the number of images and their sizes
don't change).
From a technical perspective this means that a deterministic augmenter
starts each batch's augmentation with a random number generator in
the same state (i.e. same seed), instead of advancing that state from
batch to batch.
Using determinism is useful to (a) get the same augmentations for
two or more image batches (e.g. for stereo cameras), (b) to augment
images and corresponding data on them (e.g. segmentation maps or
bounding boxes) in the same way.
Parameters
----------
n : None or int, optional
Number of deterministic augmenters to return.
If ``None`` then only one :class:`~imgaug.augmenters.meta.Augmenter`
instance will be returned.
If ``1`` or higher, a list containing ``n``
:class:`~imgaug.augmenters.meta.Augmenter` instances will be
returned.
Returns
-------
imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter
A single Augmenter object if `n` was None,
otherwise a list of Augmenter objects (even if `n` was ``1``).
"""
assert n is None or n >= 1, (
"Expected 'n' to be None or >=1, got %s." % (n,))
if n is None:
return self.to_deterministic(1)[0]
return [self._to_deterministic() for _ in sm.xrange(n)]
def _to_deterministic(self):
"""Convert this augmenter from a stochastic to a deterministic one.
Augmenter-specific implementation of
:func:`~imgaug.augmenters.meta.to_deterministic`. This function is
expected to return a single new deterministic
:class:`~imgaug.augmenters.meta.Augmenter` instance of this augmenter.
Returns
-------
det : imgaug.augmenters.meta.Augmenter
Deterministic variation of this Augmenter object.
"""
aug = self.copy()
# This was changed for 0.2.8 from deriving a new random state based on
# the global random state to deriving it from the augmenter's local
# random state. This should reduce the risk that re-runs of scripts
# lead to different results upon small changes somewhere. It also
# decreases the likelihood of problems when using multiprocessing
# (the child processes might use the same global random state as the
# parent process). Note for the latter point that augment_batches()
# might call to_deterministic() if the batch contains multiply types
# of augmentables.
# aug.random_state = iarandom.create_random_rng()
aug.random_state = self.random_state.derive_rng_()
aug.deterministic = True
return aug
@ia.deprecated("imgaug.augmenters.meta.Augmenter.seed_")
def reseed(self, random_state=None, deterministic_too=False):
"""Old name of :func:`~imgaug.augmenters.meta.Augmenter.seed_`.
Deprecated since 0.4.0.
"""
self.seed_(entropy=random_state, deterministic_too=deterministic_too)
# TODO mark this as in-place
def seed_(self, entropy=None, deterministic_too=False):
"""Seed this augmenter and all of its children.
This method assigns a new random number generator to the
augmenter and all of its children (if it has any). The new random
number generator is *derived* from the provided seed or RNG -- or from
the global random number generator if ``None`` was provided.
Note that as child RNGs are *derived*, they do not all use the same
seed.
If this augmenter or any child augmenter had a random number generator
that pointed to the global random state, it will automatically be
replaced with a local random state. This is similar to what
:func:`~imgaug.augmenters.meta.Augmenter.localize_random_state`
does.
This method is useful when augmentations are run in the
background (i.e. on multiple cores).
It should be called before sending this
:class:`~imgaug.augmenters.meta.Augmenter` instance to a
background worker or once within each worker with different seeds
(i.e., if ``N`` workers are used, the function should be called
``N`` times). Otherwise, all background workers will
use the same seeds and therefore apply the same augmentations.
Note that :func:`Augmenter.augment_batches` and :func:`Augmenter.pool`
already do this automatically.
Added in 0.4.0.
Parameters
----------
entropy : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
A seed or random number generator that is used to derive new
random number generators for this augmenter and its children.
If an ``int`` is provided, it will be interpreted as a seed.
If ``None`` is provided, the global random number generator will
be used.
deterministic_too : bool, optional
Whether to also change the seed of an augmenter ``A``, if ``A``
is deterministic. This is the case both when this augmenter
object is ``A`` or one of its children is ``A``.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Sequential([
>>> iaa.Crop(px=(0, 10)),
>>> iaa.Crop(px=(0, 10))
>>> ])
>>> aug.seed_(1)
Seed an augmentation sequence containing two crop operations. Even
though the same seed was used, the two operations will still sample
different pixel amounts to crop as the child-specific seed is merely
derived from the provided seed.
"""
assert isinstance(deterministic_too, bool), (
"Expected 'deterministic_too' to be a boolean, got type %s." % (
deterministic_too))
if entropy is None:
random_state = iarandom.RNG.create_pseudo_random_()
else:
random_state = iarandom.RNG.create_if_not_rng_(entropy)
if not self.deterministic or deterministic_too:
# note that derive_rng_() (used below) advances the RNG, so
# child augmenters get a different RNG state
self.random_state = random_state.copy()
for lst in self.get_children_lists():
for aug in lst:
aug.seed_(entropy=random_state.derive_rng_(),
deterministic_too=deterministic_too)
def localize_random_state(self, recursive=True):
"""Assign augmenter-specific RNGs to this augmenter and its children.
See :func:`Augmenter.localize_random_state_` for more details.
Parameters
----------
recursive : bool, optional
See
:func:`~imgaug.augmenters.meta.Augmenter.localize_random_state_`.
Returns
-------
imgaug.augmenters.meta.Augmenter
Copy of the augmenter and its children, with localized RNGs.
"""
aug = self.deepcopy()
aug.localize_random_state_(
recursive=recursive
)
return aug
# TODO rename random_state -> rng
def localize_random_state_(self, recursive=True):
"""Assign augmenter-specific RNGs to this augmenter and its children.
This method iterates over this augmenter and all of its children and
replaces any pointer to the global RNG with a new local (i.e.
augmenter-specific) RNG.
A random number generator (RNG) is used for the sampling of random
values.
The global random number generator exists exactly once throughout
the library and is shared by many augmenters.
A local RNG (usually) exists within exactly one augmenter and is
only used by that augmenter.
Usually there is no need to change global into local RNGs.
The only noteworthy exceptions are
* Whenever you want to use determinism (so that the global RNG is
not accidentally reverted).
* Whenever you want to copy RNGs from one augmenter to
another. (Copying the global RNG would usually not be useful.
Copying the global RNG from augmenter A to B, then executing A
and then B would result in B's (global) RNG's state having
already changed because of A's sampling. So the samples of
A and B would differ.)
The case of determinism is handled automatically by
:func:`~imgaug.augmenters.meta.Augmenter.to_deterministic`.
Only when you copy RNGs (via
:func:`~imgaug.augmenters.meta.Augmenter.copy_random_state`),
you need to call this function first.
Parameters
----------
recursive : bool, optional
Whether to localize the RNGs of the augmenter's children too.
Returns
-------
imgaug.augmenters.meta.Augmenter
Returns itself (with localized RNGs).
"""
if self.random_state.is_global_rng():
self.random_state = self.random_state.derive_rng_()
if recursive:
for lst in self.get_children_lists():
for child in lst:
child.localize_random_state_(recursive=recursive)
return self
# TODO adapt random_state -> rng
def copy_random_state(self, source, recursive=True, matching="position",
matching_tolerant=True, copy_determinism=False):
"""Copy the RNGs from a source augmenter sequence.
Parameters
----------
source : imgaug.augmenters.meta.Augmenter
See :func:`~imgaug.augmenters.meta.Augmenter.copy_random_state_`.
recursive : bool, optional
See :func:`~imgaug.augmenters.meta.Augmenter.copy_random_state_`.
matching : {'position', 'name'}, optional
See :func:`~imgaug.augmenters.meta.Augmenter.copy_random_state_`.
matching_tolerant : bool, optional
See :func:`~imgaug.augmenters.meta.Augmenter.copy_random_state_`.
copy_determinism : bool, optional
See :func:`~imgaug.augmenters.meta.Augmenter.copy_random_state_`.
Returns
-------
imgaug.augmenters.meta.Augmenter
Copy of the augmenter itself (with copied RNGs).
"""
aug = self.deepcopy()
aug.copy_random_state_(
source,
recursive=recursive,
matching=matching,
matching_tolerant=matching_tolerant,
copy_determinism=copy_determinism
)
return aug
def copy_random_state_(self, source, recursive=True, matching="position",
matching_tolerant=True, copy_determinism=False):
"""Copy the RNGs from a source augmenter sequence (in-place).
.. note::
The source augmenters are not allowed to use the global RNG.
Call
:func:`~imgaug.augmenters.meta.Augmenter.localize_random_state_`
once on the source to localize all random states.
Parameters
----------
source : imgaug.augmenters.meta.Augmenter
The source augmenter(s) from where to copy the RNG(s).
The source may have children (e.g. the source can be a
:class:`~imgaug.augmenters.meta.Sequential`).
recursive : bool, optional
Whether to copy the RNGs of the source augmenter *and*
all of its children (``True``) or just the source
augmenter (``False``).
matching : {'position', 'name'}, optional
Defines the matching mode to use during recursive copy.
This is used to associate source augmenters with target augmenters.
If ``position`` then the target and source sequences of augmenters
are turned into flattened lists and are associated based on
their list indices. If ``name`` then the target and source
augmenters are matched based on their names (i.e.
``augmenter.name``).
matching_tolerant : bool, optional
Whether to use tolerant matching between source and target
augmenters. If set to ``False``: Name matching will raise an
exception for any target augmenter which's name does not appear
among the source augmenters. Position matching will raise an
exception if source and target augmenter have an unequal number
of children.
copy_determinism : bool, optional
Whether to copy the ``deterministic`` attributes from source to
target augmenters too.
Returns
-------
imgaug.augmenters.meta.Augmenter
The augmenter itself.
"""
# Note: the target random states are localized, but the source random
# states don't have to be localized. That means that they can be
# the global random state. Worse, if copy_random_state() was called,
# the target random states would have different identities, but
# same states. If multiple target random states were the global random
# state, then after deepcopying them, they would all share the same
# identity that is different to the global random state. I.e., if the
# state of any random state of them is set in-place, it modifies the
# state of all other target random states (that were once global),
# but not the global random state.
# Summary: Use target = source.copy() here, instead of
# target.use_state_of_(source).
source_augs = (
[source] + source.get_all_children(flat=True)
if recursive
else [source])
target_augs = (
[self] + self.get_all_children(flat=True)
if recursive
else [self])
global_rs_exc_msg = (
"You called copy_random_state_() with a source that uses global "
"RNGs. Call localize_random_state_() on the source "
"first or initialize your augmenters with local random states, "
"e.g. via Dropout(..., random_state=1234).")
if matching == "name":
source_augs_dict = {aug.name: aug for aug in source_augs}
target_augs_dict = {aug.name: aug for aug in target_augs}
different_lengths = (
len(source_augs_dict) < len(source_augs)
or len(target_augs_dict) < len(target_augs))
if different_lengths:
ia.warn(
"Matching mode 'name' with recursive=True was chosen in "
"copy_random_state_, but either the source or target "
"augmentation sequence contains multiple augmenters with "
"the same name."
)
for name in target_augs_dict:
if name in source_augs_dict:
if source_augs_dict[name].random_state.is_global_rng():
raise Exception(global_rs_exc_msg)
# has to be copy(), see above
target_augs_dict[name].random_state = \
source_augs_dict[name].random_state.copy()
if copy_determinism:
target_augs_dict[name].deterministic = \
source_augs_dict[name].deterministic
elif not matching_tolerant:
raise Exception(
"Augmenter name '%s' not found among source "
"augmenters." % (name,))
elif matching == "position":
if len(source_augs) != len(target_augs) and not matching_tolerant:
raise Exception(
"Source and target augmentation sequences have different "
"lengths.")
for source_aug, target_aug in zip(source_augs, target_augs):
if source_aug.random_state.is_global_rng():
raise Exception(global_rs_exc_msg)
# has to be copy(), see above
target_aug.random_state = source_aug.random_state.copy()
if copy_determinism:
target_aug.deterministic = source_aug.deterministic
else:
raise Exception(
"Unknown matching method '%s'. Valid options are 'name' "
"and 'position'." % (matching,))
return self
@abstractmethod
def get_parameters(self):
"""Get the parameters of this augmenter.
Returns
-------
list
List of parameters of arbitrary types (usually child class
of :class:`~imgaug.parameters.StochasticParameter`, but not
guaranteed to be).
"""
raise NotImplementedError()
def get_children_lists(self):
"""Get a list of lists of children of this augmenter.
For most augmenters, the result will be a single empty list.
For augmenters with children it will often be a list with one
sublist containing all children. In some cases the augmenter will
contain multiple distinct lists of children, e.g. an if-list and an
else-list. This will lead to a result consisting of a single list
with multiple sublists, each representing the respective sublist of
children.
E.g. for an if/else-augmenter that executes the children ``A1``,
``A2`` if a condition is met and otherwise executes the children
``B1``, ``B2``, ``B3`` the result will be
``[[A1, A2], [B1, B2, B3]]``.
IMPORTANT: While the topmost list may be newly created, each of the
sublist must be editable inplace resulting in a changed children list
of the augmenter. E.g. if an Augmenter
``IfElse(condition, [A1, A2], [B1, B2, B3])`` returns
``[[A1, A2], [B1, B2, B3]]``
for a call to
:func:`~imgaug.augmenters.meta.Augmenter.get_children_lists` and
``A2`` is removed inplace from ``[A1, A2]``, then the children lists
of ``IfElse(...)`` must also change to ``[A1], [B1, B2, B3]``. This
is used in
:func:`~imgaug.augmeneters.meta.Augmenter.remove_augmenters_`.
Returns
-------
list of list of imgaug.augmenters.meta.Augmenter
One or more lists of child augmenter.
Can also be a single empty list.
"""
return []
# TODO why does this exist? it seems to be identical to
# get_children_lists() for flat=False, aside from returning list
# copies instead of the same instances as used by the augmenters.
# TODO this can be simplified using imgaug.imgaug.flatten()?
def get_all_children(self, flat=False):
"""Get all children of this augmenter as a list.
If the augmenter has no children, the returned list is empty.
Parameters
----------
flat : bool
If set to ``True``, the returned list will be flat.
Returns
-------
list of imgaug.augmenters.meta.Augmenter
The children as a nested or flat list.
"""
result = []
for lst in self.get_children_lists():
for aug in lst:
result.append(aug)
children = aug.get_all_children(flat=flat)
if len(children) > 0:
if flat:
result.extend(children)
else:
result.append(children)
return result
def find_augmenters(self, func, parents=None, flat=True):
"""Find augmenters that match a condition.
This function will compare this augmenter and all of its children
with a condition. The condition is a lambda function.
Parameters
----------
func : callable
A function that receives a
:class:`~imgaug.augmenters.meta.Augmenter` instance and a list of
parent :class:`~imgaug.augmenters.meta.Augmenter` instances and
must return ``True``, if that augmenter is valid match or
``False`` otherwise.
parents : None or list of imgaug.augmenters.meta.Augmenter, optional
List of parent augmenters.
Intended for nested calls and can usually be left as ``None``.
flat : bool, optional
Whether to return the result as a flat list (``True``)
or a nested list (``False``). In the latter case, the nesting
matches each augmenters position among the children.
Returns
----------
list of imgaug.augmenters.meta.Augmenter
Nested list if `flat` was set to ``False``.
Flat list if `flat` was set to ``True``.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Sequential([
>>> iaa.Fliplr(0.5, name="fliplr"),
>>> iaa.Flipud(0.5, name="flipud")
>>> ])
>>> print(aug.find_augmenters(lambda a, parents: a.name == "fliplr"))
Return the first child augmenter (``Fliplr`` instance).
"""
if parents is None:
parents = []
result = []
if func(self, parents):
result.append(self)
subparents = parents + [self]
for lst in self.get_children_lists():
for aug in lst:
found = aug.find_augmenters(func, parents=subparents,
flat=flat)
if len(found) > 0:
if flat:
result.extend(found)
else:
result.append(found)
return result
def find_augmenters_by_name(self, name, regex=False, flat=True):
"""Find augmenter(s) by name.
Parameters
----------
name : str
Name of the augmenter(s) to search for.
regex : bool, optional
Whether `name` parameter is a regular expression.
flat : bool, optional
See :func:`~imgaug.augmenters.meta.Augmenter.find_augmenters`.
Returns
-------
augmenters : list of imgaug.augmenters.meta.Augmenter
Nested list if `flat` was set to ``False``.
Flat list if `flat` was set to ``True``.
"""
return self.find_augmenters_by_names([name], regex=regex, flat=flat)
def find_augmenters_by_names(self, names, regex=False, flat=True):
"""Find augmenter(s) by names.
Parameters
----------
names : list of str
Names of the augmenter(s) to search for.
regex : bool, optional
Whether `names` is a list of regular expressions.
If it is, an augmenter is considered a match if *at least* one
of these expressions is a match.
flat : boolean, optional
See :func:`~imgaug.augmenters.meta.Augmenter.find_augmenters`.
Returns
-------
augmenters : list of imgaug.augmenters.meta.Augmenter
Nested list if `flat` was set to ``False``.
Flat list if `flat` was set to ``True``.
"""
if regex:
def comparer(aug, _parents):
for pattern in names:
if re.match(pattern, aug.name):
return True
return False
return self.find_augmenters(comparer, flat=flat)
return self.find_augmenters(
lambda aug, parents: aug.name in names, flat=flat)
# TODO remove copy arg
# TODO allow first arg to be string name, class type or func
def remove_augmenters(self, func, copy=True, identity_if_topmost=True,
noop_if_topmost=None):
"""Remove this augmenter or children that match a condition.
Parameters
----------
func : callable
Condition to match per augmenter.
The function must expect the augmenter itself and a list of parent
augmenters and returns ``True`` if that augmenter is supposed to
be removed, or ``False`` otherwise.
E.g. ``lambda a, parents: a.name == "fliplr" and len(parents) == 1``
removes an augmenter with name ``fliplr`` if it is the direct child
of the augmenter upon which ``remove_augmenters()`` was initially
called.
copy : bool, optional
Whether to copy this augmenter and all if its children before
removing. If ``False``, removal is performed in-place.
identity_if_topmost : bool, optional
If ``True`` and the condition (lambda function) leads to the
removal of the topmost augmenter (the one this function is called
on initially), then that topmost augmenter will be replaced by an
instance of :class:`~imgaug.augmenters.meta.Noop` (i.e. an
augmenter that doesn't change its inputs). If ``False``, ``None``
will be returned in these cases.
This can only be ``False`` if copy is set to ``True``.
noop_if_topmost : bool, optional
Deprecated since 0.4.0.
Returns
-------
imgaug.augmenters.meta.Augmenter or None
This augmenter after the removal was performed.
``None`` is returned if the condition was matched for the
topmost augmenter, `copy` was set to ``True`` and `noop_if_topmost`
was set to ``False``.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> seq = iaa.Sequential([
>>> iaa.Fliplr(0.5, name="fliplr"),
>>> iaa.Flipud(0.5, name="flipud"),
>>> ])
>>> seq = seq.remove_augmenters(lambda a, parents: a.name == "fliplr")
This removes the augmenter ``Fliplr`` from the ``Sequential``
object's children.
"""
if noop_if_topmost is not None:
ia.warn_deprecated("Parameter 'noop_if_topmost' is deprecated. "
"Use 'identity_if_topmost' instead.")
identity_if_topmost = noop_if_topmost
if func(self, []):
if not copy:
raise Exception(
"Inplace removal of topmost augmenter requested, "
"which is currently not possible. Set 'copy' to True.")
if identity_if_topmost:
return Identity()
return None
aug = self if not copy else self.deepcopy()
aug.remove_augmenters_(func, parents=[])
return aug
@ia.deprecated("remove_augmenters_")
def remove_augmenters_inplace(self, func, parents=None):
"""Old name for :func:`~imgaug.meta.Augmenter.remove_augmenters_`.
Deprecated since 0.4.0.
"""
self.remove_augmenters_(func=func, parents=parents)
# TODO allow first arg to be string name, class type or func
# TODO remove parents arg + add _remove_augmenters_() with parents arg
def remove_augmenters_(self, func, parents=None):
"""Remove in-place children of this augmenter that match a condition.
This is functionally identical to
:func:`~imgaug.augmenters.meta.remove_augmenters` with
``copy=False``, except that it does not affect the topmost augmenter
(the one on which this function is initially called on).
Added in 0.4.0.
Parameters
----------
func : callable
See :func:`~imgaug.augmenters.meta.Augmenter.remove_augmenters`.
parents : None or list of imgaug.augmenters.meta.Augmenter, optional
List of parent :class:`~imgaug.augmenters.meta.Augmenter` instances
that lead to this augmenter. If ``None``, an empty list will be
used. This parameter can usually be left empty and will be set
automatically for children.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> seq = iaa.Sequential([
>>> iaa.Fliplr(0.5, name="fliplr"),
>>> iaa.Flipud(0.5, name="flipud"),
>>> ])
>>> seq.remove_augmenters_(lambda a, parents: a.name == "fliplr")
This removes the augmenter ``Fliplr`` from the ``Sequential``
object's children.
"""
parents = [] if parents is None else parents
subparents = parents + [self]
for lst in self.get_children_lists():
to_remove = []
for i, aug in enumerate(lst):
if func(aug, subparents):
to_remove.append((i, aug))
for count_removed, (i, aug) in enumerate(to_remove):
del lst[i - count_removed]
for aug in lst:
aug.remove_augmenters_(func, subparents)
def copy(self):
"""Create a shallow copy of this Augmenter instance.
Returns
-------
imgaug.augmenters.meta.Augmenter
Shallow copy of this Augmenter instance.
"""
return copy_module.copy(self)
def deepcopy(self):
"""Create a deep copy of this Augmenter instance.
Returns
-------
imgaug.augmenters.meta.Augmenter
Deep copy of this Augmenter instance.
"""
# TODO if this augmenter has child augmenters and multiple of them
# use the global random state, then after copying, these
# augmenters share a single new random state that is a copy of
# the global random state (i.e. all use the same *instance*,
# not just state). This can lead to confusing bugs.
# TODO write a custom copying routine?
return copy_module.deepcopy(self)
def __repr__(self):
return self.__str__()
def __str__(self):
params = self.get_parameters()
params_str = ", ".join([param.__str__() for param in params])
return "%s(name=%s, parameters=[%s], deterministic=%s)" % (
self.__class__.__name__, self.name, params_str, self.deterministic)
class Sequential(Augmenter, list):
"""List augmenter containing child augmenters to apply to inputs.
This augmenter is simply a list of other augmenters. To augment an image
or any other data, it iterates over its children and applies each one
of them independently to the data. (This also means that the second
applied augmenter will already receive augmented input data and augment
it further.)
This augmenter offers the option to apply its children in random order
using the `random_order` parameter. This should often be activated as
it greatly increases the space of possible augmentations.
.. note::
You are *not* forced to use :class:`~imgaug.augmenters.meta.Sequential`
in order to use other augmenters. Each augmenter can be used on its
own, e.g the following defines an augmenter for horizontal flips and
then augments a single image:
>>> import numpy as np
>>> import imgaug.augmenters as iaa
>>> image = np.zeros((32, 32, 3), dtype=np.uint8)
>>> aug = iaa.Fliplr(0.5)
>>> image_aug = aug.augment_image(image)
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
The augmenters to apply to images.
random_order : bool, optional
Whether to apply the child augmenters in random order.
If ``True``, the order will be randomly sampled once per batch.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import numpy as np
>>> import imgaug.augmenters as iaa
>>> imgs = [np.random.rand(10, 10)]
>>> seq = iaa.Sequential([
>>> iaa.Fliplr(0.5),
>>> iaa.Flipud(0.5)
>>> ])
>>> imgs_aug = seq.augment_images(imgs)
Create a :class:`~imgaug.augmenters.meta.Sequential` that always first
applies a horizontal flip augmenter and then a vertical flip augmenter.
Each of these two augmenters has a ``50%`` probability of actually
flipping the image.
>>> seq = iaa.Sequential([
>>> iaa.Fliplr(0.5),
>>> iaa.Flipud(0.5)
>>> ], random_order=True)
>>> imgs_aug = seq.augment_images(imgs)
Create a :class:`~imgaug.augmenters.meta.Sequential` that sometimes first
applies a horizontal flip augmenter (followed by a vertical flip
augmenter) and sometimes first a vertical flip augmenter (followed by a
horizontal flip augmenter). Again, each of them has a ``50%`` probability
of actually flipping the image.
"""
def __init__(self, children=None, random_order=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
Augmenter.__init__(
self,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
if children is None:
list.__init__(self, [])
elif isinstance(children, Augmenter):
# this must be separate from `list.__init__(self, children)`,
# otherwise in `Sequential(OneOf(...))` the OneOf(...) is
# interpreted as a list and OneOf's children become Sequential's
# children
list.__init__(self, [children])
elif ia.is_iterable(children):
assert all([isinstance(child, Augmenter) for child in children]), (
"Expected all children to be augmenters, got types %s." % (
", ".join([str(type(v)) for v in children])))
list.__init__(self, children)
else:
raise Exception("Expected None or Augmenter or list of Augmenter, "
"got %s." % (type(children),))
assert ia.is_single_bool(random_order), (
"Expected random_order to be boolean, got %s." % (
type(random_order),))
self.random_order = random_order
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
with batch.propagation_hooks_ctx(self, hooks, parents):
if self.random_order:
order = random_state.permutation(len(self))
else:
order = sm.xrange(len(self))
for index in order:
batch = self[index].augment_batch_(
batch,
parents=parents + [self],
hooks=hooks
)
return batch
def _to_deterministic(self):
augs = [aug.to_deterministic() for aug in self]
seq = self.copy()
seq[:] = augs
seq.random_state = self.random_state.derive_rng_()
seq.deterministic = True
return seq
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.random_order]
def add(self, augmenter):
"""Add an augmenter to the list of child augmenters.
Parameters
----------
imgaug.augmenters.meta.Augmenter
The augmenter to add.
"""
self.append(augmenter)
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [self]
def __str__(self):
augs_str = ", ".join([aug.__str__() for aug in self])
pattern = (
"%s("
"name=%s, random_order=%s, children=[%s], deterministic=%s"
")")
return pattern % (
self.__class__.__name__, self.name, self.random_order, augs_str,
self.deterministic)
class SomeOf(Augmenter, list):
"""List augmenter that applies only some of its children to inputs.
This augmenter is similar to :class:`~imgaug.augmenters.meta.Sequential`,
but may apply only a fixed or random subset of its child augmenters to
inputs. E.g. the augmenter could be initialized with a list of 20 child
augmenters and then apply 5 randomly chosen child augmenters to images.
The subset of augmenters to apply (and their order) is sampled once
*per image*. If `random_order` is ``True``, the order will be sampled once
*per batch* (similar to :class:`~imgaug.augmenters.meta.Sequential`).
This augmenter currently does not support replacing (i.e. picking the same
child multiple times) due to implementation difficulties in connection
with deterministic augmenters.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
n : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or None, optional
Count of augmenters to apply.
* If ``int``, then exactly `n` of the child augmenters are applied
to every image.
* If tuple of two ``int`` s ``(a, b)``, then a random value will
be uniformly sampled per image from the discrete interval
``[a..b]`` and denote the number of child augmenters to pick
and apply. ``b`` may be set to ``None``, which is then equivalent
to ``(a..C)`` with ``C`` denoting the number of children that
the augmenter has.
* If ``StochasticParameter``, then ``N`` numbers will be sampled
for ``N`` images. The parameter is expected to be discrete.
* If ``None``, then the total number of available children will be
used (i.e. all children will be applied).
children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
The augmenters to apply to images.
If this is a list of augmenters, it will be converted to a
:class:`~imgaug.augmenters.meta.Sequential`.
random_order : boolean, optional
Whether to apply the child augmenters in random order.
If ``True``, the order will be randomly sampled once per batch.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> imgs = [np.random.rand(10, 10)]
>>> seq = iaa.SomeOf(1, [
>>> iaa.Fliplr(1.0),
>>> iaa.Flipud(1.0)
>>> ])
>>> imgs_aug = seq.augment_images(imgs)
Apply either ``Fliplr`` or ``Flipud`` to images.
>>> seq = iaa.SomeOf((1, 3), [
>>> iaa.Fliplr(1.0),
>>> iaa.Flipud(1.0),
>>> iaa.GaussianBlur(1.0)
>>> ])
>>> imgs_aug = seq.augment_images(imgs)
Apply one to three of the listed augmenters (``Fliplr``, ``Flipud``,
``GaussianBlur``) to images. They are always applied in the
provided order, i.e. first ``Fliplr``, second ``Flipud``, third
``GaussianBlur``.
>>> seq = iaa.SomeOf((1, None), [
>>> iaa.Fliplr(1.0),
>>> iaa.Flipud(1.0),
>>> iaa.GaussianBlur(1.0)
>>> ], random_order=True)
>>> imgs_aug = seq.augment_images(imgs)
Apply one to all of the listed augmenters (``Fliplr``, ``Flipud``,
``GaussianBlur``) to images. They are applied in random order, i.e.
sometimes ``GaussianBlur`` first, followed by ``Fliplr``, sometimes
``Fliplr`` followed by ``Flipud`` followed by ``Blur`` etc.
The order is sampled once per batch.
"""
def __init__(self, n=None, children=None, random_order=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
Augmenter.__init__(
self,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO use handle_children_list() here?
if children is None:
list.__init__(self, [])
elif isinstance(children, Augmenter):
# this must be separate from `list.__init__(self, children)`,
# otherwise in `SomeOf(OneOf(...))` the OneOf(...) is
# interpreted as a list and OneOf's children become SomeOf's
# children
list.__init__(self, [children])
elif ia.is_iterable(children):
assert all([isinstance(child, Augmenter) for child in children]), (
"Expected all children to be augmenters, got types %s." % (
", ".join([str(type(v)) for v in children])))
list.__init__(self, children)
else:
raise Exception("Expected None or Augmenter or list of Augmenter, "
"got %s." % (type(children),))
self.n, self.n_mode = self._handle_arg_n(n)
assert ia.is_single_bool(random_order), (
"Expected random_order to be boolean, got %s." % (
type(random_order),))
self.random_order = random_order
@classmethod
def _handle_arg_n(cls, n):
if ia.is_single_number(n):
n = int(n)
n_mode = "deterministic"
elif n is None:
n = None
n_mode = "None"
elif ia.is_iterable(n):
assert len(n) == 2, (
"Expected iterable 'n' to contain exactly two values, "
"got %d." % (len(n),))
if ia.is_single_number(n[0]) and n[1] is None:
n = (int(n[0]), None)
n_mode = "(int,None)"
elif ia.is_single_number(n[0]) and ia.is_single_number(n[1]):
n = iap.DiscreteUniform(int(n[0]), int(n[1]))
n_mode = "stochastic"
else:
raise Exception("Expected tuple of (int, None) or (int, int), "
"got %s" % ([type(el) for el in n],))
elif isinstance(n, iap.StochasticParameter):
n_mode = "stochastic"
else:
raise Exception("Expected int, (int, None), (int, int) or "
"StochasticParameter, got %s" % (type(n),))
return n, n_mode
def _get_n(self, nb_images, random_state):
if self.n_mode == "deterministic":
return [self.n] * nb_images
if self.n_mode == "None":
return [len(self)] * nb_images
if self.n_mode == "(int,None)":
param = iap.DiscreteUniform(self.n[0], len(self))
return param.draw_samples((nb_images,), random_state=random_state)
if self.n_mode == "stochastic":
return self.n.draw_samples((nb_images,), random_state=random_state)
raise Exception("Invalid n_mode: %s" % (self.n_mode,))
def _get_augmenter_order(self, random_state):
if not self.random_order:
augmenter_order = np.arange(len(self))
else:
augmenter_order = random_state.permutation(len(self))
return augmenter_order
def _get_augmenter_active(self, nb_rows, random_state):
# pylint: disable=invalid-name
nn = self._get_n(nb_rows, random_state)
nn = [min(n, len(self)) for n in nn]
augmenter_active = np.zeros((nb_rows, len(self)), dtype=np.bool)
for row_idx, n_true in enumerate(nn):
if n_true > 0:
augmenter_active[row_idx, 0:n_true] = 1
for row in augmenter_active:
random_state.shuffle(row)
return augmenter_active
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
with batch.propagation_hooks_ctx(self, hooks, parents):
# This must happen before creating the augmenter_active array,
# otherwise in case of determinism the number of augmented images
# would change the random_state's state, resulting in the order
# being dependent on the number of augmented images (and not be
# constant). By doing this first, the random state is always the
# same (when determinism is active), so the order is always the
# same.
augmenter_order = self._get_augmenter_order(random_state)
# create an array of active augmenters per image
# e.g.
# [[0, 0, 1],
# [1, 0, 1],
# [1, 0, 0]]
# would signal, that augmenter 3 is active for the first image,
# augmenter 1 and 3 for the 2nd image and augmenter 1 for the 3rd.
augmenter_active = self._get_augmenter_active(batch.nb_rows,
random_state)
for augmenter_index in augmenter_order:
active = augmenter_active[:, augmenter_index].nonzero()[0]
if len(active) > 0:
batch_sub = batch.subselect_rows_by_indices(active)
batch_sub = self[augmenter_index].augment_batch_(
batch_sub,
parents=parents + [self],
hooks=hooks
)
batch = batch.invert_subselect_rows_by_indices_(active,
batch_sub)
return batch
def _to_deterministic(self):
augs = [aug.to_deterministic() for aug in self]
seq = self.copy()
seq[:] = augs
seq.random_state = self.random_state.derive_rng_()
seq.deterministic = True
return seq
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.n]
def add(self, augmenter):
"""Add an augmenter to the list of child augmenters.
Parameters
----------
augmenter : imgaug.augmenters.meta.Augmenter
The augmenter to add.
"""
self.append(augmenter)
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [self]
def __str__(self):
augs_str = ", ".join([aug.__str__() for aug in self])
pattern = (
"%s("
"name=%s, n=%s, random_order=%s, augmenters=[%s], deterministic=%s"
")")
return pattern % (
self.__class__.__name__, self.name, str(self.n),
str(self.random_order), augs_str, self.deterministic)
class OneOf(SomeOf):
"""Augmenter that always executes exactly one of its children.
**Supported dtypes**:
See :class:`imgaug.augmenters.meta.SomeOf`.
Parameters
----------
children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter
The choices of augmenters to apply.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> images = [np.ones((10, 10), dtype=np.uint8)] # dummy example images
>>> seq = iaa.OneOf([
>>> iaa.Fliplr(1.0),
>>> iaa.Flipud(1.0)
>>> ])
>>> images_aug = seq.augment_images(images)
Flip each image either horizontally or vertically.
>>> images = [np.ones((10, 10), dtype=np.uint8)] # dummy example images
>>> seq = iaa.OneOf([
>>> iaa.Fliplr(1.0),
>>> iaa.Sequential([
>>> iaa.GaussianBlur(1.0),
>>> iaa.Dropout(0.05),
>>> iaa.AdditiveGaussianNoise(0.1*255)
>>> ]),
>>> iaa.Noop()
>>> ])
>>> images_aug = seq.augment_images(images)
Either flip each image horizontally, or add blur+dropout+noise or do
nothing.
"""
def __init__(self, children,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(OneOf, self).__init__(
n=1,
children=children,
random_order=False,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Sometimes(Augmenter):
"""Apply child augmenter(s) with a probability of `p`.
Let ``C`` be one or more child augmenters given to
:class:`~imgaug.augmenters.meta.Sometimes`.
Let ``p`` be the fraction of images (or other data) to augment.
Let ``I`` be the input images (or other data).
Let ``N`` be the number of input images (or other entities).
Then (on average) ``p*N`` images of ``I`` will be augmented using ``C``.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
p : float or imgaug.parameters.StochasticParameter, optional
Sets the probability with which the given augmenters will be applied to
input images/data. E.g. a value of ``0.5`` will result in ``50%`` of
all input images (or other augmentables) being augmented.
then_list : None or imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) to apply to `p%` percent of all images.
If this is a list of augmenters, it will be converted to a
:class:`~imgaug.augmenters.meta.Sequential`.
else_list : None or imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter, optional
Augmenter(s) to apply to ``(1-p)`` percent of all images.
These augmenters will be applied only when the ones in `then_list`
are *not* applied (either-or-relationship).
If this is a list of augmenters, it will be converted to a
:class:`~imgaug.augmenters.meta.Sequential`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Sometimes(0.5, iaa.GaussianBlur(0.3))
Apply ``GaussianBlur`` to ``50%`` of all input images.
>>> aug = iaa.Sometimes(0.5, iaa.GaussianBlur(0.3), iaa.Fliplr(1.0))
Apply ``GaussianBlur`` to ``50%`` of all input images. Apply ``Fliplr``
to the other ``50%`` of all input images.
"""
def __init__(self, p=0.5, then_list=None, else_list=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Sometimes, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.p = iap.handle_probability_param(p, "p")
self.then_list = handle_children_list(then_list, self.name, "then",
default=None)
self.else_list = handle_children_list(else_list, self.name, "else",
default=None)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
with batch.propagation_hooks_ctx(self, hooks, parents):
samples = self.p.draw_samples((batch.nb_rows,),
random_state=random_state)
# create lists/arrays of images for if and else lists (one for each)
# note that np.where returns tuple(array([0, 5, 9, ...])) or
# tuple(array([]))
indices_then_list = np.where(samples == 1)[0]
indices_else_list = np.where(samples == 0)[0]
indice_lists = [indices_then_list, indices_else_list]
augmenter_lists = [self.then_list, self.else_list]
# For then_list: collect augmentables to be processed by then_list
# augmenters, apply them to the list, then map back to the output
# list. Analogous for else_list.
# TODO maybe this would be easier if augment_*() accepted a list
# that can contain Nones
for indices, augmenters in zip(indice_lists, augmenter_lists):
if augmenters is not None and len(augmenters) > 0:
batch_sub = batch.subselect_rows_by_indices(indices)
batch_sub = augmenters.augment_batch_(
batch_sub,
parents=parents + [self],
hooks=hooks
)
batch = batch.invert_subselect_rows_by_indices_(indices,
batch_sub)
return batch
def _to_deterministic(self):
aug = self.copy()
aug.then_list = (aug.then_list.to_deterministic()
if aug.then_list is not None
else aug.then_list)
aug.else_list = (aug.else_list.to_deterministic()
if aug.else_list is not None
else aug.else_list)
aug.deterministic = True
aug.random_state = self.random_state.derive_rng_()
return aug
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.p]
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
result = []
if self.then_list is not None:
result.append(self.then_list)
if self.else_list is not None:
result.append(self.else_list)
return result
def __str__(self):
pattern = (
"%s("
"p=%s, name=%s, then_list=%s, else_list=%s, deterministic=%s"
")")
return pattern % (
self.__class__.__name__, self.p, self.name, self.then_list,
self.else_list, self.deterministic)
class WithChannels(Augmenter):
"""Apply child augmenters to specific channels.
Let ``C`` be one or more child augmenters given to this augmenter.
Let ``H`` be a list of channels.
Let ``I`` be the input images.
Then this augmenter will pick the channels ``H`` from each image
in ``I`` (resulting in new images) and apply ``C`` to them.
The result of the augmentation will be merged back into the original
images.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
channels : None or int or list of int, optional
Sets the channels to be extracted from each image.
If ``None``, all channels will be used. Note that this is not
stochastic - the extracted channels are always the same ones.
children : imgaug.augmenters.meta.Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
One or more augmenters to apply to images, after the channels
are extracted.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.WithChannels([0], iaa.Add(10))
Assuming input images are RGB, then this augmenter will add ``10`` only to
the first channel, i.e. it will make images appear more red.
"""
def __init__(self, channels=None, children=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(WithChannels, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO change this to a stochastic parameter
if channels is None:
self.channels = None
elif ia.is_single_integer(channels):
self.channels = [channels]
elif ia.is_iterable(channels):
only_ints = all([
ia.is_single_integer(channel) for channel in channels])
assert only_ints, (
"Expected integers as channels, got %s." % (
[type(channel) for channel in channels],))
self.channels = channels
else:
raise Exception("Expected None, int or list of ints as channels, "
"got %s." % (type(channels),))
self.children = handle_children_list(children, self.name, "then")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if self.channels is not None and len(self.channels) == 0:
return batch
with batch.propagation_hooks_ctx(self, hooks, parents):
batch_cp = batch.deepcopy()
if batch.images is not None:
batch.images = self._reduce_images_to_channels(batch.images)
# Note that we augment here all data, including non-image data
# for which less than 50% of the corresponding image channels
# were augmented. This is because (a) the system does not yet
# understand None as cell values and (b) decreasing the length
# of columns leads to potential RNG misalignments.
# We replace non-image data that was not supposed to be augmented
# further below.
batch = self.children.augment_batch_(
batch, parents=parents + [self], hooks=hooks)
# If the shapes changed we cannot insert the augmented channels
# into the existing ones as the shapes of the non-augmented
# channels are still the same.
if batch.images is not None:
self._assert_lengths_not_changed(batch.images, batch_cp.images)
self._assert_shapes_not_changed(batch.images, batch_cp.images)
self._assert_dtypes_not_changed(batch.images, batch_cp.images)
batch.images = self._recover_images_array(batch.images,
batch_cp.images)
for column in batch.columns:
if column.name != "images":
value_old = getattr(batch_cp, column.attr_name)
value = self._replace_unaugmented_cells(column.value,
value_old)
setattr(batch, column.attr_name, value)
if batch.images is not None:
batch.images = self._invert_reduce_images_to_channels(
batch.images, batch_cp.images)
return batch
# Added in 0.4.0.
@classmethod
def _assert_lengths_not_changed(cls, images_aug, images):
assert len(images_aug) == len(images), (
"Expected that number of images does not change during "
"augmentation, but got %d vs. originally %d images." % (
len(images_aug), len(images)))
# Added in 0.4.0.
@classmethod
def _assert_shapes_not_changed(cls, images_aug, images):
if ia.is_np_array(images_aug) and ia.is_np_array(images):
shapes_same = (images_aug.shape[1:3] == images.shape[1:3])
else:
shapes_same = all(
[image_aug.shape[0:2] == image.shape[0:2]
for image_aug, image
in zip(images_aug, images)])
assert shapes_same, (
"Heights/widths of images changed in WithChannels from "
"%s to %s, but expected to be the same." % (
str([image.shape[0:2] for image in images]),
str([image_aug.shape[0:2] for image_aug in images_aug]),
))
# Added in 0.4.0.
@classmethod
def _assert_dtypes_not_changed(cls, images_aug, images):
if ia.is_np_array(images_aug) and ia.is_np_array(images):
dtypes_same = (images_aug.dtype == images.dtype)
else:
dtypes_same = all([
image_aug.dtype == image.dtype
for image_aug, image
in zip(images_aug, images)
])
assert dtypes_same, (
"dtypes of images changed in WithChannels from "
"%s to %s, but expected to be the same." % (
str([image.dtype.name for image in images]),
str([image_aug.dtype.name for image_aug in images_aug]),
))
# Added in 0.4.0.
@classmethod
def _recover_images_array(cls, images_aug, images):
if ia.is_np_array(images):
return np.array(images_aug)
return images_aug
# Added in 0.4.0.
def _reduce_images_to_channels(self, images):
if self.channels is None:
return images
if ia.is_np_array(images):
return images[..., self.channels]
return [image[..., self.channels] for image in images]
# Added in 0.4.0.
def _invert_reduce_images_to_channels(self, images_aug, images):
if self.channels is None:
return images_aug
for image, image_aug in zip(images, images_aug):
image[..., self.channels] = image_aug
return images
# Added in 0.4.0.
def _replace_unaugmented_cells(self, augmentables_aug, augmentables):
if self.channels is None:
return augmentables_aug
nb_channels_to_aug = len(self.channels)
nb_channels_lst = [augm.shape[2] if len(augm.shape) > 2 else 1
for augm in augmentables]
# We use the augmented form of a non-image if at least 50% of the
# corresponding image's channels were augmented. Otherwise we use
# the unaugmented form.
fraction_augmented_lst = [nb_channels_to_aug/nb_channels
for nb_channels in nb_channels_lst]
result = [
(augmentable_aug if fraction_augmented >= 0.5 else augmentable)
for augmentable_aug, augmentable, fraction_augmented
in zip(augmentables_aug, augmentables, fraction_augmented_lst)]
return result
def _to_deterministic(self):
aug = self.copy()
aug.children = aug.children.to_deterministic()
aug.deterministic = True
aug.random_state = self.random_state.derive_rng_()
return aug
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.channels]
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [self.children]
def __str__(self):
pattern = (
"%s("
"channels=%s, name=%s, children=%s, deterministic=%s"
")")
return pattern % (self.__class__.__name__, self.channels, self.name,
self.children, self.deterministic)
class Identity(Augmenter):
"""Augmenter that does not change the input data.
This augmenter is useful e.g. during validation/testing as it allows
to re-use the training code without actually performing any augmentation.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Identity()
Create an augmenter that does not change inputs.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Identity, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
return batch
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return []
class Noop(Identity):
"""Alias for augmenter :class:`Identity`.
It is recommended to now use :class:`Identity`. :class:`Noop` might be
deprecated in the future.
**Supported dtypes**:
See :class:`~imgaug.augmenters.meta.Identity`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Noop()
Create an augmenter that does not change inputs.
"""
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Noop, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Lambda(Augmenter):
"""Augmenter that calls a lambda function for each input batch.
This is useful to add missing functions to a list of augmenters.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
func_images : None or callable, optional
The function to call for each batch of images.
It must follow the form::
function(images, random_state, parents, hooks)
and return the changed images (may be transformed in-place).
This is essentially the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_images`.
If this is ``None`` instead of a function, the images will not be
altered.
func_heatmaps : None or callable, optional
The function to call for each batch of heatmaps.
It must follow the form::
function(heatmaps, random_state, parents, hooks)
and return the changed heatmaps (may be transformed in-place).
This is essentially the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_heatmaps`.
If this is ``None`` instead of a function, the heatmaps will not be
altered.
func_segmentation_maps : None or callable, optional
The function to call for each batch of segmentation maps.
It must follow the form::
function(segmaps, random_state, parents, hooks)
and return the changed segmaps (may be transformed in-place).
This is essentially the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_segmentation_maps`.
If this is ``None`` instead of a function, the segmentatio maps will
not be altered.
func_keypoints : None or callable, optional
The function to call for each batch of keypoints.
It must follow the form::
function(keypoints_on_images, random_state, parents, hooks)
and return the changed keypoints (may be transformed in-place).
This is essentially the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_keypoints`.
If this is ``None`` instead of a function, the keypoints will not be
altered.
func_bounding_boxes : "keypoints" or None or callable, optional
The function to call for each batch of bounding boxes.
It must follow the form::
function(bounding_boxes_on_images, random_state, parents, hooks)
and return the changed bounding boxes (may be transformed in-place).
This is essentially the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_bounding_boxes`.
If this is ``None`` instead of a function, the bounding boxes will not
be altered.
If this is the string ``"keypoints"`` instead of a function, the
bounding boxes will automatically be augmented by transforming their
corner vertices to keypoints and calling `func_keypoints`.
Added in 0.4.0.
func_polygons : "keypoints" or None or callable, optional
The function to call for each batch of polygons.
It must follow the form::
function(polygons_on_images, random_state, parents, hooks)
and return the changed polygons (may be transformed in-place).
This is essentially the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_polygons`.
If this is ``None`` instead of a function, the polygons will not
be altered.
If this is the string ``"keypoints"`` instead of a function, the
polygons will automatically be augmented by transforming their
corner vertices to keypoints and calling `func_keypoints`.
func_line_strings : "keypoints" or None or callable, optional
The function to call for each batch of line strings.
It must follow the form::
function(line_strings_on_images, random_state, parents, hooks)
and return the changed line strings (may be transformed in-place).
This is essentially the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_line_strings`.
If this is ``None`` instead of a function, the line strings will not
be altered.
If this is the string ``"keypoints"`` instead of a function, the
line strings will automatically be augmented by transforming their
corner vertices to keypoints and calling `func_keypoints`.
Added in 0.4.0.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>>
>>> def func_images(images, random_state, parents, hooks):
>>> images[:, ::2, :, :] = 0
>>> return images
>>>
>>> aug = iaa.Lambda(
>>> func_images=func_images
>>> )
Replace every second row in input images with black pixels. Leave
other data (e.g. heatmaps, keypoints) unchanged.
>>> def func_images(images, random_state, parents, hooks):
>>> images[:, ::2, :, :] = 0
>>> return images
>>>
>>> def func_heatmaps(heatmaps, random_state, parents, hooks):
>>> for heatmaps_i in heatmaps:
>>> heatmaps.arr_0to1[::2, :, :] = 0
>>> return heatmaps
>>>
>>> def func_keypoints(keypoints_on_images, random_state, parents, hooks):
>>> return keypoints_on_images
>>>
>>> aug = iaa.Lambda(
>>> func_images=func_images,
>>> func_heatmaps=func_heatmaps,
>>> func_keypoints=func_keypoints
>>> )
Replace every second row in images with black pixels, set every second
row in heatmaps to zero and leave other data (e.g. keypoints)
unchanged.
"""
def __init__(self, func_images=None, func_heatmaps=None,
func_segmentation_maps=None, func_keypoints=None,
func_bounding_boxes="keypoints", func_polygons="keypoints",
func_line_strings="keypoints",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Lambda, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.func_images = func_images
self.func_heatmaps = func_heatmaps
self.func_segmentation_maps = func_segmentation_maps
self.func_keypoints = func_keypoints
self.func_bounding_boxes = func_bounding_boxes
self.func_polygons = func_polygons
self.func_line_strings = func_line_strings
def _augment_images(self, images, random_state, parents, hooks):
if self.func_images is not None:
return self.func_images(images, random_state, parents, hooks)
return images
def _augment_heatmaps(self, heatmaps, random_state, parents, hooks):
if self.func_heatmaps is not None:
result = self.func_heatmaps(heatmaps, random_state, parents, hooks)
assert ia.is_iterable(result), (
"Expected callback function for heatmaps to return list of "
"imgaug.HeatmapsOnImage instances, got %s." % (
type(result),))
only_heatmaps = all([
isinstance(el, ia.HeatmapsOnImage) for el in result])
assert only_heatmaps, (
"Expected callback function for heatmaps to return list of "
"imgaug.HeatmapsOnImage instances, got %s." % (
[type(el) for el in result],))
return result
return heatmaps
def _augment_segmentation_maps(self, segmaps, random_state, parents, hooks):
if self.func_segmentation_maps is not None:
result = self.func_segmentation_maps(segmaps, random_state,
parents, hooks)
assert ia.is_iterable(result), (
"Expected callback function for segmentation maps to return "
"list of imgaug.SegmentationMapsOnImage() instances, "
"got %s." % (type(result),))
only_segmaps = all([
isinstance(el, ia.SegmentationMapsOnImage) for el in result])
assert only_segmaps, (
"Expected callback function for segmentation maps to return "
"list of imgaug.SegmentationMapsOnImage() instances, "
"got %s." % ([type(el) for el in result],))
return result
return segmaps
def _augment_keypoints(self, keypoints_on_images, random_state, parents,
hooks):
if self.func_keypoints is not None:
result = self.func_keypoints(keypoints_on_images, random_state,
parents, hooks)
assert ia.is_iterable(result), (
"Expected callback function for keypoints to return list of "
"imgaug.augmentables.kps.KeypointsOnImage instances, "
"got %s." % (type(result),))
only_keypoints = all([
isinstance(el, ia.KeypointsOnImage) for el in result])
assert only_keypoints, (
"Expected callback function for keypoints to return list of "
"imgaug.augmentables.kps.KeypointsOnImage instances, "
"got %s." % ([type(el) for el in result],))
return result
return keypoints_on_images
def _augment_polygons(self, polygons_on_images, random_state, parents,
hooks):
from imgaug.augmentables.polys import _ConcavePolygonRecoverer
if self.func_polygons == "keypoints":
return self._augment_polygons_as_keypoints(
polygons_on_images, random_state, parents, hooks,
recoverer=_ConcavePolygonRecoverer())
if self.func_polygons is not None:
result = self.func_polygons(polygons_on_images, random_state,
parents, hooks)
assert ia.is_iterable(result), (
"Expected callback function for polygons to return list of "
"imgaug.augmentables.polys.PolygonsOnImage instances, "
"got %s." % (type(result),))
only_polygons = all([
isinstance(el, ia.PolygonsOnImage) for el in result])
assert only_polygons, (
"Expected callback function for polygons to return list of "
"imgaug.augmentables.polys.PolygonsOnImage instances, "
"got %s." % ([type(el) for el in result],))
return result
return polygons_on_images
# Added in 0.4.0.
def _augment_line_strings(self, line_strings_on_images, random_state,
parents, hooks):
if self.func_line_strings == "keypoints":
return self._augment_line_strings_as_keypoints(
line_strings_on_images, random_state, parents, hooks)
if self.func_line_strings is not None:
result = self.func_line_strings(line_strings_on_images,
random_state, parents, hooks)
assert ia.is_iterable(result), (
"Expected callback function for line strings to return list of "
"imgaug.augmentables.lines.LineStringsOnImage instances, "
"got %s." % (type(result),))
only_ls = all([
isinstance(el, ia.LineStringsOnImage) for el in result])
assert only_ls, (
"Expected callback function for line strings to return list of "
"imgaug.augmentables.lines.LineStringsOnImages instances, "
"got %s." % ([type(el) for el in result],))
return result
return line_strings_on_images
# Added in 0.4.0.
def _augment_bounding_boxes(self, bounding_boxes_on_images, random_state,
parents, hooks):
if self.func_bounding_boxes == "keypoints":
return self._augment_bounding_boxes_as_keypoints(
bounding_boxes_on_images, random_state, parents, hooks)
if self.func_bounding_boxes is not None:
result = self.func_bounding_boxes(
bounding_boxes_on_images, random_state, parents, hooks)
assert ia.is_iterable(result), (
"Expected callback function for bounding boxes to return list "
"of imgaug.augmentables.bbs.BoundingBoxesOnImage instances, "
"got %s." % (type(result),))
only_bbs = all([
isinstance(el, ia.BoundingBoxesOnImage) for el in result])
assert only_bbs, (
"Expected callback function for polygons to return list of "
"imgaug.augmentables.polys.PolygonsOnImage instances, "
"got %s." % ([type(el) for el in result],))
for bboi in bounding_boxes_on_images:
for bb in bboi.bounding_boxes:
if bb.x1 > bb.x2:
bb.x1, bb.x2 = bb.x2, bb.x1
if bb.y1 > bb.y2:
bb.y1, bb.y2 = bb.y2, bb.y1
return result
return bounding_boxes_on_images
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return []
class AssertLambda(Lambda):
"""Assert conditions based on lambda-function to be the case for input data.
This augmenter applies a lambda function to each image or other input.
The lambda function must return ``True`` or ``False``. If ``False`` is
returned, an assertion error is produced.
This is useful to ensure that generic assumption about the input data
are actually the case and error out early otherwise.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
func_images : None or callable, optional
The function to call for each batch of images.
It must follow the form::
function(images, random_state, parents, hooks)
and return either ``True`` (valid input) or ``False`` (invalid input).
It essentially re-uses the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_images`.
func_heatmaps : None or callable, optional
The function to call for each batch of heatmaps.
It must follow the form::
function(heatmaps, random_state, parents, hooks)
and return either ``True`` (valid input) or ``False`` (invalid input).
It essentially re-uses the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_heatmaps`.
func_segmentation_maps : None or callable, optional
The function to call for each batch of segmentation maps.
It must follow the form::
function(segmaps, random_state, parents, hooks)
and return either ``True`` (valid input) or ``False`` (invalid input).
It essentially re-uses the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_segmentation_maps`.
func_keypoints : None or callable, optional
The function to call for each batch of keypoints.
It must follow the form::
function(keypoints_on_images, random_state, parents, hooks)
and return either ``True`` (valid input) or ``False`` (invalid input).
It essentially re-uses the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_keypoints`.
func_bounding_boxes : None or callable, optional
The function to call for each batch of bounding boxes.
It must follow the form::
function(bounding_boxes_on_images, random_state, parents, hooks)
and return either ``True`` (valid input) or ``False`` (invalid input).
It essentially re-uses the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_bounding_boxes`.
Added in 0.4.0.
func_polygons : None or callable, optional
The function to call for each batch of polygons.
It must follow the form::
function(polygons_on_images, random_state, parents, hooks)
and return either ``True`` (valid input) or ``False`` (invalid input).
It essentially re-uses the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_polygons`.
func_line_strings : None or callable, optional
The function to call for each batch of line strings.
It must follow the form::
function(line_strings_on_images, random_state, parents, hooks)
and return either ``True`` (valid input) or ``False`` (invalid input).
It essentially re-uses the interface of
:func:`~imgaug.augmenters.meta.Augmenter._augment_line_strings`.
Added in 0.4.0.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
"""
def __init__(self, func_images=None, func_heatmaps=None,
func_segmentation_maps=None, func_keypoints=None,
func_bounding_boxes=None, func_polygons=None,
func_line_strings=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
def _default(var, augmentable_name):
return (
_AssertLambdaCallback(var, augmentable_name=augmentable_name)
if var is not None
else None
)
super(AssertLambda, self).__init__(
func_images=_default(func_images, "images"),
func_heatmaps=_default(func_heatmaps, "heatmaps"),
func_segmentation_maps=_default(func_segmentation_maps,
"segmentation_maps"),
func_keypoints=_default(func_keypoints, "keypoints"),
func_bounding_boxes=_default(func_bounding_boxes, "bounding_boxes"),
func_polygons=_default(func_polygons, "polygons"),
func_line_strings=_default(func_line_strings, "line_strings"),
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
class _AssertLambdaCallback(object):
# Added in 0.4.0.
def __init__(self, func, augmentable_name):
self.func = func
self.augmentable_name = augmentable_name
# Added in 0.4.0.
def __call__(self, augmentables, random_state, parents, hooks):
assert self.func(augmentables, random_state, parents, hooks), (
"Input %s did not fulfill user-defined assertion in "
"AssertLambda." % (self.augmentable_name,))
return augmentables
# TODO add tests for segmaps
# TODO This evaluates .shape for kps/polys, but the array shape for
# heatmaps/segmaps. Not very consistent.
class AssertShape(Lambda):
"""Assert that inputs have a specified shape.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
shape : tuple
The expected shape, given as a ``tuple``. The number of entries in
the ``tuple`` must match the number of dimensions, i.e. it must
contain four entries for ``(N, H, W, C)``. If only a single entity
is augmented, e.g. via
:func:`~imgaug.augmenters.meta.Augmenter.augment_image`, then ``N`` is
``1`` in the input to this augmenter. Images that don't have
a channel axis will automatically have one assigned, i.e. ``C`` is
at least ``1``.
For each component of the ``tuple`` one of the following datatypes
may be used:
* If a component is ``None``, any value for that dimensions is
accepted.
* If a component is ``int``, exactly that value (and no other one)
will be accepted for that dimension.
* If a component is a ``tuple`` of two ``int`` s with values ``a``
and ``b``, only a value within the interval ``[a, b)`` will be
accepted for that dimension.
* If an entry is a ``list`` of ``int`` s, only a value from that
``list`` will be accepted for that dimension.
check_images : bool, optional
Whether to validate input images via the given shape.
check_heatmaps : bool, optional
Whether to validate input heatmaps via the given shape.
The number of heatmaps will be verified as ``N``. For each
:class:`~imgaug.augmentables.heatmaps.HeatmapsOnImage` instance
its array's height and width will be verified as ``H`` and ``W``,
but not the channel count.
check_segmentation_maps : bool, optional
Whether to validate input segmentation maps via the given shape.
The number of segmentation maps will be verified as ``N``. For each
:class:`~imgaug.augmentables.segmaps.SegmentationMapOnImage` instance
its array's height and width will be verified as ``H`` and ``W``,
but not the channel count.
check_keypoints : bool, optional
Whether to validate input keypoints via the given shape.
This will check (a) the number of keypoints and (b) for each
:class:`~imgaug.augmentables.kps.KeypointsOnImage` instance the
``.shape`` attribute, i.e. the shape of the corresponding image.
check_bounding_boxes : bool, optional
Whether to validate input bounding boxes via the given shape.
This will check (a) the number of bounding boxes and (b) for each
:class:`~imgaug.augmentables.bbs.BoundingBoxesOnImage` instance the
``.shape`` attribute, i.e. the shape of the corresponding image.
Added in 0.4.0.
check_polygons : bool, optional
Whether to validate input polygons via the given shape.
This will check (a) the number of polygons and (b) for each
:class:`~imgaug.augmentables.polys.PolygonsOnImage` instance the
``.shape`` attribute, i.e. the shape of the corresponding image.
check_line_strings : bool, optional
Whether to validate input line strings via the given shape.
This will check (a) the number of line strings and (b) for each
:class:`~imgaug.augmentables.lines.LineStringsOnImage` instance the
``.shape`` attribute, i.e. the shape of the corresponding image.
Added in 0.4.0.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> seq = iaa.Sequential([
>>> iaa.AssertShape((None, 32, 32, 3)),
>>> iaa.Fliplr(0.5)
>>> ])
Verify first for each image batch if it contains a variable number of
``32x32`` images with ``3`` channels each. Only if that check succeeds, the
horizontal flip will be executed. Otherwise an assertion error will be
raised.
>>> seq = iaa.Sequential([
>>> iaa.AssertShape((None, (32, 64), 32, [1, 3])),
>>> iaa.Fliplr(0.5)
>>> ])
Similar to the above example, but now the height may be in the interval
``[32, 64)`` and the number of channels may be either ``1`` or ``3``.
"""
def __init__(self, shape, check_images=True, check_heatmaps=True,
check_segmentation_maps=True, check_keypoints=True,
check_bounding_boxes=True, check_polygons=True,
check_line_strings=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
assert len(shape) == 4, (
"Expected shape to have length 4, got %d with shape: %s." % (
len(shape), str(shape)))
self.shape = shape
def _default(func, do_use):
return func if do_use else None
super(AssertShape, self).__init__(
func_images=_default(_AssertShapeImagesCheck(shape),
check_images),
func_heatmaps=_default(_AssertShapeHeatmapsCheck(shape),
check_heatmaps),
func_segmentation_maps=_default(_AssertShapeSegmapCheck(shape),
check_segmentation_maps),
func_keypoints=_default(_AssertShapeKeypointsCheck(shape),
check_keypoints),
func_bounding_boxes=_default(_AssertShapeBoundingBoxesCheck(shape),
check_bounding_boxes),
func_polygons=_default(_AssertShapePolygonsCheck(shape),
check_polygons),
func_line_strings=_default(_AssertShapeLineStringsCheck(shape),
check_line_strings),
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
@classmethod
def _compare(cls, observed, expected, dimension, image_index):
if expected is not None:
if ia.is_single_integer(expected):
assert observed == expected, (
"Expected dim %d (entry index: %s) to have value %d, "
"got %d." % (dimension, image_index, expected,
observed))
elif isinstance(expected, tuple):
assert len(expected) == 2, (
"Expected tuple argument 'expected' to contain "
"exactly 2 entries, got %d." % (len(expected),))
assert expected[0] <= observed < expected[1], (
"Expected dim %d (entry index: %s) to have value in "
"interval [%d, %d), got %d." % (
dimension, image_index, expected[0], expected[1],
observed))
elif isinstance(expected, list):
assert any([observed == val for val in expected]), (
"Expected dim %d (entry index: %s) to have any value "
"of %s, got %d." % (
dimension, image_index, str(expected), observed))
else:
raise Exception(
"Invalid datatype for shape entry %d, expected each "
"entry to be an integer, a tuple (with two entries) "
"or a list, got %s." % (dimension, type(expected),))
@classmethod
def _check_shapes(cls, shapes, shape_target):
if shape_target[0] is not None:
cls._compare(len(shapes), shape_target[0], 0, "ALL")
for augm_idx, shape in enumerate(shapes):
# note that dim_idx is here per object, dim 0 of shape target
# denotes "number of all objects" and was checked above
for dim_idx, expected in enumerate(shape_target[1:]):
observed = shape[dim_idx]
cls._compare(observed, expected, dim_idx, augm_idx)
# turning these checks below into classmethods of AssertShape breaks pickling
# in python 2.7
# Added in 0.4.0.
class _AssertShapeImagesCheck(object):
def __init__(self, shape):
self.shape = shape
def __call__(self, images, _random_state, _parents, _hooks):
# set shape_target so that we check all target dimensions,
# including C, which isn't checked for the other methods
AssertShape._check_shapes([obj.shape for obj in images],
self.shape)
return images
# Added in 0.4.0.
class _AssertShapeHeatmapsCheck(object):
def __init__(self, shape):
self.shape = shape
def __call__(self, heatmaps, _random_state, _parents, _hooks):
AssertShape._check_shapes([obj.arr_0to1.shape for obj in heatmaps],
self.shape[0:3])
return heatmaps
# Added in 0.4.0.
class _AssertShapeSegmapCheck(object):
def __init__(self, shape):
self.shape = shape
def __call__(self, segmaps, _random_state, _parents, _hooks):
AssertShape._check_shapes([obj.arr.shape for obj in segmaps],
self.shape[0:3])
return segmaps
# Added in 0.4.0.
class _AssertShapeKeypointsCheck(object):
def __init__(self, shape):
self.shape = shape
def __call__(self, keypoints_on_images, _random_state, _parents, _hooks):
AssertShape._check_shapes([obj.shape for obj in keypoints_on_images],
self.shape[0:3])
return keypoints_on_images
# Added in 0.4.0.
class _AssertShapeBoundingBoxesCheck(object):
def __init__(self, shape):
self.shape = shape
def __call__(self, bounding_boxes_on_images, _random_state, _parents,
_hooks):
AssertShape._check_shapes([obj.shape for obj
in bounding_boxes_on_images],
self.shape[0:3])
return bounding_boxes_on_images
# Added in 0.4.0.
class _AssertShapePolygonsCheck(object):
def __init__(self, shape):
self.shape = shape
def __call__(self, polygons_on_images, _random_state, _parents, _hooks):
AssertShape._check_shapes([obj.shape for obj in polygons_on_images],
self.shape[0:3])
return polygons_on_images
# Added in 0.4.0.
class _AssertShapeLineStringsCheck(object):
def __init__(self, shape):
self.shape = shape
def __call__(self, line_strings_on_images, _random_state, _parents,
_hooks):
AssertShape._check_shapes([obj.shape for obj
in line_strings_on_images],
self.shape[0:3])
return line_strings_on_images
class ChannelShuffle(Augmenter):
"""Randomize the order of channels in input images.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
p : float or imgaug.parameters.StochasticParameter, optional
Probability of shuffling channels in any given image.
May be a fixed probability as a ``float``, or a
:class:`~imgaug.parameters.StochasticParameter` that returns ``0`` s
and ``1`` s.
channels : None or imgaug.ALL or list of int, optional
Which channels are allowed to be shuffled with each other.
If this is ``None`` or ``imgaug.ALL``, then all channels may be
shuffled. If it is a ``list`` of ``int`` s,
then only the channels with indices in that list may be shuffled.
(Values start at ``0``. All channel indices in the list must exist in
each image.)
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.ChannelShuffle(0.35)
Shuffle all channels of ``35%`` of all images.
>>> aug = iaa.ChannelShuffle(0.35, channels=[0, 1])
Shuffle only channels ``0`` and ``1`` of ``35%`` of all images. As the new
channel orders ``0, 1`` and ``1, 0`` are both valid outcomes of the
shuffling, it means that for ``0.35 * 0.5 = 0.175`` or ``17.5%`` of all
images the order of channels ``0`` and ``1`` is inverted.
"""
def __init__(self, p=1.0, channels=None,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ChannelShuffle, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.p = iap.handle_probability_param(p, "p")
valid_channels = (
channels is None
or channels == ia.ALL
or (
isinstance(channels, list)
and all([ia.is_single_integer(v) for v in channels])
))
assert valid_channels, (
"Expected None or imgaug.ALL or list of int, got %s." % (
type(channels),))
self.channels = channels
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
nb_images = len(images)
p_samples = self.p.draw_samples((nb_images,),
random_state=random_state)
rss = random_state.duplicate(nb_images)
for i, (image, p_i, rs) in enumerate(zip(images, p_samples, rss)):
if p_i >= 1-1e-4:
batch.images[i] = shuffle_channels(image, rs, self.channels)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.p, self.channels]
def shuffle_channels(image, random_state, channels=None):
"""Randomize the order of (color) channels in an image.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; indirectly tested (1)
* ``uint32``: yes; indirectly tested (1)
* ``uint64``: yes; indirectly tested (1)
* ``int8``: yes; indirectly tested (1)
* ``int16``: yes; indirectly tested (1)
* ``int32``: yes; indirectly tested (1)
* ``int64``: yes; indirectly tested (1)
* ``float16``: yes; indirectly tested (1)
* ``float32``: yes; indirectly tested (1)
* ``float64``: yes; indirectly tested (1)
* ``float128``: yes; indirectly tested (1)
* ``bool``: yes; indirectly tested (1)
- (1) Indirectly tested via :class:`ChannelShuffle`.
Parameters
----------
image : (H,W,[C]) ndarray
Image of any dtype for which to shuffle the channels.
random_state : imgaug.random.RNG
The random state to use for this shuffling operation.
channels : None or imgaug.ALL or list of int, optional
Which channels are allowed to be shuffled with each other.
If this is ``None`` or ``imgaug.ALL``, then all channels may be
shuffled. If it is a ``list`` of ``int`` s,
then only the channels with indices in that list may be shuffled.
(Values start at ``0``. All channel indices in the list must exist in
the image.)
Returns
-------
ndarray
The input image with shuffled channels.
"""
if image.ndim < 3 or image.shape[2] == 1:
return image
nb_channels = image.shape[2]
all_channels = np.arange(nb_channels)
is_all_channels = (
channels is None
or channels == ia.ALL
or len(set(all_channels).difference(set(channels))) == 0
)
if is_all_channels:
# note that if this is the case, then 'channels' may be None or
# imgaug.ALL, so don't simply move the assignment outside of the
# if/else
channels_perm = random_state.permutation(all_channels)
return image[..., channels_perm]
channels_perm = random_state.permutation(channels)
channels_perm_full = all_channels
for channel_source, channel_target in zip(channels, channels_perm):
channels_perm_full[channel_source] = channel_target
return image[..., channels_perm_full]
class RemoveCBAsByOutOfImageFraction(Augmenter):
"""Remove coordinate-based augmentables exceeding an out of image fraction.
This augmenter inspects all coordinate-based augmentables (e.g.
bounding boxes, line strings) within a given batch and removes any such
augmentable which's out of image fraction is exactly a given value or
greater than that. The out of image fraction denotes the fraction of the
augmentable's area that is outside of the image, e.g. for a bounding box
that has half of its area outside of the image it would be ``0.5``.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; fully tested
* ``uint32``: yes; fully tested
* ``uint64``: yes; fully tested
* ``int8``: yes; fully tested
* ``int16``: yes; fully tested
* ``int32``: yes; fully tested
* ``int64``: yes; fully tested
* ``float16``: yes; fully tested
* ``float32``: yes; fully tested
* ``float64``: yes; fully tested
* ``float128``: yes; fully tested
* ``bool``: yes; fully tested
Parameters
----------
fraction : number
Remove any augmentable for which ``fraction_{actual} >= fraction``,
where ``fraction_{actual}`` denotes the estimated out of image
fraction.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Sequential([
>>> iaa.Affine(translate_px={"x": (-100, 100)}),
>>> iaa.RemoveCBAsByOutOfImageFraction(0.5)
>>> ])
Translate all inputs by ``-100`` to ``100`` pixels on the x-axis, then
remove any coordinate-based augmentable (e.g. bounding boxes) which has
at least ``50%`` of its area outside of the image plane.
>>> import imgaug as ia
>>> import imgaug.augmenters as iaa
>>> image = ia.quokka_square((100, 100))
>>> bb = ia.BoundingBox(x1=50-25, y1=0, x2=50+25, y2=100)
>>> bbsoi = ia.BoundingBoxesOnImage([bb], shape=image.shape)
>>> aug_without = iaa.Affine(translate_px={"x": 51})
>>> aug_with = iaa.Sequential([
>>> iaa.Affine(translate_px={"x": 51}),
>>> iaa.RemoveCBAsByOutOfImageFraction(0.5)
>>> ])
>>>
>>> image_without, bbsoi_without = aug_without(
>>> image=image, bounding_boxes=bbsoi)
>>> image_with, bbsoi_with = aug_with(
>>> image=image, bounding_boxes=bbsoi)
>>>
>>> assert len(bbsoi_without.bounding_boxes) == 1
>>> assert len(bbsoi_with.bounding_boxes) == 0
Create a bounding box on an example image, then translate the image so that
``50%`` of the bounding box's area is outside of the image and compare
the effects and using ``RemoveCBAsByOutOfImageFraction`` with not using it.
"""
# Added in 0.4.0.
def __init__(self, fraction,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(RemoveCBAsByOutOfImageFraction, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.fraction = fraction
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
for column in batch.columns:
if column.name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
for i, cbaoi in enumerate(column.value):
column.value[i] = cbaoi.remove_out_of_image_fraction_(
self.fraction)
return batch
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.fraction]
class ClipCBAsToImagePlanes(Augmenter):
"""Clip coordinate-based augmentables to areas within the image plane.
This augmenter inspects all coordinate-based augmentables (e.g.
bounding boxes, line strings) within a given batch and from each of them
parts that are outside of the image plane. Parts within the image plane
will be retained. This may e.g. shrink down bounding boxes. For keypoints,
it removes any single points outside of the image plane. Any augmentable
that is completely outside of the image plane will be removed.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; fully tested
* ``uint32``: yes; fully tested
* ``uint64``: yes; fully tested
* ``int8``: yes; fully tested
* ``int16``: yes; fully tested
* ``int32``: yes; fully tested
* ``int64``: yes; fully tested
* ``float16``: yes; fully tested
* ``float32``: yes; fully tested
* ``float64``: yes; fully tested
* ``float128``: yes; fully tested
* ``bool``: yes; fully tested
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Sequential([
>>> iaa.Affine(translate_px={"x": (-100, 100)}),
>>> iaa.ClipCBAsToImagePlanes()
>>> ])
Translate input data on the x-axis by ``-100`` to ``100`` pixels,
then cut all coordinate-based augmentables (e.g. bounding boxes) down
to areas that are within the image planes of their corresponding images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(ClipCBAsToImagePlanes, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
for column in batch.columns:
if column.name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
for i, cbaoi in enumerate(column.value):
column.value[i] = cbaoi.clip_out_of_image_()
return batch
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return []
================================================
FILE: imgaug/augmenters/overlay.py
================================================
"""Alias for module blend.
Deprecated module. Original name for module blend.py. Was changed in 0.2.8.
"""
from __future__ import print_function, division, absolute_import
import imgaug as ia
from . import blend
_DEPRECATION_COMMENT = (
"It has the same interface, except that the parameter "
"`first` was renamed to `foreground` and the parameter "
"`second` to `background`."
)
@ia.deprecated(alt_func="imgaug.augmenters.blend.blend_alpha()",
comment=_DEPRECATION_COMMENT)
def blend_alpha(*args, **kwargs):
"""See :func:`~imgaug.augmenters.blend.blend_alpha`."""
# pylint: disable=invalid-name
return blend.blend_alpha(*args, **kwargs)
@ia.deprecated(alt_func="imgaug.augmenters.blend.BlendAlpha",
comment=_DEPRECATION_COMMENT)
def Alpha(*args, **kwargs):
"""See :class:`~imgaug.augmenters.blend.BlendAlpha`."""
# pylint: disable=invalid-name
return blend.Alpha(*args, **kwargs)
@ia.deprecated(alt_func="imgaug.augmenters.blend.BlendAlphaElementwise",
comment=_DEPRECATION_COMMENT)
def AlphaElementwise(*args, **kwargs):
"""See :class:`~imgaug.augmenters.blend.BlendAlphaElementwise`."""
# pylint: disable=invalid-name
return blend.AlphaElementwise(*args, **kwargs)
@ia.deprecated(alt_func="imgaug.augmenters.blend.BlendAlphaSimplexNoise",
comment=_DEPRECATION_COMMENT)
def SimplexNoiseAlpha(*args, **kwargs):
"""See :class:`~imgaug.augmenters.blend.BlendAlphaSimplexNoise`."""
# pylint: disable=invalid-name
return blend.SimplexNoiseAlpha(*args, **kwargs)
@ia.deprecated(alt_func="imgaug.augmenters.blend.BlendAlphaFrequencyNoise",
comment=_DEPRECATION_COMMENT)
def FrequencyNoiseAlpha(*args, **kwargs):
"""See :class:`~imgaug.augmenters.blend.BlendAlphaFrequencyNoise`."""
# pylint: disable=invalid-name
return blend.FrequencyNoiseAlpha(*args, **kwargs)
================================================
FILE: imgaug/augmenters/pillike.py
================================================
"""
Augmenters that have identical outputs to well-known PIL functions.
The ``like`` in ``pillike`` indicates that the augmenters in this module
have identical outputs and mostly identical inputs to corresponding PIL
functions, but do not *have to* wrap these functions internally. They may
use internally different (e.g. faster) techniques to produce these outputs.
Some of the augmenters in this module may also exist in other modules
under similar name. These other augmenters may currently have the same
outputs as the corresponding PIL functions, but that is not guaranteed
for the future. Use the augmenters in this module if identical outputs
to PIL are required.
List of augmenters:
* :class:`Solarize`
* :class:`Posterize`
* :class:`Equalize`
* :class:`Autocontrast`
* :class:`EnhanceColor`
* :class:`EnhanceContrast`
* :class:`EnhanceBrightness`
* :class:`EnhanceSharpness`
* :class:`FilterBlur`
* :class:`FilterSmooth`
* :class:`FilterSmoothMore`
* :class:`FilterEdgeEnhance`
* :class:`FilterEdgeEnhanceMore`
* :class:`FilterFindEdges`
* :class:`FilterContour`
* :class:`FilterEmboss`
* :class:`FilterSharpen`
* :class:`FilterDetail`
* :class:`Affine`
Standard usage of these augmenters follows roughly the schema::
import numpy as np
import imgaug.augmenters as iaa
aug = iaa.pillike.Affine(translate_px={"x": (-5, 5)})
image = np.full((32, 32, 3), 255, dtype=np.uint8)
images_aug = aug(images=[image, image, image])
Added in 0.4.0.
"""
from __future__ import print_function, division, absolute_import
import six.moves as sm
import numpy as np
import cv2
import PIL.Image
import PIL.ImageOps
import PIL.ImageEnhance
import PIL.ImageFilter
import imgaug as ia
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from . import arithmetic
from . import color as colorlib
from . import contrast as contrastlib
from . import geometric
from . import size as sizelib
from .. import parameters as iap
from .. import dtypes as iadt
# TODO some of the augmenters in this module broke on numpy arrays as
# image inputs (as opposed to lists of arrays) without any test failing
# add appropriate tests for that
_EQUALIZE_USE_PIL_BELOW = 64*64 # H*W
# Added in 0.4.0.
def _ensure_valid_shape(image, func_name):
is_hw1 = image.ndim == 3 and image.shape[-1] == 1
if is_hw1:
image = image[:, :, 0]
assert (
image.ndim == 2
or (image.ndim == 3 and image.shape[-1] in [3, 4])
), (
"Can apply %s only to images of "
"shape (H, W) or (H, W, 1) or (H, W, 3) or (H, W, 4). "
"Got shape %s." % (func_name, image.shape,))
return image, is_hw1
def solarize_(image, threshold=128):
"""Invert all array components above a threshold in-place.
This function has identical outputs to ``PIL.ImageOps.solarize``.
It does however work in-place.
Added in 0.4.0.
**Supported dtypes**:
See ``~imgaug.augmenters.arithmetic.invert_(min_value=None and max_value=None)``.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
The array *might* be modified in-place.
threshold : int, optional
A threshold to use in order to invert only numbers above or below
the threshold.
Returns
-------
ndarray
Inverted image.
This *can* be the same array as input in `image`, modified in-place.
"""
return arithmetic.invert_(image, threshold=threshold)
def solarize(image, threshold=128):
"""Invert all array components above a threshold.
This function has identical outputs to ``PIL.ImageOps.solarize``.
Added in 0.4.0.
**Supported dtypes**:
See ``~imgaug.augmenters.arithmetic.invert_(min_value=None and max_value=None)``.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
threshold : int, optional
A threshold to use in order to invert only numbers above or below
the threshold.
Returns
-------
ndarray
Inverted image.
"""
return arithmetic.invert(image, threshold=threshold)
def posterize_(image, bits):
"""Reduce the number of bits for each color channel in-place.
This function has identical outputs to ``PIL.ImageOps.posterize``.
It does however work in-place.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.quantize_uniform_to_n_bits_`.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
bits : int
The number of bits to keep per component.
Values in the interval ``[1, 8]`` are valid.
Returns
-------
ndarray
Posterized image.
This *can* be the same array as input in `image`, modified in-place.
"""
return colorlib.posterize(image, bits)
def posterize(image, bits):
"""Reduce the number of bits for each color channel.
This function has identical outputs to ``PIL.ImageOps.posterize``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.color.quantize_uniform_to_n_bits`.
Parameters
----------
image : ndarray
Image array of shape ``(H,W,[C])``.
bits : int
The number of bits to keep per component.
Values in the interval ``[1, 8]`` are valid.
Returns
-------
ndarray
Posterized image.
"""
return colorlib.posterize(image, bits)
def equalize(image, mask=None):
"""Equalize the image histogram.
See :func:`~imgaug.augmenters.pillike.equalize_` for details.
This function is identical in inputs and outputs to
``PIL.ImageOps.equalize``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.equalize_`.
Parameters
----------
image : ndarray
``uint8`` ``(H,W,[C])`` image to equalize.
mask : None or ndarray, optional
An optional mask. If given, only the pixels selected by the mask are
included in the analysis.
Returns
-------
ndarray
Equalized image.
"""
# internally used method works in-place by default and hence needs a copy
size = image.size
if size == 0:
return np.copy(image)
if size >= _EQUALIZE_USE_PIL_BELOW:
image = np.copy(image)
return equalize_(image, mask)
def equalize_(image, mask=None):
"""Equalize the image histogram in-place.
This function applies a non-linear mapping to the input image, in order
to create a uniform distribution of grayscale values in the output image.
This function has identical outputs to ``PIL.ImageOps.equalize``.
It does however work in-place.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
``uint8`` ``(H,W,[C])`` image to equalize.
mask : None or ndarray, optional
An optional mask. If given, only the pixels selected by the mask are
included in the analysis.
Returns
-------
ndarray
Equalized image. *Might* have been modified in-place.
"""
nb_channels = 1 if image.ndim == 2 else image.shape[-1]
if nb_channels not in [1, 3]:
result = [equalize_(image[:, :, c])
for c in np.arange(nb_channels)]
return np.stack(result, axis=-1)
iadt.allow_only_uint8({image.dtype})
if mask is not None:
assert mask.ndim == 2, (
"Expected 2-dimensional mask, got shape %s." % (mask.shape,))
assert mask.dtype == iadt._UINT8_DTYPE, (
"Expected mask of dtype uint8, got dtype %s." % (mask.dtype.name,))
size = image.size
if size == 0:
return image
if nb_channels == 3 and size < _EQUALIZE_USE_PIL_BELOW:
return _equalize_pil_(image, mask)
return _equalize_no_pil_(image, mask)
# note that this is supposed to be a non-PIL reimplementation of PIL's
# equalize, which produces slightly different results from cv2.equalizeHist()
# Added in 0.4.0.
def _equalize_no_pil_(image, mask=None):
nb_channels = 1 if image.ndim == 2 else image.shape[-1]
# TODO remove the first axis, no longer needed
lut = np.empty((1, 256, nb_channels), dtype=np.int32)
for c_idx in range(nb_channels):
if image.ndim == 2:
image_c = image[:, :, np.newaxis]
else:
image_c = image[:, :, c_idx:c_idx+1]
histo = cv2.calcHist(
[_normalize_cv2_input_arr_(image_c)], [0], mask, [256], [0, 256])
if len(histo.nonzero()[0]) <= 1:
lut[0, :, c_idx] = np.arange(256).astype(np.int32)
continue
step = np.sum(histo[:-1]) // 255
if not step:
lut[0, :, c_idx] = np.arange(256).astype(np.int32)
continue
n = step // 2
cumsum = np.cumsum(histo)
lut[0, 0, c_idx] = n
lut[0, 1:, c_idx] = n + cumsum[0:-1]
lut[0, :, c_idx] //= int(step)
lut = np.clip(lut, None, 255, out=lut).astype(np.uint8)
image = ia.apply_lut_(image, lut)
return image
# Added in 0.4.0.
def _equalize_pil_(image, mask=None):
if mask is not None:
mask = PIL.Image.fromarray(mask).convert("L")
# don't return np.asarray(...) directly as its results are read-only
image[...] = np.asarray(
PIL.ImageOps.equalize(
PIL.Image.fromarray(image),
mask=mask
)
)
return image
def autocontrast(image, cutoff=0, ignore=None):
"""Maximize (normalize) image contrast.
This function calculates a histogram of the input image, removes
**cutoff** percent of the lightest and darkest pixels from the histogram,
and remaps the image so that the darkest pixel becomes black (``0``), and
the lightest becomes white (``255``).
This function has identical outputs to ``PIL.ImageOps.autocontrast``.
The speed is almost identical.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image for which to enhance the contrast.
cutoff : number
How many percent to cut off at the low and high end of the
histogram. E.g. ``20`` will cut off the lowest and highest ``20%``
of values. Expected value range is ``[0, 100]``.
ignore : None or int or iterable of int
Intensity values to ignore, i.e. to treat as background. If ``None``,
no pixels will be ignored. Otherwise exactly the given intensity
value(s) will be ignored.
Returns
-------
ndarray
Contrast-enhanced image.
"""
iadt.allow_only_uint8({image.dtype})
if 0 in image.shape:
return np.copy(image)
standard_channels = (image.ndim == 2 or image.shape[2] == 3)
if cutoff and standard_channels:
return _autocontrast_pil(image, cutoff, ignore)
return _autocontrast_no_pil(image, cutoff, ignore)
# Added in 0.4.0.
def _autocontrast_pil(image, cutoff, ignore):
# don't return np.asarray(...) as its results are read-only
return np.array(
PIL.ImageOps.autocontrast(
PIL.Image.fromarray(image),
cutoff=cutoff, ignore=ignore
)
)
# This function is only faster than the corresponding PIL function if no
# cutoff is used.
# C901 is " is too complex"
# Added in 0.4.0.
def _autocontrast_no_pil(image, cutoff, ignore): # noqa: C901
# pylint: disable=invalid-name
if ignore is not None and not ia.is_iterable(ignore):
ignore = [ignore]
result = np.empty_like(image)
if result.ndim == 2:
result = result[..., np.newaxis]
nb_channels = image.shape[2] if image.ndim >= 3 else 1
for c_idx in sm.xrange(nb_channels):
# using [0] instead of [int(c_idx)] allows this to work with >4
# channels
if image.ndim == 2:
image_c = image[:, :, np.newaxis]
else:
image_c = image[:, :, c_idx:c_idx+1]
h = cv2.calcHist(
[_normalize_cv2_input_arr_(image_c)], [0], None, [256], [0, 256])
if ignore is not None:
h[ignore] = 0
if cutoff:
cs = np.cumsum(h)
n = cs[-1]
cut = n * cutoff // 100
# remove cutoff% pixels from the low end
lo_cut = cut - cs
lo_cut_nz = np.nonzero(lo_cut <= 0.0)[0]
if len(lo_cut_nz) == 0:
lo = 255
else:
lo = lo_cut_nz[0]
if lo > 0:
h[:lo] = 0
h[lo] = lo_cut[lo]
# remove cutoff% samples from the hi end
cs_rev = np.cumsum(h[::-1])
hi_cut = cs_rev - cut
hi_cut_nz = np.nonzero(hi_cut > 0.0)[0]
if len(hi_cut_nz) == 0:
hi = -1
else:
hi = 255 - hi_cut_nz[0]
h[hi+1:] = 0
if hi > -1:
h[hi] = hi_cut[255-hi]
# find lowest/highest samples after preprocessing
for lo, lo_val in enumerate(h):
if lo_val:
break
for hi in range(255, -1, -1):
if h[hi]:
break
if hi <= lo:
# don't bother
lut = np.arange(256)
else:
scale = 255.0 / (hi - lo)
offset = -lo * scale
ix = np.arange(256).astype(np.float64) * scale + offset
ix = np.clip(ix, 0, 255).astype(np.uint8)
lut = ix
lut = np.array(lut, dtype=np.uint8)
# Vectorized implementation of above block.
# This is overall slower.
# h_nz = np.nonzero(h)[0]
# if len(h_nz) <= 1:
# lut = np.arange(256).astype(np.uint8)
# else:
# lo = h_nz[0]
# hi = h_nz[-1]
#
# scale = 255.0 / (hi - lo)
# offset = -lo * scale
# ix = np.arange(256).astype(np.float64) * scale + offset
# ix = np.clip(ix, 0, 255).astype(np.uint8)
# lut = ix
# TODO change to a single call instead of one per channel
image_c_aug = ia.apply_lut(image_c, lut)
result[:, :, c_idx:c_idx+1] = image_c_aug
if image.ndim == 2:
return result[..., 0]
return result
# Added in 0.4.0.
def _apply_enhance_func(image, cls, factor):
iadt.allow_only_uint8({image.dtype})
if 0 in image.shape:
return np.copy(image)
image, is_hw1 = _ensure_valid_shape(
image, "imgaug.augmenters.pillike.enhance_*()")
# don't return np.asarray(...) as its results are read-only
result = np.array(
cls(
PIL.Image.fromarray(image)
).enhance(factor)
)
if is_hw1:
result = result[:, :, np.newaxis]
return result
def enhance_color(image, factor):
"""Change the strength of colors in an image.
This function has identical outputs to
``PIL.ImageEnhance.Color``.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
factor : number
Colorfulness of the output image. Values close to ``0.0`` lead
to grayscale images, values above ``1.0`` increase the strength of
colors. Sane values are roughly in ``[0.0, 3.0]``.
Returns
-------
ndarray
Color-modified image.
"""
return _apply_enhance_func(image, PIL.ImageEnhance.Color, factor)
def enhance_contrast(image, factor):
"""Change the contrast of an image.
This function has identical outputs to
``PIL.ImageEnhance.Contrast``.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
factor : number
Strength of contrast in the image. Values below ``1.0`` decrease the
contrast, leading to a gray image around ``0.0``. Values
above ``1.0`` increase the contrast. Sane values are roughly in
``[0.5, 1.5]``.
Returns
-------
ndarray
Contrast-modified image.
"""
return _apply_enhance_func(image, PIL.ImageEnhance.Contrast, factor)
def enhance_brightness(image, factor):
"""Change the brightness of images.
This function has identical outputs to
``PIL.ImageEnhance.Brightness``.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
factor : number
Brightness of the image. Values below ``1.0`` decrease the brightness,
leading to a black image around ``0.0``. Values above ``1.0`` increase
the brightness. Sane values are roughly in ``[0.5, 1.5]``.
Returns
-------
ndarray
Brightness-modified image.
"""
return _apply_enhance_func(image, PIL.ImageEnhance.Brightness, factor)
def enhance_sharpness(image, factor):
"""Change the sharpness of an image.
This function has identical outputs to
``PIL.ImageEnhance.Sharpness``.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
factor : number
Sharpness of the image. Values below ``1.0`` decrease the sharpness,
values above ``1.0`` increase it. Sane values are roughly in
``[0.0, 2.0]``.
Returns
-------
ndarray
Sharpness-modified image.
"""
return _apply_enhance_func(image, PIL.ImageEnhance.Sharpness, factor)
# Added in 0.4.0.
def _filter_by_kernel(image, kernel):
iadt.allow_only_uint8({image.dtype})
if 0 in image.shape:
return np.copy(image)
image, is_hw1 = _ensure_valid_shape(
image, "imgaug.augmenters.pillike.filter_*()")
image_pil = PIL.Image.fromarray(image)
image_filtered = image_pil.filter(kernel)
# don't return np.asarray(...) as its results are read-only
result = np.array(image_filtered)
if is_hw1:
result = result[:, :, np.newaxis]
return result
def filter_blur(image):
"""Apply a blur filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.BLUR`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Blurred image.
"""
return _filter_by_kernel(image, PIL.ImageFilter.BLUR)
def filter_smooth(image):
"""Apply a smoothness filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.SMOOTH`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Smoothened image.
"""
return _filter_by_kernel(image, PIL.ImageFilter.SMOOTH)
def filter_smooth_more(image):
"""Apply a strong smoothness filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.SMOOTH_MORE`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Smoothened image.
"""
return _filter_by_kernel(image, PIL.ImageFilter.SMOOTH_MORE)
def filter_edge_enhance(image):
"""Apply an edge enhancement filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.EDGE_ENHANCE`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Image with enhanced edges.
"""
return _filter_by_kernel(image, PIL.ImageFilter.EDGE_ENHANCE)
def filter_edge_enhance_more(image):
"""Apply a stronger edge enhancement filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.EDGE_ENHANCE_MORE``
kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Smoothened image.
"""
return _filter_by_kernel(image, PIL.ImageFilter.EDGE_ENHANCE_MORE)
def filter_find_edges(image):
"""Apply an edge detection filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.FIND_EDGES`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Image with detected edges.
"""
return _filter_by_kernel(image, PIL.ImageFilter.FIND_EDGES)
def filter_contour(image):
"""Apply a contour filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.CONTOUR`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Image with pronounced contours.
"""
return _filter_by_kernel(image, PIL.ImageFilter.CONTOUR)
def filter_emboss(image):
"""Apply an emboss filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.EMBOSS`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Embossed image.
"""
return _filter_by_kernel(image, PIL.ImageFilter.EMBOSS)
def filter_sharpen(image):
"""Apply a sharpening filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.SHARPEN`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Sharpened image.
"""
return _filter_by_kernel(image, PIL.ImageFilter.SHARPEN)
def filter_detail(image):
"""Apply a detail enhancement filter kernel to the image.
This is the same as using PIL's ``PIL.ImageFilter.DETAIL`` kernel.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify.
Returns
-------
ndarray
Image with enhanced details.
"""
return _filter_by_kernel(image, PIL.ImageFilter.DETAIL)
# TODO unify this with the matrix generation for Affine,
# there is probably no need to keep these separate
# Added in 0.4.0.
def _create_affine_matrix(scale_x=1.0, scale_y=1.0,
translate_x_px=0, translate_y_px=0,
rotate_deg=0,
shear_x_deg=0, shear_y_deg=0,
center_px=(0, 0)):
from .geometric import _AffineMatrixGenerator, _RAD_PER_DEGREE
scale_x = max(scale_x, 0.0001)
scale_y = max(scale_y, 0.0001)
rotate_rad = rotate_deg * _RAD_PER_DEGREE
shear_x_rad = shear_x_deg * _RAD_PER_DEGREE
shear_y_rad = shear_y_deg * _RAD_PER_DEGREE
matrix_gen = _AffineMatrixGenerator()
matrix_gen.translate(x_px=-center_px[0], y_px=-center_px[1])
matrix_gen.scale(x_frac=scale_x, y_frac=scale_y)
matrix_gen.translate(x_px=translate_x_px, y_px=translate_y_px)
matrix_gen.shear(x_rad=-shear_x_rad, y_rad=shear_y_rad)
matrix_gen.rotate(rotate_rad)
matrix_gen.translate(x_px=center_px[0], y_px=center_px[1])
matrix = matrix_gen.matrix
matrix = np.linalg.inv(matrix)
return matrix
def warp_affine(image,
scale_x=1.0, scale_y=1.0,
translate_x_px=0, translate_y_px=0,
rotate_deg=0,
shear_x_deg=0, shear_y_deg=0,
fillcolor=None,
center=(0.5, 0.5)):
"""Apply an affine transformation to an image.
This function has identical outputs to
``PIL.Image.transform`` with ``method=PIL.Image.AFFINE``.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
The image to modify. Expected to be ``uint8`` with shape ``(H,W)``
or ``(H,W,C)`` with ``C`` being ``3`` or ``4``.
scale_x : number, optional
Affine scale factor along the x-axis, where ``1.0`` denotes an
identity transform and ``2.0`` is a strong zoom-in effect.
scale_y : number, optional
Affine scale factor along the y-axis, where ``1.0`` denotes an
identity transform and ``2.0`` is a strong zoom-in effect.
translate_x_px : number, optional
Affine translation along the x-axis in pixels.
Positive values translate the image towards the right.
translate_y_px : number, optional
Affine translation along the y-axis in pixels.
Positive values translate the image towards the bottom.
rotate_deg : number, optional
Affine rotation in degrees *around the top left* of the image.
shear_x_deg : number, optional
Affine shearing in degrees along the x-axis with center point
being the top-left of the image.
shear_y_deg : number, optional
Affine shearing in degrees along the y-axis with center point
being the top-left of the image.
fillcolor : None or int or tuple of int, optional
Color tuple or intensity value to use when filling up newly
created pixels. ``None`` fills with zeros. ``int`` will only fill
the ``0`` th channel with that intensity value and all other channels
with ``0`` (this is the default behaviour of PIL, use a tuple to
fill all channels).
center : tuple of number, optional
Center xy-coordinate of the affine transformation, given as *relative*
values, i.e. ``(0.0, 0.0)`` sets the transformation center to the
top-left image corner, ``(1.0, 0.0)`` sets it to the the top-right
image corner and ``(0.5, 0.5)`` sets it to the image center.
The transformation center is relevant e.g. for rotations ("rotate
around this center point"). PIL uses the image top-left corner
as the transformation center if no centerization is included in the
affine transformation matrix.
Returns
-------
ndarray
Image after affine transformation.
"""
iadt.allow_only_uint8({image.dtype})
if 0 in image.shape:
return np.copy(image)
fillcolor = fillcolor if fillcolor is not None else 0
if ia.is_iterable(fillcolor):
# make sure that iterable fillcolor contains only ints
# otherwise we get a deprecation warning in py3.8
fillcolor = tuple(map(int, fillcolor))
image, is_hw1 = _ensure_valid_shape(
image, "imgaug.augmenters.pillike.warp_affine()")
image_pil = PIL.Image.fromarray(image)
height, width = image.shape[0:2]
center_px = (width * center[0], height * center[1])
matrix = _create_affine_matrix(scale_x=scale_x,
scale_y=scale_y,
translate_x_px=translate_x_px,
translate_y_px=translate_y_px,
rotate_deg=rotate_deg,
shear_x_deg=shear_x_deg,
shear_y_deg=shear_y_deg,
center_px=center_px)
matrix = matrix[:2, :].flat
# don't return np.asarray(...) as its results are read-only
result = np.array(
image_pil.transform(image_pil.size, PIL.Image.AFFINE, matrix,
fillcolor=fillcolor)
)
if is_hw1:
result = result[:, :, np.newaxis]
return result
# we don't use pil_solarize() here. but instead just subclass Invert,
# which is easier and comes down to the same
class Solarize(arithmetic.Invert):
"""Augmenter with identical outputs to PIL's ``solarize()`` function.
This augmenter inverts all pixel values above a threshold.
The outputs are identical to PIL's ``solarize()``.
Added in 0.4.0.
**Supported dtypes**:
See ``~imgaug.augmenters.arithmetic.invert_(min_value=None and max_value=None)``.
Parameters
----------
p : float or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.arithmetic.Invert`.
threshold : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.arithmetic.Invert`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Solarize(0.5, threshold=(32, 128))
Invert the colors in ``50`` percent of all images for pixels with a
value between ``32`` and ``128`` or more. The threshold is sampled once
per image. The thresholding operation happens per channel.
"""
def __init__(self, p=1.0, threshold=128,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Solarize, self).__init__(
p=p, per_channel=False,
min_value=None, max_value=None,
threshold=threshold, invert_above_threshold=True,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Posterize(colorlib.Posterize):
"""Augmenter with identical outputs to PIL's ``posterize()`` function.
This augmenter quantizes each array component to ``N`` bits.
This class is currently an alias for
:class:`~imgaug.augmenters.color.Posterize`, which again is an alias
for :class:`~imgaug.augmenters.color.UniformColorQuantizationToNBits`,
i.e. all three classes are right now guarantueed to have the same
outputs as PIL's function.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.color.Posterize`.
"""
class Equalize(meta.Augmenter):
"""Equalize the image histogram.
This augmenter has identical outputs to ``PIL.ImageOps.equalize``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.equalize_`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.Equalize()
Equalize the histograms of all input images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Equalize, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
# pylint: disable=no-self-use
if batch.images is not None:
for image in batch.images:
image[...] = equalize_(image)
return batch
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return []
class Autocontrast(contrastlib._ContrastFuncWrapper):
"""Adjust contrast by cutting off ``p%`` of lowest/highest histogram values.
This augmenter has identical outputs to ``PIL.ImageOps.autocontrast``.
See :func:`~imgaug.augmenters.pillike.autocontrast` for more details.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.autocontrast`.
Parameters
----------
cutoff : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Percentage of values to cut off from the low and high end of each
image's histogram, before stretching it to ``[0, 255]``.
* If ``int``: The value will be used for all images.
* If ``tuple`` ``(a, b)``: A value will be uniformly sampled from
the discrete interval ``[a..b]`` per image.
* If ``list``: A random value will be sampled from the list
per image.
* If ``StochasticParameter``: A value will be sampled from that
parameter per image.
per_channel : bool or float, optional
Whether to use the same value for all channels (``False``) or to
sample a new value for each channel (``True``). If this value is a
float ``p``, then for ``p`` percent of all images `per_channel` will
be treated as ``True``, otherwise as ``False``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.Autocontrast()
Modify the contrast of images by cutting off the ``0`` to ``20%`` lowest
and highest values from the histogram, then stretching it to full length.
>>> aug = iaa.pillike.Autocontrast((10, 20), per_channel=True)
Modify the contrast of images by cutting off the ``10`` to ``20%`` lowest
and highest values from the histogram, then stretching it to full length.
The cutoff value is sampled per *channel* instead of per *image*.
"""
# pylint: disable=protected-access
# Added in 0.4.0.
def __init__(self, cutoff=(0, 20), per_channel=False,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
params1d = [
iap.handle_discrete_param(
cutoff, "cutoff", value_range=(0, 49), tuple_to_uniform=True,
list_to_choice=True)
]
func = autocontrast
super(Autocontrast, self).__init__(
func, params1d, per_channel,
dtypes_allowed="uint8",
dtypes_disallowed="uint16 uint32 uint64 int8 int16 int32 int64 "
"float16 float32 float64 float128 "
"bool",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic
)
# Added in 0.4.0.
class _EnhanceBase(meta.Augmenter):
# Added in 0.4.0.
def __init__(self, func, factor, factor_value_range,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(_EnhanceBase, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.func = func
self.factor = iap.handle_continuous_param(
factor, "factor", value_range=factor_value_range,
tuple_to_uniform=True, list_to_choice=True)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
factors = self._draw_samples(len(batch.images), random_state)
for image, factor in zip(batch.images, factors):
image[...] = self.func(image, factor)
return batch
# Added in 0.4.0.
def _draw_samples(self, nb_rows, random_state):
return self.factor.draw_samples((nb_rows,), random_state=random_state)
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.factor]
class EnhanceColor(_EnhanceBase):
"""Convert images to grayscale.
This augmenter has identical outputs to ``PIL.ImageEnhance.Color``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.enhance_color`.
Parameters
----------
factor : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Colorfulness of the output image. Values close to ``0.0`` lead
to grayscale images, values above ``1.0`` increase the strength of
colors. Sane values are roughly in ``[0.0, 3.0]``.
* If ``number``: The value will be used for all images.
* If ``tuple`` ``(a, b)``: A value will be uniformly sampled per
image from the interval ``[a, b)``.
* If ``list``: A random value will be picked from the list per
image.
* If ``StochasticParameter``: Per batch of size ``N``, the
parameter will be queried once to return ``(N,)`` samples.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.EnhanceColor()
Create an augmenter to remove a random fraction of color from
input images.
"""
# Added in 0.4.0.
def __init__(self, factor=(0.0, 3.0),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(EnhanceColor, self).__init__(
func=enhance_color,
factor=factor,
factor_value_range=(0.0, None),
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class EnhanceContrast(_EnhanceBase):
"""Change the contrast of images.
This augmenter has identical outputs to ``PIL.ImageEnhance.Contrast``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.enhance_contrast`.
Parameters
----------
factor : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Strength of contrast in the image. Values below ``1.0`` decrease the
contrast, leading to a gray image around ``0.0``. Values
above ``1.0`` increase the contrast. Sane values are roughly in
``[0.5, 1.5]``.
* If ``number``: The value will be used for all images.
* If ``tuple`` ``(a, b)``: A value will be uniformly sampled per
image from the interval ``[a, b)``.
* If ``list``: A random value will be picked from the list per
image.
* If ``StochasticParameter``: Per batch of size ``N``, the
parameter will be queried once to return ``(N,)`` samples.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.EnhanceContrast()
Create an augmenter that worsens the contrast of an image by a random
factor.
"""
# Added in 0.4.0.
def __init__(self, factor=(0.5, 1.5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(EnhanceContrast, self).__init__(
func=enhance_contrast,
factor=factor,
factor_value_range=(0.0, None),
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class EnhanceBrightness(_EnhanceBase):
"""Change the brightness of images.
This augmenter has identical outputs to
``PIL.ImageEnhance.Brightness``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.enhance_brightness`.
Parameters
----------
factor : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Brightness of the image. Values below ``1.0`` decrease the brightness,
leading to a black image around ``0.0``. Values above ``1.0`` increase
the brightness. Sane values are roughly in ``[0.5, 1.5]``.
* If ``number``: The value will be used for all images.
* If ``tuple`` ``(a, b)``: A value will be uniformly sampled per
image from the interval ``[a, b)``.
* If ``list``: A random value will be picked from the list per
image.
* If ``StochasticParameter``: Per batch of size ``N``, the
parameter will be queried once to return ``(N,)`` samples.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.EnhanceBrightness()
Create an augmenter that worsens the brightness of an image by a random
factor.
"""
# Added in 0.4.0.
def __init__(self, factor=(0.5, 1.5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(EnhanceBrightness, self).__init__(
func=enhance_brightness,
factor=factor,
factor_value_range=(0.0, None),
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class EnhanceSharpness(_EnhanceBase):
"""Change the sharpness of images.
This augmenter has identical outputs to
``PIL.ImageEnhance.Sharpness``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.enhance_sharpness`.
Parameters
----------
factor : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Sharpness of the image. Values below ``1.0`` decrease the sharpness,
values above ``1.0`` increase it. Sane values are roughly in
``[0.0, 2.0]``.
* If ``number``: The value will be used for all images.
* If ``tuple`` ``(a, b)``: A value will be uniformly sampled per
image from the interval ``[a, b)``.
* If ``list``: A random value will be picked from the list per
image.
* If ``StochasticParameter``: Per batch of size ``N``, the
parameter will be queried once to return ``(N,)`` samples.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.EnhanceSharpness()
Create an augmenter that randomly decreases or increases the sharpness
of an image.
"""
# Added in 0.4.0.
def __init__(self, factor=(0.0, 2.0),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(EnhanceSharpness, self).__init__(
func=enhance_sharpness,
factor=factor,
factor_value_range=(0.0, None),
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
class _FilterBase(meta.Augmenter):
# Added in 0.4.0.
def __init__(self, func,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(_FilterBase, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.func = func
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is not None:
for image in batch.images:
image[...] = self.func(image)
return batch
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return []
class FilterBlur(_FilterBase):
"""Apply a blur filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel ``PIL.ImageFilter.BLUR``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_blur`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterBlur()
Create an augmenter that applies a blur filter kernel to images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterBlur, self).__init__(
func=filter_blur,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterSmooth(_FilterBase):
"""Apply a smoothening filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel ``PIL.ImageFilter.SMOOTH``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_smooth`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterSmooth()
Create an augmenter that applies a smoothening filter kernel to images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterSmooth, self).__init__(
func=filter_smooth,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterSmoothMore(_FilterBase):
"""Apply a strong smoothening filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel ``PIL.ImageFilter.BLUR``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_smooth_more`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterSmoothMore()
Create an augmenter that applies a strong smoothening filter kernel to
images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterSmoothMore, self).__init__(
func=filter_smooth_more,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterEdgeEnhance(_FilterBase):
"""Apply an edge enhance filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel
``PIL.ImageFilter.EDGE_ENHANCE``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_edge_enhance`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterEdgeEnhance()
Create an augmenter that applies a edge enhancement filter kernel to
images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterEdgeEnhance, self).__init__(
func=filter_edge_enhance,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterEdgeEnhanceMore(_FilterBase):
"""Apply a strong edge enhancement filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel
``PIL.ImageFilter.EDGE_ENHANCE_MORE``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_edge_enhance_more`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterEdgeEnhanceMore()
Create an augmenter that applies a strong edge enhancement filter kernel
to images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterEdgeEnhanceMore, self).__init__(
func=filter_edge_enhance_more,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterFindEdges(_FilterBase):
"""Apply a edge detection kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel
``PIL.ImageFilter.FIND_EDGES``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_find_edges`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterFindEdges()
Create an augmenter that applies an edge detection filter kernel to images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterFindEdges, self).__init__(
func=filter_find_edges,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterContour(_FilterBase):
"""Apply a contour detection filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel ``PIL.ImageFilter.CONTOUR``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_contour`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterContour()
Create an augmenter that applies a contour detection filter kernel to
images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterContour, self).__init__(
func=filter_contour,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterEmboss(_FilterBase):
"""Apply an emboss filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel ``PIL.ImageFilter.EMBOSS``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_emboss`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterEmboss()
Create an augmenter that applies an emboss filter kernel to images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterEmboss, self).__init__(
func=filter_emboss,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterSharpen(_FilterBase):
"""Apply a sharpening filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel ``PIL.ImageFilter.SHARPEN``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_sharpen`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterSharpen()
Create an augmenter that applies a sharpening filter kernel to images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterSharpen, self).__init__(
func=filter_sharpen,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class FilterDetail(_FilterBase):
"""Apply a detail enhancement filter kernel to images.
This augmenter has identical outputs to
calling ``PIL.Image.filter`` with kernel ``PIL.ImageFilter.DETAIL``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.filter_detail`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.FilterDetail()
Create an augmenter that applies a detail enhancement filter kernel to
images.
"""
# Added in 0.4.0.
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FilterDetail, self).__init__(
func=filter_detail,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Affine(geometric.Affine):
"""Apply PIL-like affine transformations to images.
This augmenter has identical outputs to
``PIL.Image.transform`` with parameter ``method=PIL.Image.AFFINE``.
.. warning::
This augmenter can currently only transform image-data.
Batches containing heatmaps, segmentation maps and
coordinate-based augmentables will be rejected with an error.
Use :class:`~imgaug.augmenters.geometric.Affine` if you have to
transform such inputs.
.. note::
This augmenter uses the image center as the transformation center.
This has to be explicitly enforced in PIL using corresponding
translation matrices. Without such translation, PIL uses the image
top left corner as the transformation center. To mirror that
behaviour, use ``center=(0.0, 0.0)``.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.augmenters.pillike.warp_affine`.
Parameters
----------
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
See :class:`~imgaug.augmenters.geometric.Affine`.
translate_percent : None or number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": number/tuple/list/StochasticParameter, "y": number/tuple/list/StochasticParameter}, optional
See :class:`~imgaug.augmenters.geometric.Affine`.
translate_px : None or int or tuple of int or list of int or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
See :class:`~imgaug.augmenters.geometric.Affine`.
rotate : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :class:`~imgaug.augmenters.geometric.Affine`.
shear : number or tuple of number or list of number or imgaug.parameters.StochasticParameter or dict {"x": int/tuple/list/StochasticParameter, "y": int/tuple/list/StochasticParameter}, optional
See :class:`~imgaug.augmenters.geometric.Affine`.
fillcolor : number or tuple of number or list of number or imgaug.ALL or imgaug.parameters.StochasticParameter, optional
See parameter ``cval`` in :class:`~imgaug.augmenters.geometric.Affine`.
center : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
The center point of the affine transformation, given as relative
xy-coordinates.
Set this to ``(0.0, 0.0)`` or ``left-top`` to use the top left image
corner as the transformation center.
Set this to ``(0.5, 0.5)`` or ``center-center`` to use the image
center as the transformation center.
See also paramerer ``position`` in
:class:`~imgaug.augmenters.size.PadToFixedSize` for details
about valid datatypes of this parameter.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.pillike.Affine(scale={"x": (0.8, 1.2), "y": (0.5, 1.5)})
Create an augmenter that applies affine scaling (zoom in/out) to images.
Along the x-axis they are scaled to 80-120% of their size, along
the y-axis to 50-150% (both values randomly and uniformly chosen per
image).
>>> aug = iaa.pillike.Affine(translate_px={"x": 0, "y": [-10, 10]},
>>> fillcolor=128)
Create an augmenter that translates images along the y-axis by either
``-10px`` or ``10px``. Newly created pixels are always filled with
the value ``128`` (along all channels).
>>> aug = iaa.pillike.Affine(rotate=(-20, 20), fillcolor=(0, 256))
Rotate an image by ``-20`` to ``20`` degress and fill up all newly
created pixels with a random RGB color.
See the similar augmenter :class:`~imgaug.augmenters.geometric.Affine`
for more examples.
"""
# Added in 0.4.0.
def __init__(self, scale=1.0, translate_percent=None, translate_px=None,
rotate=0.0, shear=0.0, fillcolor=0, center=(0.5, 0.5),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Affine, self).__init__(
scale=scale,
translate_percent=translate_percent,
translate_px=translate_px,
rotate=rotate,
shear=shear,
order=1,
cval=fillcolor,
mode="constant",
fit_output=False,
backend="auto",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO move that func to iap
self.center = sizelib._handle_position_parameter(center)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
cols = batch.get_column_names()
assert len(cols) == 0 or (len(cols) == 1 and "images" in cols), (
"pillike.Affine can currently only process image data. Got a "
"batch containing: %s. Use imgaug.augmenters.geometric.Affine for "
"batches containing non-image data." % (", ".join(cols),))
return super(Affine, self)._augment_batch_(
batch, random_state, parents, hooks)
# Added in 0.4.0.
def _augment_images_by_samples(self, images, samples,
image_shapes=None,
return_matrices=False):
assert return_matrices is False, (
"Got unexpectedly return_matrices=True. pillike.Affine does not "
"yet produce that output.")
for i, image in enumerate(images):
image_shape = (image.shape if image_shapes is None
else image_shapes[i])
params = samples.get_affine_parameters(
i, arr_shape=image_shape, image_shape=image_shape)
image[...] = warp_affine(
image,
scale_x=params["scale_x"],
scale_y=params["scale_y"],
translate_x_px=params["translate_x_px"],
translate_y_px=params["translate_y_px"],
rotate_deg=params["rotate_deg"],
shear_x_deg=params["shear_x_deg"],
shear_y_deg=params["shear_y_deg"],
fillcolor=tuple(samples.cval[i]),
center=(samples.center_x[i], samples.center_y[i])
)
return images
# Added in 0.4.0.
def _draw_samples(self, nb_samples, random_state):
# standard affine samples
samples = super(Affine, self)._draw_samples(nb_samples,
random_state)
# add samples for 'center' parameter, which is not yet a part of
# Affine
if isinstance(self.center, tuple):
xx = self.center[0].draw_samples(nb_samples,
random_state=random_state)
yy = self.center[1].draw_samples(nb_samples,
random_state=random_state)
else:
xy = self.center.draw_samples((nb_samples, 2),
random_state=random_state)
xx = xy[:, 0]
yy = xy[:, 1]
samples.center_x = xx
samples.center_y = yy
return samples
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [
self.scale, self.translate, self.rotate, self.shear, self.cval,
self.center]
================================================
FILE: imgaug/augmenters/pooling.py
================================================
"""
Augmenters that apply pooling operations to images.
List of augmenters:
* :class:`AveragePooling`
* :class:`MaxPooling`
* :class:`MinPooling`
* :class:`MedianPooling`
"""
from __future__ import print_function, division, absolute_import
from abc import ABCMeta, abstractmethod
import functools
import six
import numpy as np
import imgaug as ia
from . import meta
from .. import parameters as iap
def _compute_shape_after_pooling(image_shape, ksize_h, ksize_w):
if any([axis == 0 for axis in image_shape]):
return image_shape
height, width = image_shape[0:2]
if height % ksize_h > 0:
height += ksize_h - (height % ksize_h)
if width % ksize_w > 0:
width += ksize_w - (width % ksize_w)
return tuple([
height//ksize_h,
width//ksize_w,
] + list(image_shape[2:]))
@six.add_metaclass(ABCMeta)
class _AbstractPoolingBase(meta.Augmenter):
# TODO add floats as ksize denoting fractions of image sizes
# (note possible overlap with fractional kernel sizes here)
def __init__(self, kernel_size, keep_size=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(_AbstractPoolingBase, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.kernel_size = iap.handle_discrete_kernel_size_param(
kernel_size,
"kernel_size",
value_range=(0, None),
allow_floats=False)
self.keep_size = keep_size
self._resize_hm_and_sm_arrays = True
@abstractmethod
def _pool_image(self, image, kernel_size_h, kernel_size_w):
"""Apply pooling method with given kernel height/width to an image."""
def _draw_samples(self, nb_rows, random_state):
rss = random_state.duplicate(2)
mode = "single" if self.kernel_size[1] is None else "two"
kernel_sizes_h = self.kernel_size[0].draw_samples(
(nb_rows,),
random_state=rss[0])
if mode == "single":
kernel_sizes_w = kernel_sizes_h
else:
kernel_sizes_w = self.kernel_size[1].draw_samples(
(nb_rows,), random_state=rss[1])
return (
np.clip(kernel_sizes_h, 1, None),
np.clip(kernel_sizes_w, 1, None)
)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None and self.keep_size:
return batch
samples = self._draw_samples(batch.nb_rows, random_state)
for column in batch.columns:
value_aug = getattr(
self, "_augment_%s_by_samples" % (column.name,)
)(column.value, samples)
setattr(batch, column.attr_name, value_aug)
return batch
# Added in 0.4.0.
def _augment_images_by_samples(self, images, samples):
if not self.keep_size:
images = list(images)
kernel_sizes_h, kernel_sizes_w = samples
gen = enumerate(zip(images, kernel_sizes_h, kernel_sizes_w))
for i, (image, ksize_h, ksize_w) in gen:
if ksize_h >= 2 or ksize_w >= 2:
image_pooled = self._pool_image(
image, ksize_h, ksize_w)
if self.keep_size:
image_pooled = ia.imresize_single_image(
image_pooled, image.shape[0:2])
images[i] = image_pooled
return images
# Added in 0.4.0.
def _augment_heatmaps_by_samples(self, heatmaps, samples):
return self._augment_hms_and_segmaps_by_samples(heatmaps, samples,
"arr_0to1")
# Added in 0.4.0.
def _augment_segmentation_maps_by_samples(self, segmaps, samples):
return self._augment_hms_and_segmaps_by_samples(segmaps, samples,
"arr")
# Added in 0.4.0.
def _augment_hms_and_segmaps_by_samples(self, augmentables, samples,
arr_attr_name):
if self.keep_size:
return augmentables
kernel_sizes_h, kernel_sizes_w = samples
gen = enumerate(zip(augmentables, kernel_sizes_h, kernel_sizes_w))
for i, (augmentable, ksize_h, ksize_w) in gen:
if ksize_h >= 2 or ksize_w >= 2:
# We could also keep the size of the HM/SM array unchanged
# here as the library can handle HMs/SMs that are larger
# than the image. This might be inintuitive however and
# could lead to unnecessary performance degredation.
if self._resize_hm_and_sm_arrays:
new_shape_arr = _compute_shape_after_pooling(
getattr(augmentable, arr_attr_name).shape,
ksize_h, ksize_w)
augmentable = augmentable.resize(new_shape_arr[0:2])
new_shape = _compute_shape_after_pooling(
augmentable.shape, ksize_h, ksize_w)
augmentable.shape = new_shape
augmentables[i] = augmentable
return augmentables
# Added in 0.4.0.
def _augment_keypoints_by_samples(self, keypoints_on_images, samples):
if self.keep_size:
return keypoints_on_images
kernel_sizes_h, kernel_sizes_w = samples
gen = enumerate(zip(keypoints_on_images, kernel_sizes_h,
kernel_sizes_w))
for i, (kpsoi, ksize_h, ksize_w) in gen:
if ksize_h >= 2 or ksize_w >= 2:
new_shape = _compute_shape_after_pooling(
kpsoi.shape, ksize_h, ksize_w)
keypoints_on_images[i] = kpsoi.on_(new_shape)
return keypoints_on_images
# Added in 0.4.0.
def _augment_polygons_by_samples(self, polygons_on_images, samples):
func = functools.partial(self._augment_keypoints_by_samples,
samples=samples)
return self._apply_to_polygons_as_keypoints(polygons_on_images, func,
recoverer=None)
# Added in 0.4.0.
def _augment_line_strings_by_samples(self, line_strings_on_images, samples):
func = functools.partial(self._augment_keypoints_by_samples,
samples=samples)
return self._apply_to_cbaois_as_keypoints(line_strings_on_images, func)
# Added in 0.4.0.
def _augment_bounding_boxes_by_samples(self, bounding_boxes_on_images,
samples):
func = functools.partial(self._augment_keypoints_by_samples,
samples=samples)
return self._apply_to_cbaois_as_keypoints(bounding_boxes_on_images,
func)
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.kernel_size, self.keep_size]
# TODO rename kernel size parameters in all augmenters to kernel_size
# TODO add per_channel
# TODO add upscaling interpolation mode?
class AveragePooling(_AbstractPoolingBase):
"""
Apply average pooling to images.
This augmenter pools images with kernel sizes ``H x W`` by averaging the
pixel values within these windows. For e.g. ``2 x 2`` this halves the image
size. Optionally, the augmenter will automatically re-upscale the image
to the input size (by default this is activated).
Note that this augmenter is very similar to ``AverageBlur``.
``AverageBlur`` applies averaging within windows of given kernel size
*without* striding, while ``AveragePooling`` applies striding corresponding
to the kernel size, with optional upscaling afterwards. The upscaling
is configured to create "pixelated"/"blocky" images by default.
.. note::
During heatmap or segmentation map augmentation, the respective
arrays are not changed, only the shapes of the underlying images
are updated. This is because imgaug can handle maps/maks that are
larger/smaller than their corresponding image.
**Supported dtypes**:
See :func:`~imgaug.imgaug.avg_pool`.
Attributes
----------
kernel_size : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or tuple of tuple of int or tuple of list of int or tuple of imgaug.parameters.StochasticParameter, optional
The kernel size of the pooling operation.
* If an int, then that value will be used for all images for both
kernel height and width.
* If a tuple ``(a, b)``, then a value from the discrete range
``[a..b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list per
image and used for both kernel height and width.
* If a StochasticParameter, then a value will be sampled per image
from that parameter per image and used for both kernel height and
width.
* If a tuple of tuple of int given as ``((a, b), (c, d))``, then two
values will be sampled independently from the discrete ranges
``[a..b]`` and ``[c..d]`` per image and used as the kernel height
and width.
* If a tuple of lists of int, then two values will be sampled
independently per image, one from the first list and one from the
second, and used as the kernel height and width.
* If a tuple of StochasticParameter, then two values will be sampled
indepdently per image, one from the first parameter and one from the
second, and used as the kernel height and width.
keep_size : bool, optional
After pooling, the result image will usually have a different
height/width compared to the original input image. If this
parameter is set to True, then the pooled image will be resized
to the input image's size, i.e. the augmenter's output shape is always
identical to the input shape.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.AveragePooling(2)
Create an augmenter that always pools with a kernel size of ``2 x 2``.
>>> aug = iaa.AveragePooling(2, keep_size=False)
Create an augmenter that always pools with a kernel size of ``2 x 2``
and does *not* resize back to the input image size, i.e. the resulting
images have half the resolution.
>>> aug = iaa.AveragePooling([2, 8])
Create an augmenter that always pools either with a kernel size
of ``2 x 2`` or ``8 x 8``.
>>> aug = iaa.AveragePooling((1, 7))
Create an augmenter that always pools with a kernel size of
``1 x 1`` (does nothing) to ``7 x 7``. The kernel sizes are always
symmetric.
>>> aug = iaa.AveragePooling(((1, 7), (1, 7)))
Create an augmenter that always pools with a kernel size of
``H x W`` where ``H`` and ``W`` are both sampled independently from the
range ``[1..7]``. E.g. resulting kernel sizes could be ``3 x 7``
or ``5 x 1``.
"""
# TODO add floats as ksize denoting fractions of image sizes
# (note possible overlap with fractional kernel sizes here)
def __init__(self, kernel_size=(1, 5), keep_size=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(AveragePooling, self).__init__(
kernel_size=kernel_size, keep_size=keep_size,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
def _pool_image(self, image, kernel_size_h, kernel_size_w):
return ia.avg_pool(
image,
(kernel_size_h, kernel_size_w)
)
class MaxPooling(_AbstractPoolingBase):
"""
Apply max pooling to images.
This augmenter pools images with kernel sizes ``H x W`` by taking the
maximum pixel value over windows. For e.g. ``2 x 2`` this halves the image
size. Optionally, the augmenter will automatically re-upscale the image
to the input size (by default this is activated).
The maximum within each pixel window is always taken channelwise..
.. note::
During heatmap or segmentation map augmentation, the respective
arrays are not changed, only the shapes of the underlying images
are updated. This is because imgaug can handle maps/maks that are
larger/smaller than their corresponding image.
**Supported dtypes**:
See :func:`~imgaug.imgaug.max_pool`.
Attributes
----------
kernel_size : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or tuple of tuple of int or tuple of list of int or tuple of imgaug.parameters.StochasticParameter, optional
The kernel size of the pooling operation.
* If an int, then that value will be used for all images for both
kernel height and width.
* If a tuple ``(a, b)``, then a value from the discrete range
``[a..b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list per
image and used for both kernel height and width.
* If a StochasticParameter, then a value will be sampled per image
from that parameter per image and used for both kernel height and
width.
* If a tuple of tuple of int given as ``((a, b), (c, d))``, then two
values will be sampled independently from the discrete ranges
``[a..b]`` and ``[c..d]`` per image and used as the kernel height
and width.
* If a tuple of lists of int, then two values will be sampled
independently per image, one from the first list and one from the
second, and used as the kernel height and width.
* If a tuple of StochasticParameter, then two values will be sampled
indepdently per image, one from the first parameter and one from the
second, and used as the kernel height and width.
keep_size : bool, optional
After pooling, the result image will usually have a different
height/width compared to the original input image. If this
parameter is set to True, then the pooled image will be resized
to the input image's size, i.e. the augmenter's output shape is always
identical to the input shape.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MaxPooling(2)
Create an augmenter that always pools with a kernel size of ``2 x 2``.
>>> aug = iaa.MaxPooling(2, keep_size=False)
Create an augmenter that always pools with a kernel size of ``2 x 2``
and does *not* resize back to the input image size, i.e. the resulting
images have half the resolution.
>>> aug = iaa.MaxPooling([2, 8])
Create an augmenter that always pools either with a kernel size
of ``2 x 2`` or ``8 x 8``.
>>> aug = iaa.MaxPooling((1, 7))
Create an augmenter that always pools with a kernel size of
``1 x 1`` (does nothing) to ``7 x 7``. The kernel sizes are always
symmetric.
>>> aug = iaa.MaxPooling(((1, 7), (1, 7)))
Create an augmenter that always pools with a kernel size of
``H x W`` where ``H`` and ``W`` are both sampled independently from the
range ``[1..7]``. E.g. resulting kernel sizes could be ``3 x 7``
or ``5 x 1``.
"""
# TODO add floats as ksize denoting fractions of image sizes
# (note possible overlap with fractional kernel sizes here)
def __init__(self, kernel_size=(1, 5), keep_size=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MaxPooling, self).__init__(
kernel_size=kernel_size, keep_size=keep_size,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
def _pool_image(self, image, kernel_size_h, kernel_size_w):
return ia.max_pool_(
image,
(kernel_size_h, kernel_size_w)
)
class MinPooling(_AbstractPoolingBase):
"""
Apply minimum pooling to images.
This augmenter pools images with kernel sizes ``H x W`` by taking the
minimum pixel value over windows. For e.g. ``2 x 2`` this halves the image
size. Optionally, the augmenter will automatically re-upscale the image
to the input size (by default this is activated).
The minimum within each pixel window is always taken channelwise.
.. note::
During heatmap or segmentation map augmentation, the respective
arrays are not changed, only the shapes of the underlying images
are updated. This is because imgaug can handle maps/maks that are
larger/smaller than their corresponding image.
**Supported dtypes**:
See :func:`~imgaug.imgaug.min_pool`.
Attributes
----------
kernel_size : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or tuple of tuple of int or tuple of list of int or tuple of imgaug.parameters.StochasticParameter, optional
The kernel size of the pooling operation.
* If an int, then that value will be used for all images for both
kernel height and width.
* If a tuple ``(a, b)``, then a value from the discrete range
``[a..b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list per
image and used for both kernel height and width.
* If a StochasticParameter, then a value will be sampled per image
from that parameter per image and used for both kernel height and
width.
* If a tuple of tuple of int given as ``((a, b), (c, d))``, then two
values will be sampled independently from the discrete ranges
``[a..b]`` and ``[c..d]`` per image and used as the kernel height
and width.
* If a tuple of lists of int, then two values will be sampled
independently per image, one from the first list and one from the
second, and used as the kernel height and width.
* If a tuple of StochasticParameter, then two values will be sampled
indepdently per image, one from the first parameter and one from the
second, and used as the kernel height and width.
keep_size : bool, optional
After pooling, the result image will usually have a different
height/width compared to the original input image. If this
parameter is set to True, then the pooled image will be resized
to the input image's size, i.e. the augmenter's output shape is always
identical to the input shape.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MinPooling(2)
Create an augmenter that always pools with a kernel size of ``2 x 2``.
>>> aug = iaa.MinPooling(2, keep_size=False)
Create an augmenter that always pools with a kernel size of ``2 x 2``
and does *not* resize back to the input image size, i.e. the resulting
images have half the resolution.
>>> aug = iaa.MinPooling([2, 8])
Create an augmenter that always pools either with a kernel size
of ``2 x 2`` or ``8 x 8``.
>>> aug = iaa.MinPooling((1, 7))
Create an augmenter that always pools with a kernel size of
``1 x 1`` (does nothing) to ``7 x 7``. The kernel sizes are always
symmetric.
>>> aug = iaa.MinPooling(((1, 7), (1, 7)))
Create an augmenter that always pools with a kernel size of
``H x W`` where ``H`` and ``W`` are both sampled independently from the
range ``[1..7]``. E.g. resulting kernel sizes could be ``3 x 7``
or ``5 x 1``.
"""
# TODO add floats as ksize denoting fractions of image sizes
# (note possible overlap with fractional kernel sizes here)
def __init__(self, kernel_size=(1, 5), keep_size=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MinPooling, self).__init__(
kernel_size=kernel_size, keep_size=keep_size,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
def _pool_image(self, image, kernel_size_h, kernel_size_w):
return ia.min_pool_(
image,
(kernel_size_h, kernel_size_w)
)
class MedianPooling(_AbstractPoolingBase):
"""
Apply median pooling to images.
This augmenter pools images with kernel sizes ``H x W`` by taking the
median pixel value over windows. For e.g. ``2 x 2`` this halves the image
size. Optionally, the augmenter will automatically re-upscale the image
to the input size (by default this is activated).
The median within each pixel window is always taken channelwise.
.. note::
During heatmap or segmentation map augmentation, the respective
arrays are not changed, only the shapes of the underlying images
are updated. This is because imgaug can handle maps/maks that are
larger/smaller than their corresponding image.
**Supported dtypes**:
See :func:`~imgaug.imgaug.median_pool`.
Attributes
----------
kernel_size : int or tuple of int or list of int or imgaug.parameters.StochasticParameter or tuple of tuple of int or tuple of list of int or tuple of imgaug.parameters.StochasticParameter, optional
The kernel size of the pooling operation.
* If an int, then that value will be used for all images for both
kernel height and width.
* If a tuple ``(a, b)``, then a value from the discrete range
``[a..b]`` will be sampled per image.
* If a list, then a random value will be sampled from that list per
image and used for both kernel height and width.
* If a StochasticParameter, then a value will be sampled per image
from that parameter per image and used for both kernel height and
width.
* If a tuple of tuple of int given as ``((a, b), (c, d))``, then two
values will be sampled independently from the discrete ranges
``[a..b]`` and ``[c..d]`` per image and used as the kernel height
and width.
* If a tuple of lists of int, then two values will be sampled
independently per image, one from the first list and one from the
second, and used as the kernel height and width.
* If a tuple of StochasticParameter, then two values will be sampled
indepdently per image, one from the first parameter and one from the
second, and used as the kernel height and width.
keep_size : bool, optional
After pooling, the result image will usually have a different
height/width compared to the original input image. If this
parameter is set to True, then the pooled image will be resized
to the input image's size, i.e. the augmenter's output shape is always
identical to the input shape.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.MedianPooling(2)
Create an augmenter that always pools with a kernel size of ``2 x 2``.
>>> aug = iaa.MedianPooling(2, keep_size=False)
Create an augmenter that always pools with a kernel size of ``2 x 2``
and does *not* resize back to the input image size, i.e. the resulting
images have half the resolution.
>>> aug = iaa.MedianPooling([2, 8])
Create an augmenter that always pools either with a kernel size
of ``2 x 2`` or ``8 x 8``.
>>> aug = iaa.MedianPooling((1, 7))
Create an augmenter that always pools with a kernel size of
``1 x 1`` (does nothing) to ``7 x 7``. The kernel sizes are always
symmetric.
>>> aug = iaa.MedianPooling(((1, 7), (1, 7)))
Create an augmenter that always pools with a kernel size of
``H x W`` where ``H`` and ``W`` are both sampled independently from the
range ``[1..7]``. E.g. resulting kernel sizes could be ``3 x 7``
or ``5 x 1``.
"""
# TODO add floats as ksize denoting fractions of image sizes
# (note possible overlap with fractional kernel sizes here)
def __init__(self, kernel_size=(1, 5), keep_size=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(MedianPooling, self).__init__(
kernel_size=kernel_size, keep_size=keep_size,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
def _pool_image(self, image, kernel_size_h, kernel_size_w):
# TODO extend pool to support pad_mode and set it here
# to reflection padding
return ia.median_pool(
image,
(kernel_size_h, kernel_size_w)
)
================================================
FILE: imgaug/augmenters/segmentation.py
================================================
"""
Augmenters that apply changes to images based on segmentation methods.
List of augmenters:
* :class:`Superpixels`
* :class:`Voronoi`
* :class:`UniformVoronoi`
* :class:`RegularGridVoronoi`
* :class:`RelativeRegularGridVoronoi`
"""
from __future__ import print_function, division, absolute_import
from abc import ABCMeta, abstractmethod
import numpy as np
# use skimage.segmentation instead `from skimage import segmentation` here,
# because otherwise unittest seems to mix up imgaug.augmenters.segmentation
# with skimage.segmentation for whatever reason
import skimage.segmentation
import skimage.measure
import six
import six.moves as sm
import imgaug as ia
from . import meta
from .. import random as iarandom
from .. import parameters as iap
from .. import dtypes as iadt
from ..imgaug import _NUMBA_INSTALLED, _numbajit
_SLIC_SUPPORTS_START_LABEL = (
tuple(map(int, skimage.__version__.split(".")[0:2]))
>= (0, 17)
) # Added in 0.5.0.
# TODO merge this into imresize?
def _ensure_image_max_size(image, max_size, interpolation):
"""Ensure that images do not exceed a required maximum sidelength.
This downscales to `max_size` if any side violates that maximum.
The other side is downscaled too so that the aspect ratio is maintained.
**Supported dtypes**:
See :func:`~imgaug.imgaug.imresize_single_image`.
Parameters
----------
image : ndarray
Image to potentially downscale.
max_size : int
Maximum length of any side of the image.
interpolation : string or int
See :func:`~imgaug.imgaug.imresize_single_image`.
"""
if max_size is not None:
size = max(image.shape[0], image.shape[1])
if size > max_size:
resize_factor = max_size / size
new_height = int(image.shape[0] * resize_factor)
new_width = int(image.shape[1] * resize_factor)
image = ia.imresize_single_image(
image,
(new_height, new_width),
interpolation=interpolation)
return image
# TODO add compactness parameter
class Superpixels(meta.Augmenter):
"""Transform images parially/completely to their superpixel representation.
This implementation uses skimage's version of the SLIC algorithm.
.. note::
This augmenter is fairly slow. See :ref:`performance`.
**Supported dtypes**:
if (image size <= max_size):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: limited (1)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: limited (1)
* ``float16``: no (2)
* ``float32``: no (2)
* ``float64``: no (3)
* ``float128``: no (2)
* ``bool``: yes; tested
- (1) Superpixel mean intensity replacement requires computing
these means as ``float64`` s. This can cause inaccuracies for
large integer values.
- (2) Error in scikit-image.
- (3) Loss of resolution in scikit-image.
if (image size > max_size):
minimum of (
``imgaug.augmenters.segmentation.Superpixels(image size <= max_size)``,
:func:`~imgaug.augmenters.segmentation._ensure_image_max_size`
)
Parameters
----------
p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Defines for any segment the probability that the pixels within that
segment are replaced by their average color (otherwise, the pixels
are not changed).
Examples:
* A probability of ``0.0`` would mean, that the pixels in no
segment are replaced by their average color (image is not
changed at all).
* A probability of ``0.5`` would mean, that around half of all
segments are replaced by their average color.
* A probability of ``1.0`` would mean, that all segments are
replaced by their average color (resulting in a voronoi
image).
Behaviour based on chosen datatypes for this parameter:
* If a ``number``, then that ``number`` will always be used.
* If ``tuple`` ``(a, b)``, then a random probability will be
sampled from the interval ``[a, b]`` per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, it is expected to return
values between ``0.0`` and ``1.0`` and will be queried *for each
individual segment* to determine whether it is supposed to
be averaged (``>0.5``) or not (``<=0.5``).
Recommended to be some form of ``Binomial(...)``.
n_segments : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Rough target number of how many superpixels to generate (the algorithm
may deviate from this number). Lower value will lead to coarser
superpixels. Higher values are computationally more intensive and
will hence lead to a slowdown.
* If a single ``int``, then that value will always be used as the
number of segments.
* If a ``tuple`` ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
max_size : int or None, optional
Maximum image size at which the augmentation is performed.
If the width or height of an image exceeds this value, it will be
downscaled before the augmentation so that the longest side
matches `max_size`.
This is done to speed up the process. The final output image has the
same size as the input image. Note that in case `p_replace` is below
``1.0``, the down-/upscaling will affect the not-replaced pixels too.
Use ``None`` to apply no down-/upscaling.
interpolation : int or str, optional
Interpolation method to use during downscaling when `max_size` is
exceeded. Valid methods are the same as in
:func:`~imgaug.imgaug.imresize_single_image`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Superpixels(p_replace=1.0, n_segments=64)
Generate around ``64`` superpixels per image and replace all of them with
their average color (standard superpixel image).
>>> aug = iaa.Superpixels(p_replace=0.5, n_segments=64)
Generate around ``64`` superpixels per image and replace half of them
with their average color, while the other half are left unchanged (i.e.
they still show the input image's content).
>>> aug = iaa.Superpixels(p_replace=(0.25, 1.0), n_segments=(16, 128))
Generate between ``16`` and ``128`` superpixels per image and replace
``25`` to ``100`` percent of them with their average color.
"""
def __init__(self, p_replace=(0.5, 1.0), n_segments=(50, 120),
max_size=128, interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Superpixels, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.p_replace = iap.handle_probability_param(
p_replace, "p_replace", tuple_to_uniform=True, list_to_choice=True)
self.n_segments = iap.handle_discrete_param(
n_segments, "n_segments", value_range=(1, None),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
self.max_size = max_size
self.interpolation = interpolation
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
iadt.gate_dtypes_strs(
images,
allowed="bool uint8 uint16 uint32 uint64 int8 int16 int32 int64",
disallowed="float16 float32 float64 float128",
augmenter=self
)
nb_images = len(images)
rss = random_state.duplicate(1+nb_images)
n_segments_samples = self.n_segments.draw_samples(
(nb_images,), random_state=rss[0])
# We cant reduce images to 0 or less segments, hence we pick the
# lowest possible value in these cases (i.e. 1). The alternative
# would be to not perform superpixel detection in these cases
# (akin to n_segments=#pixels).
# TODO add test for this
n_segments_samples = np.clip(n_segments_samples, 1, None)
for i, (image, rs) in enumerate(zip(images, rss[1:])):
if image.size == 0:
# Image with 0-sized axis, nothing to change.
# Placing this before the sampling step should be fine.
continue
replace_samples = self.p_replace.draw_samples(
(n_segments_samples[i],), random_state=rs)
if np.max(replace_samples) == 0:
# not a single superpixel would be replaced by its average
# color, i.e. the image would not be changed, so just keep it
continue
orig_shape = image.shape
image = _ensure_image_max_size(image, self.max_size,
self.interpolation)
# skimage 0.17+ introduces the start_label arg and produces a
# warning if it is not provided. We use start_label=0 here
# (old skimage style) (not entirely sure if =0 is required or =1
# could be used here too, but *seems* like both could work),
# but skimage will change the default start_label to 1 in the
# future.
kwargs = (
{"start_label": 0}
if _SLIC_SUPPORTS_START_LABEL
else {}
)
segments = skimage.segmentation.slic(
image,
n_segments=n_segments_samples[i],
compactness=10,
**kwargs
)
image_aug = replace_segments_(
image, segments, replace_samples > 0.5
)
if orig_shape != image_aug.shape:
image_aug = ia.imresize_single_image(
image_aug,
orig_shape[0:2],
interpolation=self.interpolation)
batch.images[i] = image_aug
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.p_replace, self.n_segments, self.max_size,
self.interpolation]
# TODO add the old skimage method here for 512x512+ images as it starts to
# be faster for these areas
# TODO incorporate this dtype support in the dtype sections of docstrings for
# Superpixels and segment_voronoi()
def replace_segments_(image, segments, replace_flags):
"""Replace segments in images by their average colors in-place.
This expects an image ``(H,W,[C])`` and an integer segmentation
map ``(H,W)``. The segmentation map must contain the same id for pixels
that are supposed to be replaced by the same color ("segments").
For each segement, the average color is computed and used as the
replacement.
Added in 0.5.0.
**Supported dtypes**:
* ``uint8``: yes; indirectly tested
* ``uint16``: yes; indirectly tested
* ``uint32``: yes; indirectly tested
* ``uint64``: no; not tested
* ``int8``: yes; indirectly tested
* ``int16``: yes; indirectly tested
* ``int32``: yes; indirectly tested
* ``int64``: no; not tested
* ``float16``: ?; not tested
* ``float32``: ?; not tested
* ``float64``: ?; not tested
* ``float128``: ?; not tested
* ``bool``: yes; indirectly tested
Parameters
----------
image : ndarray
An image of shape ``(H,W,[C])``.
This image may be changed in-place.
The function is currently not tested for float dtypes.
segments : ndarray
A ``(H,W)`` integer array containing the same ids for pixels belonging
to the same segment.
replace_flags : ndarray or None
A boolean array containing at the ``i`` th index a flag denoting
whether the segment with id ``i`` should be replaced by its average
color. If the flag is ``False``, the original image pixels will be
kept unchanged for that flag.
If this is ``None``, all segments will be replaced.
Returns
-------
ndarray
The image with replaced pixels.
Might be the same image as was provided via `image`.
"""
assert replace_flags is None or replace_flags.dtype.kind == "b"
input_shape = image.shape
if 0 in image.shape:
return image
if len(input_shape) == 2:
image = image[:, :, np.newaxis]
nb_segments = None
bad_dtype = (
image.dtype not in {iadt._UINT8_DTYPE, iadt._INT8_DTYPE}
)
if bad_dtype or not _NUMBA_INSTALLED:
func = _replace_segments_np_
else:
max_id = np.max(segments)
nb_segments = 1 + max_id
func = _replace_segments_numba_dispatcher_
result = func(image, segments, replace_flags, nb_segments)
if len(input_shape) == 2:
return result[:, :, 0]
return result
# Added in 0.5.0.
def _replace_segments_np_(image, segments, replace_flags, _nb_segments):
seg_ids = np.unique(segments)
if replace_flags is None:
replace_flags = np.ones((len(seg_ids),), dtype=bool)
for i, seg_id in enumerate(seg_ids):
if replace_flags[i % len(replace_flags)]:
mask = (segments == seg_id)
mean_color = np.average(image[mask, :], axis=(0,))
image[mask] = mean_color
return image
# Added in 0.5.0.
def _replace_segments_numba_dispatcher_(
image, segments, replace_flags, nb_segments
):
if replace_flags is None:
replace_flags = np.ones((nb_segments,), dtype=bool)
elif not np.any(replace_flags[:nb_segments]):
return image
average_colors = _replace_segments_numba_collect_avg_colors(
image,
segments,
replace_flags,
nb_segments,
image.dtype
)
image = _replace_segments_numba_apply_avg_cols_(
image, segments, replace_flags, average_colors
)
return image
# Added in 0.5.0.
@_numbajit(nopython=True, nogil=True, cache=True)
def _replace_segments_numba_collect_avg_colors(
image, segments, replace_flags, nb_segments, output_dtype
):
height, width, nb_channels = image.shape
nb_flags = len(replace_flags)
average_colors = np.zeros((nb_segments, nb_channels), dtype=np.float64)
counters = np.zeros((nb_segments,), dtype=np.int32)
for seg_id in sm.xrange(nb_segments):
if not replace_flags[seg_id % nb_flags]:
counters[seg_id] = -1
for y in sm.xrange(height):
for x in sm.xrange(width):
seg_id = segments[y, x]
count = counters[seg_id]
if count != -1:
col = image[y, x, :]
average_colors[seg_id] += col
counters[seg_id] += 1
counters = np.maximum(counters, 1)
counters = counters.reshape((-1, 1))
average_colors /= counters
average_colors = average_colors.astype(output_dtype)
return average_colors
# Added in 0.5.0.
@_numbajit(nopython=True, nogil=True, cache=True)
def _replace_segments_numba_apply_avg_cols_(
image, segments, replace_flags, average_colors
):
height, width = image.shape[0:2]
nb_flags = len(replace_flags)
for y in sm.xrange(height):
for x in sm.xrange(width):
seg_id = segments[y, x]
if replace_flags[seg_id % nb_flags]:
image[y, x, :] = average_colors[seg_id]
return image
# TODO don't average the alpha channel for RGBA?
def segment_voronoi(image, cell_coordinates, replace_mask=None):
"""Average colors within voronoi cells of an image.
**Supported dtypes**:
if (image size <= max_size):
* ``uint8``: yes; fully tested
* ``uint16``: no; not tested
* ``uint32``: no; not tested
* ``uint64``: no; not tested
* ``int8``: no; not tested
* ``int16``: no; not tested
* ``int32``: no; not tested
* ``int64``: no; not tested
* ``float16``: no; not tested
* ``float32``: no; not tested
* ``float64``: no; not tested
* ``float128``: no; not tested
* ``bool``: no; not tested
if (image size > max_size):
minimum of (
``imgaug.augmenters.segmentation.Voronoi(image size <= max_size)``,
:func:`~imgaug.augmenters.segmentation._ensure_image_max_size`
)
Parameters
----------
image : ndarray
The image to convert to a voronoi image. May be ``HxW`` or
``HxWxC``. Note that for ``RGBA`` images the alpha channel
will currently also by averaged.
cell_coordinates : ndarray
A ``Nx2`` float array containing the center coordinates of voronoi
cells on the image. Values are expected to be in the interval
``[0.0, height-1.0]`` for the y-axis (x-axis analogous).
If this array contains no coordinate, the image will not be
changed.
replace_mask : None or ndarray, optional
Boolean mask of the same length as `cell_coordinates`, denoting
for each cell whether its pixels are supposed to be replaced
by the cell's average color (``True``) or left untouched (``False``).
If this is set to ``None``, all cells will be replaced.
Returns
-------
ndarray
Voronoi image.
"""
input_dims = image.ndim
if input_dims == 2:
image = image[..., np.newaxis]
if len(cell_coordinates) <= 0:
if input_dims == 2:
return image[..., 0]
return image
height, width = image.shape[0:2]
ids_of_nearest_cells = \
_match_pixels_with_voronoi_cells(height, width, cell_coordinates)
image_aug = replace_segments_(
image,
ids_of_nearest_cells.reshape(image.shape[0:2]),
replace_mask
)
if input_dims == 2:
return image_aug[..., 0]
return image_aug
def _match_pixels_with_voronoi_cells(height, width, cell_coordinates):
# deferred import so that scipy is an optional dependency
from scipy.spatial import cKDTree as KDTree # TODO add scipy for reqs
tree = KDTree(cell_coordinates)
pixel_coords = _generate_pixel_coords(height, width)
pixel_coords_subpixel = pixel_coords.astype(np.float32) + 0.5
ids_of_nearest_cells = tree.query(pixel_coords_subpixel)[1]
return ids_of_nearest_cells
def _generate_pixel_coords(height, width):
xx, yy = np.meshgrid(np.arange(width), np.arange(height))
return np.c_[xx.ravel(), yy.ravel()]
# TODO this can be reduced down to a similar problem as Superpixels:
# generate an integer-based class id map of segments, then replace all
# segments with the same class id by the average color within that
# segment
class Voronoi(meta.Augmenter):
"""Average colors of an image within Voronoi cells.
This augmenter performs the following steps:
1. Query `points_sampler` to sample random coordinates of cell
centers. On the image.
2. Estimate for each pixel to which voronoi cell (i.e. segment)
it belongs. Each pixel belongs to the cell with the closest center
coordinate (euclidean distance).
3. Compute for each cell the average color of the pixels within it.
4. Replace the pixels of `p_replace` percent of all cells by their
average color. Do not change the pixels of ``(1 - p_replace)``
percent of all cells. (The percentages are average values over
many images. Some images may get more/less cells replaced by
their average color.)
This code is very loosely based on
https://codegolf.stackexchange.com/questions/50299/draw-an-image-as-a-voronoi-map/50345#50345
**Supported dtypes**:
See :func:`imgaug.augmenters.segmentation.segment_voronoi`.
Parameters
----------
points_sampler : IPointsSampler
A points sampler which will be queried per image to generate the
coordinates of the centers of voronoi cells.
p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Defines for any segment the probability that the pixels within that
segment are replaced by their average color (otherwise, the pixels
are not changed).
Examples:
* A probability of ``0.0`` would mean, that the pixels in no
segment are replaced by their average color (image is not
changed at all).
* A probability of ``0.5`` would mean, that around half of all
segments are replaced by their average color.
* A probability of ``1.0`` would mean, that all segments are
replaced by their average color (resulting in a voronoi
image).
Behaviour based on chosen datatypes for this parameter:
* If a ``number``, then that ``number`` will always be used.
* If ``tuple`` ``(a, b)``, then a random probability will be
sampled from the interval ``[a, b]`` per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, it is expected to return
values between ``0.0`` and ``1.0`` and will be queried *for each
individual segment* to determine whether it is supposed to
be averaged (``>0.5``) or not (``<=0.5``).
Recommended to be some form of ``Binomial(...)``.
max_size : int or None, optional
Maximum image size at which the augmentation is performed.
If the width or height of an image exceeds this value, it will be
downscaled before the augmentation so that the longest side
matches `max_size`.
This is done to speed up the process. The final output image has the
same size as the input image. Note that in case `p_replace` is below
``1.0``, the down-/upscaling will affect the not-replaced pixels too.
Use ``None`` to apply no down-/upscaling.
interpolation : int or str, optional
Interpolation method to use during downscaling when `max_size` is
exceeded. Valid methods are the same as in
:func:`~imgaug.imgaug.imresize_single_image`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> points_sampler = iaa.RegularGridPointsSampler(n_cols=20, n_rows=40)
>>> aug = iaa.Voronoi(points_sampler)
Create an augmenter that places a ``20x40`` (``HxW``) grid of cells on
the image and replaces all pixels within each cell by the cell's average
color. The process is performed at an image size not exceeding ``128`` px
on any side (default). If necessary, the downscaling is performed using
``linear`` interpolation (default).
>>> points_sampler = iaa.DropoutPointsSampler(
>>> iaa.RelativeRegularGridPointsSampler(
>>> n_cols_frac=(0.05, 0.2),
>>> n_rows_frac=0.1),
>>> 0.2)
>>> aug = iaa.Voronoi(points_sampler, p_replace=0.9, max_size=None)
Create a voronoi augmenter that generates a grid of cells dynamically
adapted to the image size. Larger images get more cells. On the x-axis,
the distance between two cells is ``w * W`` pixels, where ``W`` is the
width of the image and ``w`` is always ``0.1``. On the y-axis,
the distance between two cells is ``h * H`` pixels, where ``H`` is the
height of the image and ``h`` is sampled uniformly from the interval
``[0.05, 0.2]``. To make the voronoi pattern less regular, about ``20``
percent of the cell coordinates are randomly dropped (i.e. the remaining
cells grow in size). In contrast to the first example, the image is not
resized (if it was, the sampling would happen *after* the resizing,
which would affect ``W`` and ``H``). Not all voronoi cells are replaced
by their average color, only around ``90`` percent of them. The
remaining ``10`` percent's pixels remain unchanged.
"""
def __init__(self, points_sampler, p_replace=1.0, max_size=128,
interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Voronoi, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
assert isinstance(points_sampler, IPointsSampler), (
"Expected 'points_sampler' to be an instance of IPointsSampler, "
"got %s." % (type(points_sampler),))
self.points_sampler = points_sampler
self.p_replace = iap.handle_probability_param(
p_replace, "p_replace", tuple_to_uniform=True, list_to_choice=True)
self.max_size = max_size
self.interpolation = interpolation
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
iadt.allow_only_uint8(images, augmenter=self)
rss = random_state.duplicate(len(images))
for i, (image, rs) in enumerate(zip(images, rss)):
batch.images[i] = self._augment_single_image(image, rs)
return batch
def _augment_single_image(self, image, random_state):
rss = random_state.duplicate(2)
orig_shape = image.shape
image = _ensure_image_max_size(image, self.max_size, self.interpolation)
cell_coordinates = self.points_sampler.sample_points([image], rss[0])[0]
p_replace = self.p_replace.draw_samples((len(cell_coordinates),),
rss[1])
replace_mask = (p_replace > 0.5)
image_aug = segment_voronoi(image, cell_coordinates, replace_mask)
if orig_shape != image_aug.shape:
image_aug = ia.imresize_single_image(
image_aug,
orig_shape[0:2],
interpolation=self.interpolation)
return image_aug
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.points_sampler, self.p_replace, self.max_size,
self.interpolation]
class UniformVoronoi(Voronoi):
"""Uniformly sample Voronoi cells on images and average colors within them.
This augmenter is a shortcut for the combination of
:class:`~imgaug.augmenters.segmentation.Voronoi` with
:class:`~imgaug.augmenters.segmentation.UniformPointsSampler`. Hence, it
generates a fixed amount of ``N`` random coordinates of voronoi cells on
each image. The cell coordinates are sampled uniformly using the image
height and width as maxima.
**Supported dtypes**:
See :class:`~imgaug.augmenters.segmentation.Voronoi`.
Parameters
----------
n_points : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of points to sample on each image.
* If a single ``int``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Defines for any segment the probability that the pixels within that
segment are replaced by their average color (otherwise, the pixels
are not changed).
Examples:
* A probability of ``0.0`` would mean, that the pixels in no
segment are replaced by their average color (image is not
changed at all).
* A probability of ``0.5`` would mean, that around half of all
segments are replaced by their average color.
* A probability of ``1.0`` would mean, that all segments are
replaced by their average color (resulting in a voronoi
image).
Behaviour based on chosen datatypes for this parameter:
* If a ``number``, then that ``number`` will always be used.
* If ``tuple`` ``(a, b)``, then a random probability will be
sampled from the interval ``[a, b]`` per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, it is expected to return
values between ``0.0`` and ``1.0`` and will be queried *for each
individual segment* to determine whether it is supposed to
be averaged (``>0.5``) or not (``<=0.5``).
Recommended to be some form of ``Binomial(...)``.
max_size : int or None, optional
Maximum image size at which the augmentation is performed.
If the width or height of an image exceeds this value, it will be
downscaled before the augmentation so that the longest side
matches `max_size`.
This is done to speed up the process. The final output image has the
same size as the input image. Note that in case `p_replace` is below
``1.0``, the down-/upscaling will affect the not-replaced pixels too.
Use ``None`` to apply no down-/upscaling.
interpolation : int or str, optional
Interpolation method to use during downscaling when `max_size` is
exceeded. Valid methods are the same as in
:func:`~imgaug.imgaug.imresize_single_image`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.UniformVoronoi((100, 500))
Sample for each image uniformly the number of voronoi cells ``N`` from the
interval ``[100, 500]``. Then generate ``N`` coordinates by sampling
uniformly the x-coordinates from ``[0, W]`` and the y-coordinates from
``[0, H]``, where ``H`` is the image height and ``W`` the image width.
Then use these coordinates to group the image pixels into voronoi
cells and average the colors within them. The process is performed at an
image size not exceeding ``128`` px on any side (default). If necessary,
the downscaling is performed using ``linear`` interpolation (default).
>>> aug = iaa.UniformVoronoi(250, p_replace=0.9, max_size=None)
Same as above, but always samples ``N=250`` cells, replaces only
``90`` percent of them with their average color (the pixels of the
remaining ``10`` percent are not changed) and performs the transformation
at the original image size (``max_size=None``).
"""
def __init__(self, n_points=(50, 500), p_replace=(0.5, 1.0), max_size=128,
interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(UniformVoronoi, self).__init__(
points_sampler=UniformPointsSampler(n_points),
p_replace=p_replace,
max_size=max_size,
interpolation=interpolation,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class RegularGridVoronoi(Voronoi):
"""Sample Voronoi cells from regular grids and color-average them.
This augmenter is a shortcut for the combination of
:class:`~imgaug.augmenters.segmentation.Voronoi`,
:class:`~imgaug.augmenters.segmentation.RegularGridPointsSampler` and
:class:`~imgaug.augmenters.segmentation.DropoutPointsSampler`. Hence, it
generates a regular grid with ``R`` rows and ``C`` columns of coordinates
on each image. Then, it drops ``p`` percent of the ``R*C`` coordinates
to randomize the grid. Each image pixel then belongs to the voronoi
cell with the closest coordinate.
**Supported dtypes**:
See :class:`~imgaug.augmenters.segmentation.Voronoi`.
Parameters
----------
n_rows : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of rows of coordinates to place on each image, i.e. the number
of coordinates on the y-axis. Note that for each image, the sampled
value is clipped to the interval ``[1..H]``, where ``H`` is the image
height.
* If a single ``int``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
n_cols : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of columns of coordinates to place on each image, i.e. the
number of coordinates on the x-axis. Note that for each image, the
sampled value is clipped to the interval ``[1..W]``, where ``W`` is
the image width.
* If a single ``int``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
p_drop_points : number or tuple of number or imgaug.parameters.StochasticParameter, optional
The probability that a coordinate will be removed from the list
of all sampled coordinates. A value of ``1.0`` would mean that (on
average) ``100`` percent of all coordinates will be dropped,
while ``0.0`` denotes ``0`` percent. Note that this sampler will
always ensure that at least one coordinate is left after the dropout
operation, i.e. even ``1.0`` will only drop all *except one*
coordinate.
* If a ``float``, then that value will be used for all images.
* If a ``tuple`` ``(a, b)``, then a value ``p`` will be sampled
from the interval ``[a, b]`` per image.
* If a ``StochasticParameter``, then this parameter will be used to
determine per coordinate whether it should be *kept* (sampled
value of ``>0.5``) or shouldn't be kept (sampled value of
``<=0.5``). If you instead want to provide the probability as
a stochastic parameter, you can usually do
``imgaug.parameters.Binomial(1-p)`` to convert parameter `p` to
a 0/1 representation.
p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Defines for any segment the probability that the pixels within that
segment are replaced by their average color (otherwise, the pixels
are not changed).
Examples:
* A probability of ``0.0`` would mean, that the pixels in no
segment are replaced by their average color (image is not
changed at all).
* A probability of ``0.5`` would mean, that around half of all
segments are replaced by their average color.
* A probability of ``1.0`` would mean, that all segments are
replaced by their average color (resulting in a voronoi
image).
Behaviour based on chosen datatypes for this parameter:
* If a ``number``, then that number will always be used.
* If ``tuple`` ``(a, b)``, then a random probability will be
sampled from the interval ``[a, b]`` per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, it is expected to return
values between ``0.0`` and ``1.0`` and will be queried *for each
individual segment* to determine whether it is supposed to
be averaged (``>0.5``) or not (``<=0.5``).
Recommended to be some form of ``Binomial(...)``.
max_size : int or None, optional
Maximum image size at which the augmentation is performed.
If the width or height of an image exceeds this value, it will be
downscaled before the augmentation so that the longest side
matches `max_size`.
This is done to speed up the process. The final output image has the
same size as the input image. Note that in case `p_replace` is below
``1.0``, the down-/upscaling will affect the not-replaced pixels too.
Use ``None`` to apply no down-/upscaling.
interpolation : int or str, optional
Interpolation method to use during downscaling when `max_size` is
exceeded. Valid methods are the same as in
:func:`~imgaug.imgaug.imresize_single_image`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.RegularGridVoronoi(10, 20)
Place a regular grid of ``10x20`` (``height x width``) coordinates on
each image. Randomly drop on average ``20`` percent of these points
to create a less regular pattern. Then use the remaining coordinates
to group the image pixels into voronoi cells and average the colors
within them. The process is performed at an image size not exceeding
``128`` px on any side (default). If necessary, the downscaling is
performed using ``linear`` interpolation (default).
>>> aug = iaa.RegularGridVoronoi(
>>> (10, 30), 20, p_drop_points=0.0, p_replace=0.9, max_size=None)
Same as above, generates a grid with randomly ``10`` to ``30`` rows,
drops none of the generates points, replaces only ``90`` percent of
the voronoi cells with their average color (the pixels of the remaining
``10`` percent are not changed) and performs the transformation
at the original image size (``max_size=None``).
"""
def __init__(self, n_rows=(10, 30), n_cols=(10, 30),
p_drop_points=(0.0, 0.5), p_replace=(0.5, 1.0),
max_size=128, interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(RegularGridVoronoi, self).__init__(
points_sampler=DropoutPointsSampler(
RegularGridPointsSampler(n_rows, n_cols),
p_drop_points
),
p_replace=p_replace,
max_size=max_size,
interpolation=interpolation,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class RelativeRegularGridVoronoi(Voronoi):
"""Sample Voronoi cells from image-dependent grids and color-average them.
This augmenter is a shortcut for the combination of
:class:`~imgaug.augmenters.segmentation.Voronoi`,
:class:`~imgaug.augmenters.segmentation.RegularGridPointsSampler` and
:class:`~imgaug.augmenters.segmentation.DropoutPointsSampler`. Hence, it
generates a regular grid with ``R`` rows and ``C`` columns of coordinates
on each image. Then, it drops ``p`` percent of the ``R*C`` coordinates
to randomize the grid. Each image pixel then belongs to the voronoi
cell with the closest coordinate.
.. note::
In contrast to the other voronoi augmenters, this one uses
``None`` as the default value for `max_size`, i.e. the color averaging
is always performed at full resolution. This enables the augmenter to
make use of the additional points on larger images. It does
however slow down the augmentation process.
**Supported dtypes**:
See :class:`~imgaug.augmenters.segmentation.Voronoi`.
Parameters
----------
n_rows_frac : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Relative number of coordinates to place on the y-axis. For a value
``y`` and image height ``H`` the number of actually placed coordinates
(i.e. computed rows) is given by ``int(round(y*H))``.
Note that for each image, the number of coordinates is clipped to the
interval ``[1,H]``, where ``H`` is the image height.
* If a single ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the interval
``[a, b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
n_cols_frac : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Relative number of coordinates to place on the x-axis. For a value
``x`` and image height ``W`` the number of actually placed coordinates
(i.e. computed columns) is given by ``int(round(x*W))``.
Note that for each image, the number of coordinates is clipped to the
interval ``[1,W]``, where ``W`` is the image width.
* If a single ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the interval
``[a, b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
p_drop_points : number or tuple of number or imgaug.parameters.StochasticParameter, optional
The probability that a coordinate will be removed from the list
of all sampled coordinates. A value of ``1.0`` would mean that (on
average) ``100`` percent of all coordinates will be dropped,
while ``0.0`` denotes ``0`` percent. Note that this sampler will
always ensure that at least one coordinate is left after the dropout
operation, i.e. even ``1.0`` will only drop all *except one*
coordinate.
* If a ``float``, then that value will be used for all images.
* If a ``tuple`` ``(a, b)``, then a value ``p`` will be sampled
from the interval ``[a, b]`` per image.
* If a ``StochasticParameter``, then this parameter will be used to
determine per coordinate whether it should be *kept* (sampled
value of ``>0.5``) or shouldn't be kept (sampled value of
``<=0.5``). If you instead want to provide the probability as
a stochastic parameter, you can usually do
``imgaug.parameters.Binomial(1-p)`` to convert parameter `p` to
a 0/1 representation.
p_replace : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Defines for any segment the probability that the pixels within that
segment are replaced by their average color (otherwise, the pixels
are not changed).
Examples:
* A probability of ``0.0`` would mean, that the pixels in no
segment are replaced by their average color (image is not
changed at all).
* A probability of ``0.5`` would mean, that around half of all
segments are replaced by their average color.
* A probability of ``1.0`` would mean, that all segments are
replaced by their average color (resulting in a voronoi
image).
Behaviour based on chosen datatypes for this parameter:
* If a ``number``, then that ``number`` will always be used.
* If ``tuple`` ``(a, b)``, then a random probability will be
sampled from the interval ``[a, b]`` per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, it is expected to return
values between ``0.0`` and ``1.0`` and will be queried *for each
individual segment* to determine whether it is supposed to
be averaged (``>0.5``) or not (``<=0.5``).
Recommended to be some form of ``Binomial(...)``.
max_size : int or None, optional
Maximum image size at which the augmentation is performed.
If the width or height of an image exceeds this value, it will be
downscaled before the augmentation so that the longest side
matches `max_size`.
This is done to speed up the process. The final output image has the
same size as the input image. Note that in case `p_replace` is below
``1.0``, the down-/upscaling will affect the not-replaced pixels too.
Use ``None`` to apply no down-/upscaling.
interpolation : int or str, optional
Interpolation method to use during downscaling when `max_size` is
exceeded. Valid methods are the same as in
:func:`~imgaug.imgaug.imresize_single_image`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.RelativeRegularGridVoronoi(0.1, 0.25)
Place a regular grid of ``R x C`` coordinates on each image, where
``R`` is the number of rows and computed as ``R=0.1*H`` with ``H`` being
the height of the input image. ``C`` is the number of columns and
analogously estimated from the image width ``W`` as ``C=0.25*W``.
Larger images will lead to larger ``R`` and ``C`` values.
On average, ``20`` percent of these grid coordinates are randomly
dropped to create a less regular pattern. Then, the remaining coordinates
are used to group the image pixels into voronoi cells and the colors
within them are averaged.
>>> aug = iaa.RelativeRegularGridVoronoi(
>>> (0.03, 0.1), 0.1, p_drop_points=0.0, p_replace=0.9, max_size=512)
Same as above, generates a grid with randomly ``R=r*H`` rows, where
``r`` is sampled uniformly from the interval ``[0.03, 0.1]`` and
``C=0.1*W`` rows. No points are dropped. The augmenter replaces only
``90`` percent of the voronoi cells with their average color (the pixels
of the remaining ``10`` percent are not changed). Images larger than
``512`` px are temporarily downscaled (*before* sampling the grid points)
so that no side exceeds ``512`` px. This improves performance, but
degrades the quality of the resulting image.
"""
def __init__(self, n_rows_frac=(0.05, 0.15), n_cols_frac=(0.05, 0.15),
p_drop_points=(0.0, 0.5), p_replace=(0.5, 1.0),
max_size=None, interpolation="linear",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(RelativeRegularGridVoronoi, self).__init__(
points_sampler=DropoutPointsSampler(
RelativeRegularGridPointsSampler(n_rows_frac, n_cols_frac),
p_drop_points
),
p_replace=p_replace,
max_size=max_size,
interpolation=interpolation,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
@six.add_metaclass(ABCMeta)
class IPointsSampler(object):
"""Interface for all point samplers.
Point samplers return coordinate arrays of shape ``Nx2``.
These coordinates can be used in other augmenters, see e.g.
:class:`~imgaug.augmenters.segmentation.Voronoi`.
"""
@abstractmethod
def sample_points(self, images, random_state):
"""Generate coordinates of points on images.
Parameters
----------
images : ndarray or list of ndarray
One or more images for which to generate points.
If this is a ``list`` of arrays, each one of them is expected to
have three dimensions.
If this is an array, it must be four-dimensional and the first
axis is expected to denote the image index. For ``RGB`` images
the array would hence have to be of shape ``(N, H, W, 3)``.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
A random state to use for any probabilistic function required
during the point sampling.
See :func:`~imgaug.random.RNG` for details.
Returns
-------
ndarray
An ``(N,2)`` ``float32`` array containing ``(x,y)`` subpixel
coordinates, all of which being within the intervals
``[0.0, width]`` and ``[0.0, height]``.
"""
def _verify_sample_points_images(images):
assert len(images) > 0, "Expected at least one image, got zero."
if isinstance(images, list):
assert all([ia.is_np_array(image) for image in images]), (
"Expected list of numpy arrays, got list of types %s." % (
", ".join([str(type(image)) for image in images]),))
assert all([image.ndim == 3 for image in images]), (
"Expected each image to have three dimensions, "
"got dimensions %s." % (
", ".join([str(image.ndim) for image in images]),))
else:
assert ia.is_np_array(images), (
"Expected either a list of numpy arrays or a single numpy "
"array of shape NxHxWxC. Got type %s." % (type(images),))
assert images.ndim == 4, (
"Expected a four-dimensional array of shape NxHxWxC. "
"Got shape %d dimensions (shape: %s)." % (
images.ndim, images.shape))
class RegularGridPointsSampler(IPointsSampler):
"""Sampler that generates a regular grid of coordinates on an image.
'Regular grid' here means that on each axis all coordinates have the
same distance from each other. Note that the distance may change between
axis.
Parameters
----------
n_rows : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of rows of coordinates to place on each image, i.e. the number
of coordinates on the y-axis. Note that for each image, the sampled
value is clipped to the interval ``[1..H]``, where ``H`` is the image
height.
* If a single ``int``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
n_cols : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of columns of coordinates to place on each image, i.e. the
number of coordinates on the x-axis. Note that for each image, the
sampled value is clipped to the interval ``[1..W]``, where ``W`` is
the image width.
* If a single ``int``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> sampler = iaa.RegularGridPointsSampler(
>>> n_rows=(5, 20),
>>> n_cols=50)
Create a point sampler that generates regular grids of points. These grids
contain ``r`` points on the y-axis, where ``r`` is sampled
uniformly from the discrete interval ``[5..20]`` per image.
On the x-axis, the grids always contain ``50`` points.
"""
def __init__(self, n_rows, n_cols):
self.n_rows = iap.handle_discrete_param(
n_rows, "n_rows", value_range=(1, None),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
self.n_cols = iap.handle_discrete_param(
n_cols, "n_cols", value_range=(1, None),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
def sample_points(self, images, random_state):
random_state = iarandom.RNG.create_if_not_rng_(random_state)
_verify_sample_points_images(images)
n_rows_lst, n_cols_lst = self._draw_samples(images, random_state)
return self._generate_point_grids(images, n_rows_lst, n_cols_lst)
def _draw_samples(self, images, random_state):
rss = random_state.duplicate(2)
n_rows_lst = self.n_rows.draw_samples(len(images), random_state=rss[0])
n_cols_lst = self.n_cols.draw_samples(len(images), random_state=rss[1])
return self._clip_rows_and_cols(n_rows_lst, n_cols_lst, images)
@classmethod
def _clip_rows_and_cols(cls, n_rows_lst, n_cols_lst, images):
heights = np.int32([image.shape[0] for image in images])
widths = np.int32([image.shape[1] for image in images])
# We clip intentionally not to H-1 or W-1 here. If e.g. an image has
# a width of 1, we want to get a maximum of 1 column of coordinates.
# Note that we use two clips here instead of e.g. clip(., 1, height),
# because images can have height/width zero and in these cases numpy
# prefers the smaller value in clip(). But currently we want to get
# at least 1 point for such images.
n_rows_lst = np.clip(n_rows_lst, None, heights)
n_cols_lst = np.clip(n_cols_lst, None, widths)
n_rows_lst = np.clip(n_rows_lst, 1, None)
n_cols_lst = np.clip(n_cols_lst, 1, None)
return n_rows_lst, n_cols_lst
@classmethod
def _generate_point_grids(cls, images, n_rows_lst, n_cols_lst):
grids = []
for image, n_rows_i, n_cols_i in zip(images, n_rows_lst, n_cols_lst):
grids.append(cls._generate_point_grid(image, n_rows_i, n_cols_i))
return grids
@classmethod
def _generate_point_grid(cls, image, n_rows, n_cols):
height, width = image.shape[0:2]
# We do not have to subtract 1 here from height/width as these are
# subpixel coordinates. Technically, we could also place the cell
# centers outside of the image plane.
y_spacing = height / n_rows
y_start = 0.0 + y_spacing/2
y_end = height - y_spacing/2
if y_start - 1e-4 <= y_end <= y_start + 1e-4:
yy = np.float32([y_start])
else:
yy = np.linspace(y_start, y_end, num=n_rows)
x_spacing = width / n_cols
x_start = 0.0 + x_spacing/2
x_end = width - x_spacing/2
if x_start - 1e-4 <= x_end <= x_start + 1e-4:
xx = np.float32([x_start])
else:
xx = np.linspace(x_start, x_end, num=n_cols)
xx, yy = np.meshgrid(xx, yy)
grid = np.vstack([xx.ravel(), yy.ravel()]).T
return grid
def __repr__(self):
return "RegularGridPointsSampler(%s, %s)" % (self.n_rows, self.n_cols)
def __str__(self):
return self.__repr__()
class RelativeRegularGridPointsSampler(IPointsSampler):
"""Regular grid coordinate sampler; places more points on larger images.
This is similar to ``RegularGridPointsSampler``, but the number of rows
and columns is given as fractions of each image's height and width.
Hence, more coordinates are generated for larger images.
Parameters
----------
n_rows_frac : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Relative number of coordinates to place on the y-axis. For a value
``y`` and image height ``H`` the number of actually placed coordinates
(i.e. computed rows) is given by ``int(round(y*H))``.
Note that for each image, the number of coordinates is clipped to the
interval ``[1,H]``, where ``H`` is the image height.
* If a single ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the interval
``[a, b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
n_cols_frac : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Relative number of coordinates to place on the x-axis. For a value
``x`` and image height ``W`` the number of actually placed coordinates
(i.e. computed columns) is given by ``int(round(x*W))``.
Note that for each image, the number of coordinates is clipped to the
interval ``[1,W]``, where ``W`` is the image width.
* If a single ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the interval
``[a, b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> sampler = iaa.RelativeRegularGridPointsSampler(
>>> n_rows_frac=(0.01, 0.1),
>>> n_cols_frac=0.2)
Create a point sampler that generates regular grids of points. These grids
contain ``round(y*H)`` points on the y-axis, where ``y`` is sampled
uniformly from the interval ``[0.01, 0.1]`` per image and ``H`` is the
image height. On the x-axis, the grids always contain ``0.2*W`` points,
where ``W`` is the image width.
"""
def __init__(self, n_rows_frac, n_cols_frac):
eps = 1e-4
self.n_rows_frac = iap.handle_continuous_param(
n_rows_frac, "n_rows_frac", value_range=(0.0+eps, 1.0),
tuple_to_uniform=True, list_to_choice=True)
self.n_cols_frac = iap.handle_continuous_param(
n_cols_frac, "n_cols_frac", value_range=(0.0+eps, 1.0),
tuple_to_uniform=True, list_to_choice=True)
def sample_points(self, images, random_state):
# pylint: disable=protected-access
random_state = iarandom.RNG.create_if_not_rng_(random_state)
_verify_sample_points_images(images)
n_rows, n_cols = self._draw_samples(images, random_state)
return RegularGridPointsSampler._generate_point_grids(images,
n_rows, n_cols)
def _draw_samples(self, images, random_state):
# pylint: disable=protected-access
n_augmentables = len(images)
rss = random_state.duplicate(2)
n_rows_frac = self.n_rows_frac.draw_samples(n_augmentables,
random_state=rss[0])
n_cols_frac = self.n_cols_frac.draw_samples(n_augmentables,
random_state=rss[1])
heights = np.int32([image.shape[0] for image in images])
widths = np.int32([image.shape[1] for image in images])
n_rows = np.round(n_rows_frac * heights)
n_cols = np.round(n_cols_frac * widths)
n_rows, n_cols = RegularGridPointsSampler._clip_rows_and_cols(
n_rows, n_cols, images)
return n_rows.astype(np.int32), n_cols.astype(np.int32)
def __repr__(self):
return "RelativeRegularGridPointsSampler(%s, %s)" % (
self.n_rows_frac, self.n_cols_frac)
def __str__(self):
return self.__repr__()
class DropoutPointsSampler(IPointsSampler):
"""Remove a defined fraction of sampled points.
Parameters
----------
other_points_sampler : IPointsSampler
Another point sampler that is queried to generate a list of points.
The dropout operation will be applied to that list.
p_drop : number or tuple of number or imgaug.parameters.StochasticParameter
The probability that a coordinate will be removed from the list
of all sampled coordinates. A value of ``1.0`` would mean that (on
average) ``100`` percent of all coordinates will be dropped,
while ``0.0`` denotes ``0`` percent. Note that this sampler will
always ensure that at least one coordinate is left after the dropout
operation, i.e. even ``1.0`` will only drop all *except one*
coordinate.
* If a ``float``, then that value will be used for all images.
* If a ``tuple`` ``(a, b)``, then a value ``p`` will be sampled
from the interval ``[a, b]`` per image.
* If a ``StochasticParameter``, then this parameter will be used to
determine per coordinate whether it should be *kept* (sampled
value of ``>0.5``) or shouldn't be kept (sampled value of
``<=0.5``). If you instead want to provide the probability as
a stochastic parameter, you can usually do
``imgaug.parameters.Binomial(1-p)`` to convert parameter `p` to
a 0/1 representation.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> sampler = iaa.DropoutPointsSampler(
>>> iaa.RegularGridPointsSampler(10, 20),
>>> 0.2)
Create a point sampler that first generates points following a regular
grid of ``10`` rows and ``20`` columns, then randomly drops ``20`` percent
of these points.
"""
def __init__(self, other_points_sampler, p_drop):
assert isinstance(other_points_sampler, IPointsSampler), (
"Expected to get an instance of IPointsSampler as argument "
"'other_points_sampler', got type %s." % (
type(other_points_sampler),))
self.other_points_sampler = other_points_sampler
self.p_drop = self._convert_p_drop_to_inverted_mask_param(p_drop)
@classmethod
def _convert_p_drop_to_inverted_mask_param(cls, p_drop):
# TODO this is the same as in Dropout, make DRY
# TODO add list as an option
if ia.is_single_number(p_drop):
p_drop = iap.Binomial(1 - p_drop)
elif ia.is_iterable(p_drop):
assert len(p_drop) == 2, (
"Expected 'p_drop' given as an iterable to contain exactly "
"2 values, got %d." % (len(p_drop),))
assert p_drop[0] < p_drop[1], (
"Expected 'p_drop' given as iterable to contain exactly 2 "
"values (a, b) with a < b. Got %.4f and %.4f." % (
p_drop[0], p_drop[1]))
assert 0 <= p_drop[0] <= 1.0 and 0 <= p_drop[1] <= 1.0, (
"Expected 'p_drop' given as iterable to only contain values "
"in the interval [0.0, 1.0], got %.4f and %.4f." % (
p_drop[0], p_drop[1]))
p_drop = iap.Binomial(iap.Uniform(1 - p_drop[1], 1 - p_drop[0]))
elif isinstance(p_drop, iap.StochasticParameter):
pass
else:
raise Exception(
"Expected p_drop to be float or int or StochasticParameter, "
"got %s." % (type(p_drop),))
return p_drop
def sample_points(self, images, random_state):
random_state = iarandom.RNG.create_if_not_rng_(random_state)
_verify_sample_points_images(images)
rss = random_state.duplicate(2)
points_on_images = self.other_points_sampler.sample_points(images,
rss[0])
drop_masks = self._draw_samples(points_on_images, rss[1])
return self._apply_dropout_masks(points_on_images, drop_masks)
def _draw_samples(self, points_on_images, random_state):
rss = random_state.duplicate(len(points_on_images))
drop_masks = [self._draw_samples_for_image(points_on_image, rs)
for points_on_image, rs
in zip(points_on_images, rss)]
return drop_masks
def _draw_samples_for_image(self, points_on_image, random_state):
drop_samples = self.p_drop.draw_samples((len(points_on_image),),
random_state)
keep_mask = (drop_samples > 0.5)
return keep_mask
@classmethod
def _apply_dropout_masks(cls, points_on_images, keep_masks):
points_on_images_dropped = []
for points_on_image, keep_mask in zip(points_on_images, keep_masks):
if len(points_on_image) == 0:
# other sampler didn't provide any points
poi_dropped = points_on_image
else:
if not np.any(keep_mask):
# keep at least one point if all were supposed to be
# dropped
# TODO this could also be moved into its own point sampler,
# like AtLeastOnePoint(...)
idx = (len(points_on_image) - 1) // 2
keep_mask = np.copy(keep_mask)
keep_mask[idx] = True
poi_dropped = points_on_image[keep_mask, :]
points_on_images_dropped.append(poi_dropped)
return points_on_images_dropped
def __repr__(self):
return "DropoutPointsSampler(%s, %s)" % (self.other_points_sampler,
self.p_drop)
def __str__(self):
return self.__repr__()
class UniformPointsSampler(IPointsSampler):
"""Sample points uniformly on images.
This point sampler generates `n_points` points per image. The x- and
y-coordinates are both sampled from uniform distributions matching the
respective image width and height.
Parameters
----------
n_points : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Number of points to sample on each image.
* If a single ``int``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value from the discrete
interval ``[a..b]`` will be sampled per image.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then that parameter will be
queried to draw one value per image.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> sampler = iaa.UniformPointsSampler(500)
Create a point sampler that generates an array of ``500`` random points for
each input image. The x- and y-coordinates of each point are sampled
from uniform distributions.
"""
def __init__(self, n_points):
self.n_points = iap.handle_discrete_param(
n_points, "n_points", value_range=(1, None),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False)
def sample_points(self, images, random_state):
random_state = iarandom.RNG.create_if_not_rng_(random_state)
_verify_sample_points_images(images)
rss = random_state.duplicate(2)
n_points_imagewise = self._draw_samples(len(images), rss[0])
n_points_total = np.sum(n_points_imagewise)
n_components_total = 2 * n_points_total
coords_relative = rss[1].uniform(0.0, 1.0, n_components_total)
coords_relative_xy = coords_relative.reshape(n_points_total, 2)
return self._convert_relative_coords_to_absolute(
coords_relative_xy, n_points_imagewise, images)
def _draw_samples(self, n_augmentables, random_state):
n_points = self.n_points.draw_samples((n_augmentables,),
random_state=random_state)
n_points_clipped = np.clip(n_points, 1, None)
return n_points_clipped
@classmethod
def _convert_relative_coords_to_absolute(cls, coords_rel_xy,
n_points_imagewise, images):
coords_absolute = []
i = 0
for image, n_points_image in zip(images, n_points_imagewise):
height, width = image.shape[0:2]
xx = coords_rel_xy[i:i+n_points_image, 0]
yy = coords_rel_xy[i:i+n_points_image, 1]
xx_int = np.clip(np.round(xx * width), 0, width)
yy_int = np.clip(np.round(yy * height), 0, height)
coords_absolute.append(np.stack([xx_int, yy_int], axis=-1))
i += n_points_image
return coords_absolute
def __repr__(self):
return "UniformPointsSampler(%s)" % (self.n_points,)
def __str__(self):
return self.__repr__()
class SubsamplingPointsSampler(IPointsSampler):
"""Ensure that the number of sampled points is below a maximum.
This point sampler will sample points from another sampler and
then -- in case more points were generated than an allowed maximum --
will randomly pick `n_points_max` of these.
Parameters
----------
other_points_sampler : IPointsSampler
Another point sampler that is queried to generate a ``list`` of points.
The dropout operation will be applied to that ``list``.
n_points_max : int
Maximum number of allowed points. If `other_points_sampler` generates
more points than this maximum, a random subset of size `n_points_max`
will be selected.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> sampler = iaa.SubsamplingPointsSampler(
>>> iaa.RelativeRegularGridPointsSampler(0.1, 0.2),
>>> 50
>>> )
Create a points sampler that places ``y*H`` points on the y-axis (with
``y`` being ``0.1`` and ``H`` being an image's height) and ``x*W`` on
the x-axis (analogous). Then, if that number of placed points exceeds
``50`` (can easily happen for larger images), a random subset of ``50``
points will be picked and returned.
"""
def __init__(self, other_points_sampler, n_points_max):
assert isinstance(other_points_sampler, IPointsSampler), (
"Expected to get an instance of IPointsSampler as argument "
"'other_points_sampler', got type %s." % (
type(other_points_sampler),))
self.other_points_sampler = other_points_sampler
self.n_points_max = np.clip(n_points_max, -1, None)
if self.n_points_max == 0:
ia.warn("Got n_points_max=0 in SubsamplingPointsSampler. "
"This will result in no points ever getting "
"returned.")
def sample_points(self, images, random_state):
random_state = iarandom.RNG.create_if_not_rng_(random_state)
_verify_sample_points_images(images)
rss = random_state.duplicate(len(images) + 1)
points_on_images = self.other_points_sampler.sample_points(
images, rss[-1])
return [self._subsample(points_on_image, self.n_points_max, rs)
for points_on_image, rs
in zip(points_on_images, rss[:-1])]
@classmethod
def _subsample(cls, points_on_image, n_points_max, random_state):
if len(points_on_image) <= n_points_max:
return points_on_image
indices = np.arange(len(points_on_image))
indices_to_keep = random_state.permutation(indices)[0:n_points_max]
return points_on_image[indices_to_keep]
def __repr__(self):
return "SubsamplingPointsSampler(%s, %d)" % (self.other_points_sampler,
self.n_points_max)
def __str__(self):
return self.__repr__()
# TODO Add points subsampler that drops points close to each other first
# TODO Add poisson points sampler
# TODO Add jitter points sampler that moves points around
# for both see https://codegolf.stackexchange.com/questions/50299/draw-an-image-as-a-voronoi-map/50345#50345
================================================
FILE: imgaug/augmenters/size.py
================================================
"""
Augmenters that somehow change the size of the images.
List of augmenters:
* :class:`Resize`
* :class:`CropAndPad`
* :class:`Crop`
* :class:`Pad`
* :class:`PadToFixedSize`
* :class:`CenterPadToFixedSize`
* :class:`CropToFixedSize`
* :class:`CenterCropToFixedSize`
* :class:`CropToMultiplesOf`
* :class:`CenterCropToMultiplesOf`
* :class:`PadToMultiplesOf`
* :class:`CenterPadToMultiplesOf`
* :class:`CropToPowersOf`
* :class:`CenterCropToPowersOf`
* :class:`PadToPowersOf`
* :class:`CenterPadToPowersOf`
* :class:`CropToAspectRatio`
* :class:`CenterCropToAspectRatio`
* :class:`PadToAspectRatio`
* :class:`CenterPadToAspectRatio`
* :class:`CropToSquare`
* :class:`CenterCropToSquare`
* :class:`PadToSquare`
* :class:`CenterPadToSquare`
* :class:`KeepSizeByResize`
"""
from __future__ import print_function, division, absolute_import
import re
import functools
import numpy as np
import cv2
import imgaug as ia
from imgaug.imgaug import _normalize_cv2_input_arr_
from . import meta
from .. import parameters as iap
from .. import dtypes as iadt
def _crop_trbl_to_xyxy(shape, top, right, bottom, left, prevent_zero_size=True):
if prevent_zero_size:
top, bottom = _prevent_zero_size_after_crop_(shape[0], top, bottom)
left, right = _prevent_zero_size_after_crop_(shape[1], left, right)
height, width = shape[0:2]
x1 = left
x2 = width - right
y1 = top
y2 = height - bottom
# these steps prevent negative sizes
# if x2==x1 or y2==y1 then the output arr has size 0 for the respective axis
# note that if height/width of arr is zero, then y2==y1 or x2==x1, which
# is still valid, even if height/width is zero and results in a zero-sized
# axis
x2 = max(x2, x1)
y2 = max(y2, y1)
return x1, y1, x2, y2
def _crop_arr_(arr, top, right, bottom, left, prevent_zero_size=True):
x1, y1, x2, y2 = _crop_trbl_to_xyxy(arr.shape, top, right, bottom, left,
prevent_zero_size=prevent_zero_size)
return arr[y1:y2, x1:x2, ...]
def _crop_and_pad_arr(arr, croppings, paddings, pad_mode="constant",
pad_cval=0, keep_size=False):
height, width = arr.shape[0:2]
image_cr = _crop_arr_(arr, *croppings)
image_cr_pa = pad(
image_cr,
top=paddings[0], right=paddings[1],
bottom=paddings[2], left=paddings[3],
mode=pad_mode, cval=pad_cval)
if keep_size:
image_cr_pa = ia.imresize_single_image(image_cr_pa, (height, width))
return image_cr_pa
def _crop_and_pad_heatmap_(heatmap, croppings_img, paddings_img,
pad_mode="constant", pad_cval=0.0, keep_size=False):
return _crop_and_pad_hms_or_segmaps_(heatmap, croppings_img,
paddings_img, pad_mode, pad_cval,
keep_size)
def _crop_and_pad_segmap_(segmap, croppings_img, paddings_img,
pad_mode="constant", pad_cval=0, keep_size=False):
return _crop_and_pad_hms_or_segmaps_(segmap, croppings_img,
paddings_img, pad_mode, pad_cval,
keep_size)
def _crop_and_pad_hms_or_segmaps_(augmentable, croppings_img,
paddings_img, pad_mode="constant",
pad_cval=None, keep_size=False):
if isinstance(augmentable, ia.HeatmapsOnImage):
arr_attr_name = "arr_0to1"
pad_cval = pad_cval if pad_cval is not None else 0.0
else:
assert isinstance(augmentable, ia.SegmentationMapsOnImage), (
"Expected HeatmapsOnImage or SegmentationMapsOnImage, got %s." % (
type(augmentable)))
arr_attr_name = "arr"
pad_cval = pad_cval if pad_cval is not None else 0
arr = getattr(augmentable, arr_attr_name)
arr_shape_orig = arr.shape
augm_shape = augmentable.shape
croppings_proj = _project_size_changes(croppings_img, augm_shape, arr.shape)
paddings_proj = _project_size_changes(paddings_img, augm_shape, arr.shape)
croppings_proj = _prevent_zero_size_after_crop_trbl_(arr.shape[0],
arr.shape[1],
croppings_proj)
arr_cr = _crop_arr_(arr,
croppings_proj[0], croppings_proj[1],
croppings_proj[2], croppings_proj[3])
arr_cr_pa = pad(
arr_cr,
top=paddings_proj[0], right=paddings_proj[1],
bottom=paddings_proj[2], left=paddings_proj[3],
mode=pad_mode,
cval=pad_cval)
setattr(augmentable, arr_attr_name, arr_cr_pa)
if keep_size:
augmentable = augmentable.resize(arr_shape_orig[0:2])
else:
augmentable.shape = _compute_shape_after_crop_and_pad(
augmentable.shape, croppings_img, paddings_img)
return augmentable
def _crop_and_pad_kpsoi_(kpsoi, croppings_img, paddings_img, keep_size):
# using the trbl function instead of croppings_img has the advantage
# of incorporating prevent_zero_size, dealing with zero-sized input image
# axis and dealing the negative crop amounts
x1, y1, _x2, _y2 = _crop_trbl_to_xyxy(kpsoi.shape, *croppings_img)
crop_left = x1
crop_top = y1
shape_orig = kpsoi.shape
shifted = kpsoi.shift_(
x=-crop_left+paddings_img[3],
y=-crop_top+paddings_img[0])
shifted.shape = _compute_shape_after_crop_and_pad(
shape_orig, croppings_img, paddings_img)
if keep_size:
shifted = shifted.on_(shape_orig)
return shifted
def _compute_shape_after_crop_and_pad(old_shape, croppings, paddings):
x1, y1, x2, y2 = _crop_trbl_to_xyxy(old_shape, *croppings)
new_shape = list(old_shape)
new_shape[0] = y2 - y1 + paddings[0] + paddings[2]
new_shape[1] = x2 - x1 + paddings[1] + paddings[3]
return tuple(new_shape)
def _prevent_zero_size_after_crop_trbl_(height, width, crop_trbl):
crop_top = crop_trbl[0]
crop_right = crop_trbl[1]
crop_bottom = crop_trbl[2]
crop_left = crop_trbl[3]
crop_top, crop_bottom = _prevent_zero_size_after_crop_(height, crop_top,
crop_bottom)
crop_left, crop_right = _prevent_zero_size_after_crop_(width, crop_left,
crop_right)
return (
crop_top, crop_right, crop_bottom, crop_left
)
def _prevent_zero_size_after_crop_(axis_size, crop_start, crop_end):
return map(
int,
_prevent_zero_sizes_after_crops_(
np.array([axis_size], dtype=np.int32),
np.array([crop_start], dtype=np.int32),
np.array([crop_end], dtype=np.int32)
)
)
def _prevent_zero_sizes_after_crops_(axis_sizes, crops_start, crops_end):
remaining_sizes = axis_sizes - (crops_start + crops_end)
mask_bad_sizes = (remaining_sizes < 1)
regains = mask_bad_sizes * (np.abs(remaining_sizes) + 1)
regains_half = regains.astype(np.float32) / 2
regains_start = np.ceil(regains_half).astype(np.int32)
regains_end = np.floor(regains_half).astype(np.int32)
crops_start -= regains_start
crops_end -= regains_end
mask_too_much_start = (crops_start < 0)
crops_end[mask_too_much_start] += crops_start[mask_too_much_start]
crops_start = np.maximum(crops_start, 0)
mask_too_much_end = (crops_end < 0)
crops_start[mask_too_much_end] += crops_end[mask_too_much_end]
crops_end = np.maximum(crops_end, 0)
crops_start = np.maximum(crops_start, 0)
return crops_start, crops_end
def _project_size_changes(trbl, from_shape, to_shape):
if from_shape[0:2] == to_shape[0:2]:
return trbl
height_to = to_shape[0]
width_to = to_shape[1]
height_from = from_shape[0]
width_from = from_shape[1]
top = trbl[0]
right = trbl[1]
bottom = trbl[2]
left = trbl[3]
# Adding/subtracting 1e-4 here helps for the case where a heatmap/segmap
# is exactly half the size of an image and the size change on an axis is
# an odd value. Then the projected value would end up being .5
# and the rounding would always round up to the next integer. If both
# sides then have the same change, they are both rounded up, resulting
# in more change than expected.
# E.g. image height is 8, map height is 4, change is 3 at the top and 3 at
# the bottom. The changes are projected to 4*(3/8) = 1.5 and both rounded
# up to 2.0. Hence, the maps are changed by 4 (100% of the map height,
# vs. 6 for images, which is 75% of the image height).
top = _int_r(height_to * (top/height_from) - 1e-4)
right = _int_r(width_to * (right/width_from) + 1e-4)
bottom = _int_r(height_to * (bottom/height_from) + 1e-4)
left = _int_r(width_to * (left/width_from) - 1e-4)
return top, right, bottom, left
def _int_r(value):
return int(np.round(value))
# TODO somehow integrate this with pad()
@iap._prefetchable_str
def _handle_pad_mode_param(pad_mode):
pad_modes_available = {
"constant", "edge", "linear_ramp", "maximum", "mean", "median",
"minimum", "reflect", "symmetric", "wrap"}
if pad_mode == ia.ALL:
return iap.Choice(list(pad_modes_available))
if ia.is_string(pad_mode):
assert pad_mode in pad_modes_available, (
"Value '%s' is not a valid pad mode. Valid pad modes are: %s." % (
pad_mode, ", ".join(pad_modes_available)))
return iap.Deterministic(pad_mode)
if isinstance(pad_mode, list):
assert all([v in pad_modes_available for v in pad_mode]), (
"At least one in list %s is not a valid pad mode. Valid pad "
"modes are: %s." % (str(pad_mode), ", ".join(pad_modes_available)))
return iap.Choice(pad_mode)
if isinstance(pad_mode, iap.StochasticParameter):
return pad_mode
raise Exception(
"Expected pad_mode to be ia.ALL or string or list of strings or "
"StochasticParameter, got %s." % (type(pad_mode),))
@iap._prefetchable
def _handle_position_parameter(position):
if position == "uniform":
return iap.Uniform(0.0, 1.0), iap.Uniform(0.0, 1.0)
if position == "normal":
return (
iap.Clip(iap.Normal(loc=0.5, scale=0.35 / 2),
minval=0.0, maxval=1.0),
iap.Clip(iap.Normal(loc=0.5, scale=0.35 / 2),
minval=0.0, maxval=1.0)
)
if position == "center":
return iap.Deterministic(0.5), iap.Deterministic(0.5)
if (ia.is_string(position)
and re.match(r"^(left|center|right)-(top|center|bottom)$",
position)):
mapping = {"top": 0.0, "center": 0.5, "bottom": 1.0, "left": 0.0,
"right": 1.0}
return (
iap.Deterministic(mapping[position.split("-")[0]]),
iap.Deterministic(mapping[position.split("-")[1]])
)
if isinstance(position, iap.StochasticParameter):
return position
if isinstance(position, tuple):
assert len(position) == 2, (
"Expected tuple with two entries as position parameter. "
"Got %d entries with types %s.." % (
len(position), str([type(item) for item in position])))
for item in position:
if ia.is_single_number(item) and (item < 0 or item > 1.0):
raise Exception(
"Both position values must be within the value range "
"[0.0, 1.0]. Got type %s with value %.8f." % (
type(item), item,))
position = [iap.Deterministic(item)
if ia.is_single_number(item)
else item for item in position]
only_sparams = all([isinstance(item, iap.StochasticParameter)
for item in position])
assert only_sparams, (
"Expected tuple with two entries that are both either "
"StochasticParameter or float/int. Got types %s." % (
str([type(item) for item in position])
))
return tuple(position)
raise Exception(
"Expected one of the following as position parameter: string "
"'uniform', string 'normal', string 'center', a string matching "
"regex ^(left|center|right)-(top|center|bottom)$, a single "
"StochasticParameter or a tuple of two entries, both being either "
"StochasticParameter or floats or int. Got instead type %s with "
"content '%s'." % (
type(position),
(str(position)
if len(str(position)) < 20
else str(position)[0:20] + "...")
)
)
# TODO this is the same as in imgaug.py, make DRY
# Added in 0.4.0.
def _assert_two_or_three_dims(shape):
if hasattr(shape, "shape"):
shape = shape.shape
assert len(shape) in [2, 3], (
"Expected image with two or three dimensions, but got %d dimensions "
"and shape %s." % (len(shape), shape))
def pad(arr, top=0, right=0, bottom=0, left=0, mode="constant", cval=0):
"""Pad an image-like array on its top/right/bottom/left side.
This function is a wrapper around :func:`numpy.pad`.
Added in 0.4.0. (Previously named ``imgaug.imgaug.pad()``.)
**Supported dtypes**:
* ``uint8``: yes; fully tested (1)
* ``uint16``: yes; fully tested (1)
* ``uint32``: yes; fully tested (2) (3)
* ``uint64``: yes; fully tested (2) (3)
* ``int8``: yes; fully tested (1)
* ``int16``: yes; fully tested (1)
* ``int32``: yes; fully tested (1)
* ``int64``: yes; fully tested (2) (3)
* ``float16``: yes; fully tested (2) (3)
* ``float32``: yes; fully tested (1)
* ``float64``: yes; fully tested (1)
* ``float128``: yes; fully tested (2) (3)
* ``bool``: yes; tested (2) (3)
- (1) Uses ``cv2`` if `mode` is one of: ``"constant"``, ``"edge"``,
``"reflect"``, ``"symmetric"``. Otherwise uses ``numpy``.
- (2) Uses ``numpy``.
- (3) Rejected by ``cv2``.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pad.
top : int, optional
Amount of pixels to add to the top side of the image.
Must be ``0`` or greater.
right : int, optional
Amount of pixels to add to the right side of the image.
Must be ``0`` or greater.
bottom : int, optional
Amount of pixels to add to the bottom side of the image.
Must be ``0`` or greater.
left : int, optional
Amount of pixels to add to the left side of the image.
Must be ``0`` or greater.
mode : str, optional
Padding mode to use. See :func:`numpy.pad` for details.
In case of mode ``constant``, the parameter `cval` will be used as
the ``constant_values`` parameter to :func:`numpy.pad`.
In case of mode ``linear_ramp``, the parameter `cval` will be used as
the ``end_values`` parameter to :func:`numpy.pad`.
cval : number or iterable of number, optional
Value to use for padding if `mode` is ``constant``.
See :func:`numpy.pad` for details. The cval is expected to match the
input array's dtype and value range. If an iterable is used, it is
expected to contain one value per channel. The number of values
and number of channels are expected to match.
Returns
-------
(H',W') ndarray or (H',W',C) ndarray
Padded array with height ``H'=H+top+bottom`` and width
``W'=W+left+right``.
"""
_assert_two_or_three_dims(arr)
assert all([v >= 0 for v in [top, right, bottom, left]]), (
"Expected padding amounts that are >=0, but got %d, %d, %d, %d "
"(top, right, bottom, left)" % (top, right, bottom, left))
is_multi_cval = ia.is_iterable(cval)
if top > 0 or right > 0 or bottom > 0 or left > 0:
min_value, _, max_value = iadt.get_value_range_of_dtype(arr.dtype)
# Without the if here there are crashes for float128, e.g. if
# cval is an int (just using float(cval) seems to not be accurate
# enough).
# Note: If float128 is not available on the system, _FLOAT128_DTYPE is
# None, but 'np.dtype("float64") == None' actually equates to True
# for whatever reason, so we check first if the constant is not None
# (i.e. if float128 exists).
if (iadt._FLOAT128_DTYPE is not None
and arr.dtype == iadt._FLOAT128_DTYPE):
cval = np.float128(cval) # pylint: disable=no-member
if is_multi_cval:
cval = np.clip(cval, min_value, max_value)
else:
cval = max(min(cval, max_value), min_value)
# Note that copyMakeBorder() hangs/runs endlessly if arr has an
# axis of size 0 and mode is "reflect".
# Numpy also complains in these cases if mode is not "constant".
has_zero_sized_axis = any([axis == 0 for axis in arr.shape])
if has_zero_sized_axis:
mode = "constant"
mapping_mode_np_to_cv2 = {
"constant": cv2.BORDER_CONSTANT,
"edge": cv2.BORDER_REPLICATE,
"linear_ramp": None,
"maximum": None,
"mean": None,
"median": None,
"minimum": None,
"reflect": cv2.BORDER_REFLECT_101,
"symmetric": cv2.BORDER_REFLECT,
"wrap": None,
cv2.BORDER_CONSTANT: cv2.BORDER_CONSTANT,
cv2.BORDER_REPLICATE: cv2.BORDER_REPLICATE,
cv2.BORDER_REFLECT_101: cv2.BORDER_REFLECT_101,
cv2.BORDER_REFLECT: cv2.BORDER_REFLECT
}
bad_mode_cv2 = mapping_mode_np_to_cv2.get(mode, None) is None
# these datatypes all simply generate a "TypeError: src data type = X
# is not supported" error
bad_datatype_cv2 = (
arr.dtype in iadt._convert_dtype_strs_to_types(
"uint32 uint64 int64 float16 float128 bool"
)
)
# OpenCV turns the channel axis for arrays with 0 channels to 512
# TODO add direct test for this. indirectly tested via Pad
bad_shape_cv2 = (arr.ndim == 3 and arr.shape[-1] == 0)
if not bad_datatype_cv2 and not bad_mode_cv2 and not bad_shape_cv2:
# convert cval to expected type, as otherwise we get TypeError
# for np inputs
kind = arr.dtype.kind
if is_multi_cval:
cval = [float(cval_c) if kind == "f" else int(cval_c)
for cval_c in cval]
else:
cval = float(cval) if kind == "f" else int(cval)
if arr.ndim == 2 or arr.shape[2] <= 4:
# without this, only the first channel is padded with the cval,
# all following channels with 0
if arr.ndim == 3 and not is_multi_cval:
cval = tuple([cval] * arr.shape[2])
arr = _normalize_cv2_input_arr_(arr)
arr_pad = cv2.copyMakeBorder(
arr,
top=int(top), bottom=int(bottom),
left=int(left), right=int(right),
borderType=mapping_mode_np_to_cv2[mode], value=cval)
if arr.ndim == 3 and arr_pad.ndim == 2:
arr_pad = arr_pad[..., np.newaxis]
else:
result = []
channel_start_idx = 0
cval = cval if is_multi_cval else tuple([cval] * arr.shape[2])
while channel_start_idx < arr.shape[2]:
arr_c = arr[..., channel_start_idx:channel_start_idx+4]
cval_c = cval[channel_start_idx:channel_start_idx+4]
arr_pad_c = cv2.copyMakeBorder(
_normalize_cv2_input_arr_(arr_c),
top=top, bottom=bottom, left=left, right=right,
borderType=mapping_mode_np_to_cv2[mode], value=cval_c)
arr_pad_c = np.atleast_3d(arr_pad_c)
result.append(arr_pad_c)
channel_start_idx += 4
arr_pad = np.concatenate(result, axis=2)
else:
# paddings for 2d case
paddings_np = [(top, bottom), (left, right)]
# add paddings for 3d case
if arr.ndim == 3:
paddings_np.append((0, 0))
if mode == "constant":
if arr.ndim > 2 and is_multi_cval:
arr_pad_chans = [
np.pad(arr[..., c], paddings_np[0:2], mode=mode,
constant_values=cval[c])
for c in np.arange(arr.shape[2])]
arr_pad = np.stack(arr_pad_chans, axis=-1)
else:
arr_pad = np.pad(arr, paddings_np, mode=mode,
constant_values=cval)
elif mode == "linear_ramp":
if arr.ndim > 2 and is_multi_cval:
arr_pad_chans = [
np.pad(arr[..., c], paddings_np[0:2], mode=mode,
end_values=cval[c])
for c in np.arange(arr.shape[2])]
arr_pad = np.stack(arr_pad_chans, axis=-1)
else:
arr_pad = np.pad(arr, paddings_np, mode=mode,
end_values=cval)
else:
arr_pad = np.pad(arr, paddings_np, mode=mode)
return arr_pad
return np.copy(arr)
def pad_to_aspect_ratio(arr, aspect_ratio, mode="constant", cval=0,
return_pad_amounts=False):
"""Pad an image array on its sides so that it matches a target aspect ratio.
See :func:`~imgaug.imgaug.compute_paddings_for_aspect_ratio` for an
explanation of how the required padding amounts are distributed per
image axis.
Added in 0.4.0. (Previously named ``imgaug.imgaug.pad_to_aspect_ratio()``.)
**Supported dtypes**:
See :func:`~imgaug.augmenters.size.pad`.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pad.
aspect_ratio : float
Target aspect ratio, given as width/height. E.g. ``2.0`` denotes the
image having twice as much width as height.
mode : str, optional
Padding mode to use. See :func:`~imgaug.imgaug.pad` for details.
cval : number, optional
Value to use for padding if `mode` is ``constant``.
See :func:`numpy.pad` for details.
return_pad_amounts : bool, optional
If ``False``, then only the padded image will be returned. If
``True``, a ``tuple`` with two entries will be returned, where the
first entry is the padded image and the second entry are the amounts
by which each image side was padded. These amounts are again a
``tuple`` of the form ``(top, right, bottom, left)``, with each value
being an ``int``.
Returns
-------
(H',W') ndarray or (H',W',C) ndarray
Padded image as ``(H',W')`` or ``(H',W',C)`` ndarray, fulfilling the
given `aspect_ratio`.
tuple of int
Amounts by which the image was padded on each side, given as a
``tuple`` ``(top, right, bottom, left)``.
This ``tuple`` is only returned if `return_pad_amounts` was set to
``True``.
"""
pad_top, pad_right, pad_bottom, pad_left = \
compute_paddings_to_reach_aspect_ratio(arr, aspect_ratio)
arr_padded = pad(
arr,
top=pad_top,
right=pad_right,
bottom=pad_bottom,
left=pad_left,
mode=mode,
cval=cval
)
if return_pad_amounts:
return arr_padded, (pad_top, pad_right, pad_bottom, pad_left)
return arr_padded
def pad_to_multiples_of(arr, height_multiple, width_multiple, mode="constant",
cval=0, return_pad_amounts=False):
"""Pad an image array until its side lengths are multiples of given values.
See :func:`~imgaug.imgaug.compute_paddings_for_aspect_ratio` for an
explanation of how the required padding amounts are distributed per
image axis.
Added in 0.4.0. (Previously named ``imgaug.imgaug.pad_to_multiples_of()``.)
**Supported dtypes**:
See :func:`~imgaug.augmenters.size.pad`.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pad.
height_multiple : None or int
The desired multiple of the height. The computed padding amount will
reflect a padding that increases the y axis size until it is a multiple
of this value.
width_multiple : None or int
The desired multiple of the width. The computed padding amount will
reflect a padding that increases the x axis size until it is a multiple
of this value.
mode : str, optional
Padding mode to use. See :func:`~imgaug.imgaug.pad` for details.
cval : number, optional
Value to use for padding if `mode` is ``constant``.
See :func:`numpy.pad` for details.
return_pad_amounts : bool, optional
If ``False``, then only the padded image will be returned. If
``True``, a ``tuple`` with two entries will be returned, where the
first entry is the padded image and the second entry are the amounts
by which each image side was padded. These amounts are again a
``tuple`` of the form ``(top, right, bottom, left)``, with each value
being an integer.
Returns
-------
(H',W') ndarray or (H',W',C) ndarray
Padded image as ``(H',W')`` or ``(H',W',C)`` ndarray.
tuple of int
Amounts by which the image was padded on each side, given as a
``tuple`` ``(top, right, bottom, left)``.
This ``tuple`` is only returned if `return_pad_amounts` was set to
``True``.
"""
pad_top, pad_right, pad_bottom, pad_left = \
compute_paddings_to_reach_multiples_of(
arr, height_multiple, width_multiple)
arr_padded = pad(
arr,
top=pad_top,
right=pad_right,
bottom=pad_bottom,
left=pad_left,
mode=mode,
cval=cval
)
if return_pad_amounts:
return arr_padded, (pad_top, pad_right, pad_bottom, pad_left)
return arr_padded
def compute_paddings_to_reach_aspect_ratio(arr, aspect_ratio):
"""Compute pad amounts required to fulfill an aspect ratio.
"Pad amounts" here denotes the number of pixels that have to be added to
each side to fulfill the desired constraint.
The aspect ratio is given as ``ratio = width / height``.
Depending on which dimension is smaller (height or width), only the
corresponding sides (top/bottom or left/right) will be padded.
The axis-wise padding amounts are always distributed equally over the
sides of the respective axis (i.e. left and right, top and bottom). For
odd pixel amounts, one pixel will be left over after the equal
distribution and could be added to either side of the axis. This function
will always add such a left over pixel to the bottom (y-axis) or
right (x-axis) side.
Added in 0.4.0. (Previously named
``imgaug.imgaug.compute_paddings_to_reach_aspect_ratio()``.)
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray or tuple of int
Image-like array or shape tuple for which to compute pad amounts.
aspect_ratio : float
Target aspect ratio, given as width/height. E.g. ``2.0`` denotes the
image having twice as much width as height.
Returns
-------
tuple of int
Required padding amounts to reach the target aspect ratio, given as a
``tuple`` of the form ``(top, right, bottom, left)``.
"""
_assert_two_or_three_dims(arr)
assert aspect_ratio > 0, (
"Expected to get an aspect ratio >0, got %.4f." % (aspect_ratio,))
pad_top = 0
pad_right = 0
pad_bottom = 0
pad_left = 0
shape = arr.shape if hasattr(arr, "shape") else arr
height, width = shape[0:2]
if height == 0:
height = 1
pad_bottom += 1
if width == 0:
width = 1
pad_right += 1
aspect_ratio_current = width / height
if aspect_ratio_current < aspect_ratio:
# image is more vertical than desired, width needs to be increased
diff = (aspect_ratio * height) - width
pad_right += int(np.ceil(diff / 2))
pad_left += int(np.floor(diff / 2))
elif aspect_ratio_current > aspect_ratio:
# image is more horizontal than desired, height needs to be increased
diff = ((1/aspect_ratio) * width) - height
pad_top += int(np.floor(diff / 2))
pad_bottom += int(np.ceil(diff / 2))
return pad_top, pad_right, pad_bottom, pad_left
def compute_croppings_to_reach_aspect_ratio(arr, aspect_ratio):
"""Compute crop amounts required to fulfill an aspect ratio.
"Crop amounts" here denotes the number of pixels that have to be removed
from each side to fulfill the desired constraint.
The aspect ratio is given as ``ratio = width / height``.
Depending on which dimension is smaller (height or width), only the
corresponding sides (top/bottom or left/right) will be cropped.
The axis-wise padding amounts are always distributed equally over the
sides of the respective axis (i.e. left and right, top and bottom). For
odd pixel amounts, one pixel will be left over after the equal
distribution and could be added to either side of the axis. This function
will always add such a left over pixel to the bottom (y-axis) or
right (x-axis) side.
If an aspect ratio cannot be reached exactly, this function will return
rather one pixel too few than one pixel too many.
Added in 0.4.0.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray or tuple of int
Image-like array or shape tuple for which to compute crop amounts.
aspect_ratio : float
Target aspect ratio, given as width/height. E.g. ``2.0`` denotes the
image having twice as much width as height.
Returns
-------
tuple of int
Required cropping amounts to reach the target aspect ratio, given as a
``tuple`` of the form ``(top, right, bottom, left)``.
"""
_assert_two_or_three_dims(arr)
assert aspect_ratio > 0, (
"Expected to get an aspect ratio >0, got %.4f." % (aspect_ratio,))
shape = arr.shape if hasattr(arr, "shape") else arr
assert shape[0] > 0, (
"Expected to get an array with height >0, got shape %s." % (shape,))
height, width = shape[0:2]
aspect_ratio_current = width / height
top = 0
right = 0
bottom = 0
left = 0
if aspect_ratio_current < aspect_ratio:
# image is more vertical than desired, height needs to be reduced
# c = H - W/r
crop_amount = height - (width / aspect_ratio)
crop_amount = min(crop_amount, height - 1)
top = int(np.floor(crop_amount / 2))
bottom = int(np.ceil(crop_amount / 2))
elif aspect_ratio_current > aspect_ratio:
# image is more horizontal than desired, width needs to be reduced
# c = W - Hr
crop_amount = width - height * aspect_ratio
crop_amount = min(crop_amount, width - 1)
left = int(np.floor(crop_amount / 2))
right = int(np.ceil(crop_amount / 2))
return top, right, bottom, left
def compute_paddings_to_reach_multiples_of(arr, height_multiple,
width_multiple):
"""Compute pad amounts until img height/width are multiples of given values.
See :func:`~imgaug.imgaug.compute_paddings_for_aspect_ratio` for an
explanation of how the required padding amounts are distributed per
image axis.
Added in 0.4.0. (Previously named
``imgaug.imgaug.compute_paddings_to_reach_multiples_of()``.)
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray or tuple of int
Image-like array or shape tuple for which to compute pad amounts.
height_multiple : None or int
The desired multiple of the height. The computed padding amount will
reflect a padding that increases the y axis size until it is a multiple
of this value.
width_multiple : None or int
The desired multiple of the width. The computed padding amount will
reflect a padding that increases the x axis size until it is a multiple
of this value.
Returns
-------
tuple of int
Required padding amounts to reach multiples of the provided values,
given as a ``tuple`` of the form ``(top, right, bottom, left)``.
"""
def _compute_axis_value(axis_size, multiple):
if multiple is None:
return 0, 0
if axis_size == 0:
to_pad = multiple
elif axis_size % multiple == 0:
to_pad = 0
else:
to_pad = multiple - (axis_size % multiple)
return int(np.floor(to_pad/2)), int(np.ceil(to_pad/2))
_assert_two_or_three_dims(arr)
if height_multiple is not None:
assert height_multiple > 0, (
"Can only pad to multiples of 1 or larger, got %d." % (
height_multiple,))
if width_multiple is not None:
assert width_multiple > 0, (
"Can only pad to multiples of 1 or larger, got %d." % (
width_multiple,))
shape = arr.shape if hasattr(arr, "shape") else arr
height, width = shape[0:2]
top, bottom = _compute_axis_value(height, height_multiple)
left, right = _compute_axis_value(width, width_multiple)
return top, right, bottom, left
def compute_croppings_to_reach_multiples_of(arr, height_multiple,
width_multiple):
"""Compute croppings to reach multiples of given heights/widths.
See :func:`~imgaug.imgaug.compute_paddings_for_aspect_ratio` for an
explanation of how the required cropping amounts are distributed per
image axis.
Added in 0.4.0.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray or tuple of int
Image-like array or shape tuple for which to compute crop amounts.
height_multiple : None or int
The desired multiple of the height. The computed croppings will
reflect a crop operation that decreases the y axis size until it is
a multiple of this value.
width_multiple : None or int
The desired multiple of the width. The computed croppings amount will
reflect a crop operation that decreases the x axis size until it is
a multiple of this value.
Returns
-------
tuple of int
Required cropping amounts to reach multiples of the provided values,
given as a ``tuple`` of the form ``(top, right, bottom, left)``.
"""
def _compute_axis_value(axis_size, multiple):
if multiple is None:
return 0, 0
if axis_size == 0:
to_crop = 0
elif axis_size % multiple == 0:
to_crop = 0
else:
to_crop = axis_size % multiple
return int(np.floor(to_crop/2)), int(np.ceil(to_crop/2))
_assert_two_or_three_dims(arr)
if height_multiple is not None:
assert height_multiple > 0, (
"Can only crop to multiples of 1 or larger, got %d." % (
height_multiple,))
if width_multiple is not None:
assert width_multiple > 0, (
"Can only crop to multiples of 1 or larger, got %d." % (
width_multiple,))
shape = arr.shape if hasattr(arr, "shape") else arr
height, width = shape[0:2]
top, bottom = _compute_axis_value(height, height_multiple)
left, right = _compute_axis_value(width, width_multiple)
return top, right, bottom, left
def compute_paddings_to_reach_powers_of(arr, height_base, width_base,
allow_zero_exponent=False):
"""Compute paddings to reach powers of given base values.
For given axis size ``S``, padded size ``S'`` (``S' >= S``) and base ``B``
this function computes paddings that fulfill ``S' = B^E``, where ``E``
is any exponent from the discrete interval ``[0 .. inf)``.
See :func:`~imgaug.imgaug.compute_paddings_for_aspect_ratio` for an
explanation of how the required padding amounts are distributed per
image axis.
Added in 0.4.0. (Previously named
``imgaug.imgaug.compute_paddings_to_reach_exponents_of()``.)
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray or tuple of int
Image-like array or shape tuple for which to compute pad amounts.
height_base : None or int
The desired base of the height.
width_base : None or int
The desired base of the width.
allow_zero_exponent : bool, optional
Whether ``E=0`` in ``S'=B^E`` is a valid value. If ``True``, axes
with size ``0`` or ``1`` will be padded up to size ``B^0=1`` and
axes with size ``1 < S <= B`` will be padded up to ``B^1=B``.
If ``False``, the minimum output axis size is always at least ``B``.
Returns
-------
tuple of int
Required padding amounts to fulfill ``S' = B^E`` given as a
``tuple`` of the form ``(top, right, bottom, left)``.
"""
def _compute_axis_value(axis_size, base):
if base is None:
return 0, 0
if axis_size == 0:
to_pad = 1 if allow_zero_exponent else base
elif axis_size <= base:
to_pad = base - axis_size
else:
# log_{base}(axis_size) in numpy
exponent = np.log(axis_size) / np.log(base)
to_pad = (base ** int(np.ceil(exponent))) - axis_size
return int(np.floor(to_pad/2)), int(np.ceil(to_pad/2))
_assert_two_or_three_dims(arr)
if height_base is not None:
assert height_base > 1, (
"Can only pad to base larger than 1, got %d." % (height_base,))
if width_base is not None:
assert width_base > 1, (
"Can only pad to base larger than 1, got %d." % (width_base,))
shape = arr.shape if hasattr(arr, "shape") else arr
height, width = shape[0:2]
top, bottom = _compute_axis_value(height, height_base)
left, right = _compute_axis_value(width, width_base)
return top, right, bottom, left
def compute_croppings_to_reach_powers_of(arr, height_base, width_base,
allow_zero_exponent=False):
"""Compute croppings to reach powers of given base values.
For given axis size ``S``, cropped size ``S'`` (``S' <= S``) and base ``B``
this function computes croppings that fulfill ``S' = B^E``, where ``E``
is any exponent from the discrete interval ``[0 .. inf)``.
See :func:`~imgaug.imgaug.compute_paddings_for_aspect_ratio` for an
explanation of how the required cropping amounts are distributed per
image axis.
.. note::
For axes where ``S == 0``, this function alwayws returns zeros as
croppings.
For axes where ``1 <= S < B`` see parameter `allow_zero_exponent`.
Added in 0.4.0.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray or tuple of int
Image-like array or shape tuple for which to compute crop amounts.
height_base : None or int
The desired base of the height.
width_base : None or int
The desired base of the width.
allow_zero_exponent : bool
Whether ``E=0`` in ``S'=B^E`` is a valid value. If ``True``, axes
with size ``1 <= S < B`` will be cropped to size ``B^0=1``.
If ``False``, axes with sizes ``S < B`` will not be changed.
Returns
-------
tuple of int
Required cropping amounts to fulfill ``S' = B^E`` given as a
``tuple`` of the form ``(top, right, bottom, left)``.
"""
def _compute_axis_value(axis_size, base):
if base is None:
return 0, 0
if axis_size == 0:
to_crop = 0
elif axis_size < base:
# crop down to B^0 = 1
to_crop = axis_size - 1 if allow_zero_exponent else 0
else:
# log_{base}(axis_size) in numpy
exponent = np.log(axis_size) / np.log(base)
to_crop = axis_size - (base ** int(exponent))
return int(np.floor(to_crop/2)), int(np.ceil(to_crop/2))
_assert_two_or_three_dims(arr)
if height_base is not None:
assert height_base > 1, (
"Can only crop to base larger than 1, got %d." % (height_base,))
if width_base is not None:
assert width_base > 1, (
"Can only crop to base larger than 1, got %d." % (width_base,))
shape = arr.shape if hasattr(arr, "shape") else arr
height, width = shape[0:2]
top, bottom = _compute_axis_value(height, height_base)
left, right = _compute_axis_value(width, width_base)
return top, right, bottom, left
@ia.deprecated(alt_func="Resize",
comment="Resize has the exactly same interface as Scale.")
def Scale(*args, **kwargs):
"""Augmenter that resizes images to specified heights and widths."""
# pylint: disable=invalid-name
return Resize(*args, **kwargs)
class Resize(meta.Augmenter):
"""Augmenter that resizes images to specified heights and widths.
**Supported dtypes**:
See :func:`~imgaug.imgaug.imresize_many_images`.
Parameters
----------
size : 'keep' or int or float or tuple of int or tuple of float or list of int or list of float or imgaug.parameters.StochasticParameter or dict
The new size of the images.
* If this has the string value ``keep``, the original height and
width values will be kept (image is not resized).
* If this is an ``int``, this value will always be used as the new
height and width of the images.
* If this is a ``float`` ``v``, then per image the image's height
``H`` and width ``W`` will be changed to ``H*v`` and ``W*v``.
* If this is a ``tuple``, it is expected to have two entries
``(a, b)``. If at least one of these are ``float`` s, a value
will be sampled from range ``[a, b]`` and used as the ``float``
value to resize the image (see above). If both are ``int`` s, a
value will be sampled from the discrete range ``[a..b]`` and
used as the integer value to resize the image (see above).
* If this is a ``list``, a random value from the ``list`` will be
picked to resize the image. All values in the ``list`` must be
``int`` s or ``float`` s (no mixture is possible).
* If this is a ``StochasticParameter``, then this parameter will
first be queried once per image. The resulting value will be used
for both height and width.
* If this is a ``dict``, it may contain the keys ``height`` and
``width`` or the keys ``shorter-side`` and ``longer-side``. Each
key may have the same datatypes as above and describes the
scaling on x and y-axis or the shorter and longer axis,
respectively. Both axis are sampled independently. Additionally,
one of the keys may have the value ``keep-aspect-ratio``, which
means that the respective side of the image will be resized so
that the original aspect ratio is kept. This is useful when only
resizing one image size by a pixel value (e.g. resize images to
a height of ``64`` pixels and resize the width so that the
overall aspect ratio is maintained).
interpolation : imgaug.ALL or int or str or list of int or list of str or imgaug.parameters.StochasticParameter, optional
Interpolation to use.
* If ``imgaug.ALL``, then a random interpolation from ``nearest``,
``linear``, ``area`` or ``cubic`` will be picked (per image).
* If ``int``, then this interpolation will always be used.
Expected to be any of the following:
``cv2.INTER_NEAREST``, ``cv2.INTER_LINEAR``, ``cv2.INTER_AREA``,
``cv2.INTER_CUBIC``
* If string, then this interpolation will always be used.
Expected to be any of the following:
``nearest``, ``linear``, ``area``, ``cubic``
* If ``list`` of ``int`` / ``str``, then a random one of the values
will be picked per image as the interpolation.
* If a ``StochasticParameter``, then this parameter will be
queried per image and is expected to return an ``int`` or
``str``.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Resize(32)
Resize all images to ``32x32`` pixels.
>>> aug = iaa.Resize(0.5)
Resize all images to ``50`` percent of their original size.
>>> aug = iaa.Resize((16, 22))
Resize all images to a random height and width within the discrete
interval ``[16..22]`` (uniformly sampled per image).
>>> aug = iaa.Resize((0.5, 0.75))
Resize all any input image so that its height (``H``) and width (``W``)
become ``H*v`` and ``W*v``, where ``v`` is uniformly sampled from the
interval ``[0.5, 0.75]``.
>>> aug = iaa.Resize([16, 32, 64])
Resize all images either to ``16x16``, ``32x32`` or ``64x64`` pixels.
>>> aug = iaa.Resize({"height": 32})
Resize all images to a height of ``32`` pixels and keeps the original
width.
>>> aug = iaa.Resize({"height": 32, "width": 48})
Resize all images to a height of ``32`` pixels and a width of ``48``.
>>> aug = iaa.Resize({"height": 32, "width": "keep-aspect-ratio"})
Resize all images to a height of ``32`` pixels and resizes the
x-axis (width) so that the aspect ratio is maintained.
>>> aug = iaa.Resize(
>>> {"shorter-side": 224, "longer-side": "keep-aspect-ratio"})
Resize all images to a height/width of ``224`` pixels, depending on which
axis is shorter and resize the other axis so that the aspect ratio is
maintained.
>>> aug = iaa.Resize({"height": (0.5, 0.75), "width": [16, 32, 64]})
Resize all images to a height of ``H*v``, where ``H`` is the original
height and ``v`` is a random value sampled from the interval
``[0.5, 0.75]``. The width/x-axis of each image is resized to either
``16`` or ``32`` or ``64`` pixels.
>>> aug = iaa.Resize(32, interpolation=["linear", "cubic"])
Resize all images to ``32x32`` pixels. Randomly use either ``linear``
or ``cubic`` interpolation.
"""
def __init__(self, size, interpolation="cubic",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Resize, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.size, self.size_order = self._handle_size_arg(size, False)
self.interpolation = self._handle_interpolation_arg(interpolation)
@classmethod
@iap._prefetchable_str
def _handle_size_arg(cls, size, subcall):
def _dict_to_size_tuple(val1, val2):
kaa = "keep-aspect-ratio"
not_both_kaa = (val1 != kaa or val2 != kaa)
assert not_both_kaa, (
"Expected at least one value to not be \"keep-aspect-ratio\", "
"but got it two times.")
size_tuple = []
for k in [val1, val2]:
if k in ["keep-aspect-ratio", "keep"]:
entry = iap.Deterministic(k)
else:
entry = cls._handle_size_arg(k, True)
size_tuple.append(entry)
return tuple(size_tuple)
def _contains_any_key(dict_, keys):
return any([key in dict_ for key in keys])
# HW = height, width
# SL = shorter, longer
size_order = "HW"
if size == "keep":
result = iap.Deterministic("keep")
elif ia.is_single_number(size):
assert size > 0, "Expected only values > 0, got %s" % (size,)
result = iap.Deterministic(size)
elif not subcall and isinstance(size, dict):
if len(size.keys()) == 0:
result = iap.Deterministic("keep")
elif _contains_any_key(size, ["height", "width"]):
height = size.get("height", "keep")
width = size.get("width", "keep")
result = _dict_to_size_tuple(height, width)
elif _contains_any_key(size, ["shorter-side", "longer-side"]):
shorter = size.get("shorter-side", "keep")
longer = size.get("longer-side", "keep")
result = _dict_to_size_tuple(shorter, longer)
size_order = "SL"
else:
raise ValueError(
"Expected dictionary containing no keys, "
"the keys \"height\" and/or \"width\", "
"or the keys \"shorter-side\" and/or \"longer-side\". "
"Got keys: %s." % (str(size.keys()),))
elif isinstance(size, tuple):
assert len(size) == 2, (
"Expected size tuple to contain exactly 2 values, "
"got %d." % (len(size),))
assert size[0] > 0 and size[1] > 0, (
"Expected size tuple to only contain values >0, "
"got %d and %d." % (size[0], size[1]))
if ia.is_single_float(size[0]) or ia.is_single_float(size[1]):
result = iap.Uniform(size[0], size[1])
else:
result = iap.DiscreteUniform(size[0], size[1])
elif isinstance(size, list):
if len(size) == 0:
result = iap.Deterministic("keep")
else:
all_int = all([ia.is_single_integer(v) for v in size])
all_float = all([ia.is_single_float(v) for v in size])
assert all_int or all_float, (
"Expected to get only integers or floats.")
assert all([v > 0 for v in size]), (
"Expected all values to be >0.")
result = iap.Choice(size)
elif isinstance(size, iap.StochasticParameter):
result = size
else:
raise ValueError(
"Expected number, tuple of two numbers, list of numbers, "
"dictionary of form "
"{'height': number/tuple/list/'keep-aspect-ratio'/'keep', "
"'width': }, dictionary of form "
"{'shorter-side': number/tuple/list/'keep-aspect-ratio'/"
"'keep', 'longer-side': } "
"or StochasticParameter, got %s." % (type(size),)
)
if subcall:
return result
return result, size_order
@classmethod
def _handle_interpolation_arg(cls, interpolation):
if interpolation == ia.ALL:
interpolation = iap.Choice(
["nearest", "linear", "area", "cubic"])
elif ia.is_single_integer(interpolation):
interpolation = iap.Deterministic(interpolation)
elif ia.is_string(interpolation):
interpolation = iap.Deterministic(interpolation)
elif ia.is_iterable(interpolation):
interpolation = iap.Choice(interpolation)
elif isinstance(interpolation, iap.StochasticParameter):
pass
else:
raise Exception(
"Expected int or string or iterable or StochasticParameter, "
"got %s." % (type(interpolation),))
return interpolation
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
nb_rows = batch.nb_rows
samples = self._draw_samples(nb_rows, random_state)
if batch.images is not None:
batch.images = self._augment_images_by_samples(batch.images,
samples)
if batch.heatmaps is not None:
# TODO this uses the same interpolation as for images for heatmaps
# while other augmenters resort to cubic
batch.heatmaps = self._augment_maps_by_samples(
batch.heatmaps, "arr_0to1", samples)
if batch.segmentation_maps is not None:
batch.segmentation_maps = self._augment_maps_by_samples(
batch.segmentation_maps, "arr",
(samples[0], samples[1], [None] * nb_rows))
for augm_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
samples=samples)
cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
setattr(batch, augm_name, cbaois)
return batch
# Added in 0.4.0.
def _augment_images_by_samples(self, images, samples):
input_was_array = False
input_dtype = None
if ia.is_np_array(images):
input_was_array = True
input_dtype = images.dtype
samples_a, samples_b, samples_ip = samples
result = []
for i, image in enumerate(images):
h, w = self._compute_height_width(image.shape, samples_a[i],
samples_b[i], self.size_order)
image_rs = ia.imresize_single_image(image, (h, w),
interpolation=samples_ip[i])
result.append(image_rs)
if input_was_array:
all_same_size = (len({image.shape for image in result}) == 1)
if all_same_size:
result = np.array(result, dtype=input_dtype)
return result
# Added in 0.4.0.
def _augment_maps_by_samples(self, augmentables, arr_attr_name, samples):
result = []
samples_h, samples_w, samples_ip = samples
for i, augmentable in enumerate(augmentables):
arr = getattr(augmentable, arr_attr_name)
arr_shape = arr.shape
img_shape = augmentable.shape
h_img, w_img = self._compute_height_width(
img_shape, samples_h[i], samples_w[i], self.size_order)
h = int(np.round(h_img * (arr_shape[0] / img_shape[0])))
w = int(np.round(w_img * (arr_shape[1] / img_shape[1])))
h = max(h, 1)
w = max(w, 1)
if samples_ip[0] is not None:
# TODO change this for heatmaps to always have cubic or
# automatic interpolation?
augmentable_resize = augmentable.resize(
(h, w), interpolation=samples_ip[i])
else:
augmentable_resize = augmentable.resize((h, w))
augmentable_resize.shape = (h_img, w_img) + img_shape[2:]
result.append(augmentable_resize)
return result
# Added in 0.4.0.
def _augment_keypoints_by_samples(self, kpsois, samples):
result = []
samples_a, samples_b, _samples_ip = samples
for i, kpsoi in enumerate(kpsois):
h, w = self._compute_height_width(
kpsoi.shape, samples_a[i], samples_b[i], self.size_order)
new_shape = (h, w) + kpsoi.shape[2:]
keypoints_on_image_rs = kpsoi.on_(new_shape)
result.append(keypoints_on_image_rs)
return result
def _draw_samples(self, nb_images, random_state):
rngs = random_state.duplicate(3)
if isinstance(self.size, tuple):
samples_h = self.size[0].draw_samples(nb_images,
random_state=rngs[0])
samples_w = self.size[1].draw_samples(nb_images,
random_state=rngs[1])
else:
samples_h = self.size.draw_samples(nb_images, random_state=rngs[0])
samples_w = samples_h
samples_ip = self.interpolation.draw_samples(nb_images,
random_state=rngs[2])
return samples_h, samples_w, samples_ip
@classmethod
def _compute_height_width(cls, image_shape, sample_a, sample_b, size_order):
imh, imw = image_shape[0:2]
if size_order == 'SL':
# size order: short, long
if imh < imw:
h, w = sample_a, sample_b
else:
w, h = sample_a, sample_b
else:
# size order: height, width
h, w = sample_a, sample_b
if ia.is_single_float(h):
assert h > 0, "Expected 'h' to be >0, got %.4f" % (h,)
h = int(np.round(imh * h))
h = h if h > 0 else 1
elif h == "keep":
h = imh
if ia.is_single_float(w):
assert w > 0, "Expected 'w' to be >0, got %.4f" % (w,)
w = int(np.round(imw * w))
w = w if w > 0 else 1
elif w == "keep":
w = imw
# at least the checks for keep-aspect-ratio must come after
# the float checks, as they are dependent on the results
# this is also why these are not written as elifs
if h == "keep-aspect-ratio":
h_per_w_orig = imh / imw
h = int(np.round(w * h_per_w_orig))
if w == "keep-aspect-ratio":
w_per_h_orig = imw / imh
w = int(np.round(h * w_per_h_orig))
return h, w
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.size, self.interpolation, self.size_order]
class _CropAndPadSamplingResult(object):
def __init__(self, crop_top, crop_right, crop_bottom, crop_left,
pad_top, pad_right, pad_bottom, pad_left, pad_mode, pad_cval):
self.crop_top = crop_top
self.crop_right = crop_right
self.crop_bottom = crop_bottom
self.crop_left = crop_left
self.pad_top = pad_top
self.pad_right = pad_right
self.pad_bottom = pad_bottom
self.pad_left = pad_left
self.pad_mode = pad_mode
self.pad_cval = pad_cval
def croppings(self, i):
"""Get absolute pixel amounts of croppings as a TRBL tuple."""
return (
self.crop_top[i],
self.crop_right[i],
self.crop_bottom[i],
self.crop_left[i]
)
def paddings(self, i):
"""Get absolute pixel amounts of paddings as a TRBL tuple."""
return (
self.pad_top[i],
self.pad_right[i],
self.pad_bottom[i],
self.pad_left[i]
)
class CropAndPad(meta.Augmenter):
"""Crop/pad images by pixel amounts or fractions of image sizes.
Cropping removes pixels at the sides (i.e. extracts a subimage from
a given full image). Padding adds pixels to the sides (e.g. black pixels).
This augmenter will never crop images below a height or width of ``1``.
.. note::
This augmenter automatically resizes images back to their original size
after it has augmented them. To deactivate this, add the
parameter ``keep_size=False``.
**Supported dtypes**:
if (keep_size=False):
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
if (keep_size=True):
minimum of (
``imgaug.augmenters.size.CropAndPad(keep_size=False)``,
:func:`~imgaug.imgaug.imresize_many_images`
)
Parameters
----------
px : None or int or imgaug.parameters.StochasticParameter or tuple, optional
The number of pixels to crop (negative values) or pad (positive values)
on each side of the image. Either this or the parameter `percent` may
be set, not both at the same time.
* If ``None``, then pixel-based cropping/padding will not be used.
* If ``int``, then that exact number of pixels will always be
cropped/padded.
* If ``StochasticParameter``, then that parameter will be used for
each image. Four samples will be drawn per image (top, right,
bottom, left), unless `sample_independently` is set to ``False``,
as then only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of two ``int`` s with values ``a`` and ``b``,
then each side will be cropped/padded by a random amount sampled
uniformly per image and side from the inteval ``[a, b]``. If
however `sample_independently` is set to ``False``, only one
value will be sampled per image and used for all sides.
* If a ``tuple`` of four entries, then the entries represent top,
right, bottom, left. Each entry may be a single ``int`` (always
crop/pad by exactly that value), a ``tuple`` of two ``int`` s
``a`` and ``b`` (crop/pad by an amount within ``[a, b]``), a
``list`` of ``int`` s (crop/pad by a random value that is
contained in the ``list``) or a ``StochasticParameter`` (sample
the amount to crop/pad from that parameter).
percent : None or number or imgaug.parameters.StochasticParameter or tuple, optional
The number of pixels to crop (negative values) or pad (positive values)
on each side of the image given as a *fraction* of the image
height/width. E.g. if this is set to ``-0.1``, the augmenter will
always crop away ``10%`` of the image's height at both the top and the
bottom (both ``10%`` each), as well as ``10%`` of the width at the
right and left.
Expected value range is ``(-1.0, inf)``.
Either this or the parameter `px` may be set, not both
at the same time.
* If ``None``, then fraction-based cropping/padding will not be
used.
* If ``number``, then that fraction will always be cropped/padded.
* If ``StochasticParameter``, then that parameter will be used for
each image. Four samples will be drawn per image (top, right,
bottom, left). If however `sample_independently` is set to
``False``, only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of two ``float`` s with values ``a`` and ``b``,
then each side will be cropped/padded by a random fraction
sampled uniformly per image and side from the interval
``[a, b]``. If however `sample_independently` is set to
``False``, only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of four entries, then the entries represent top,
right, bottom, left. Each entry may be a single ``float``
(always crop/pad by exactly that percent value), a ``tuple`` of
two ``float`` s ``a`` and ``b`` (crop/pad by a fraction from
``[a, b]``), a ``list`` of ``float`` s (crop/pad by a random
value that is contained in the list) or a ``StochasticParameter``
(sample the percentage to crop/pad from that parameter).
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
Padding mode to use. The available modes match the numpy padding modes,
i.e. ``constant``, ``edge``, ``linear_ramp``, ``maximum``, ``median``,
``minimum``, ``reflect``, ``symmetric``, ``wrap``. The modes
``constant`` and ``linear_ramp`` use extra values, which are provided
by ``pad_cval`` when necessary. See :func:`~imgaug.imgaug.pad` for
more details.
* If ``imgaug.ALL``, then a random mode from all available modes
will be sampled per image.
* If a ``str``, it will be used as the pad mode for all images.
* If a ``list`` of ``str``, a random one of these will be sampled
per image and used as the mode.
* If ``StochasticParameter``, a random mode will be sampled from
this parameter per image.
pad_cval : number or tuple of number list of number or imgaug.parameters.StochasticParameter, optional
The constant value to use if the pad mode is ``constant`` or the end
value to use if the mode is ``linear_ramp``.
See :func:`~imgaug.imgaug.pad` for more details.
* If ``number``, then that value will be used.
* If a ``tuple`` of two ``number`` s and at least one of them is
a ``float``, then a random number will be uniformly sampled per
image from the continuous interval ``[a, b]`` and used as the
value. If both ``number`` s are ``int`` s, the interval is
discrete.
* If a ``list`` of ``number``, then a random value will be chosen
from the elements of the ``list`` and used as the value.
* If ``StochasticParameter``, a random value will be sampled from
that parameter per image.
keep_size : bool, optional
After cropping and padding, the result image will usually have a
different height/width compared to the original input image. If this
parameter is set to ``True``, then the cropped/padded image will be
resized to the input image's size, i.e. the augmenter's output shape
is always identical to the input shape.
sample_independently : bool, optional
If ``False`` *and* the values for `px`/`percent` result in exactly
*one* probability distribution for all image sides, only one single
value will be sampled from that probability distribution and used for
all sides. I.e. the crop/pad amount then is the same for all sides.
If ``True``, four values will be sampled independently, one per side.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CropAndPad(px=(-10, 0))
Crop each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[-10..0]``.
>>> aug = iaa.CropAndPad(px=(0, 10))
Pad each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[0..10]``. The padding happens by
zero-padding, i.e. it adds black pixels (default setting).
>>> aug = iaa.CropAndPad(px=(0, 10), pad_mode="edge")
Pad each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[0..10]``. The padding uses the
``edge`` mode from numpy's pad function, i.e. the pixel colors around
the image sides are repeated.
>>> aug = iaa.CropAndPad(px=(0, 10), pad_mode=["constant", "edge"])
Similar to the previous example, but uses zero-padding (``constant``) for
half of the images and ``edge`` padding for the other half.
>>> aug = iaa.CropAndPad(px=(0, 10), pad_mode=ia.ALL, pad_cval=(0, 255))
Similar to the previous example, but uses any available padding mode.
In case the padding mode ends up being ``constant`` or ``linear_ramp``,
and random intensity is uniformly sampled (once per image) from the
discrete interval ``[0..255]`` and used as the intensity of the new
pixels.
>>> aug = iaa.CropAndPad(px=(0, 10), sample_independently=False)
Pad each side by a random pixel value sampled uniformly once per image
from the discrete interval ``[0..10]``. Each sampled value is used
for *all* sides of the corresponding image.
>>> aug = iaa.CropAndPad(px=(0, 10), keep_size=False)
Pad each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[0..10]``. Afterwards, do **not**
resize the padded image back to the input image's size. This will increase
the image's height and width by a maximum of ``20`` pixels.
>>> aug = iaa.CropAndPad(px=((0, 10), (0, 5), (0, 10), (0, 5)))
Pad the top and bottom by a random pixel value sampled uniformly from the
discrete interval ``[0..10]``. Pad the left and right analogously by
a random value sampled from ``[0..5]``. Each value is always sampled
independently.
>>> aug = iaa.CropAndPad(percent=(0, 0.1))
Pad each side by a random fraction sampled uniformly from the continuous
interval ``[0.0, 0.10]``. The fraction is sampled once per image and
side. E.g. a sampled fraction of ``0.1`` for the top side would pad by
``0.1*H``, where ``H`` is the height of the input image.
>>> aug = iaa.CropAndPad(
>>> percent=([0.05, 0.1], [0.05, 0.1], [0.05, 0.1], [0.05, 0.1]))
Pads each side by either ``5%`` or ``10%``. The values are sampled
once per side and image.
>>> aug = iaa.CropAndPad(px=(-10, 10))
Sample uniformly per image and side a value ``v`` from the discrete range
``[-10..10]``. Then either crop (negative sample) or pad (positive sample)
the side by ``v`` pixels.
"""
def __init__(self, px=None, percent=None, pad_mode="constant", pad_cval=0,
keep_size=True, sample_independently=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
# pylint: disable=invalid-name
super(CropAndPad, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
if px is None and percent is None:
percent = (-0.1, 0.1)
self.mode, self.all_sides, self.top, self.right, self.bottom, \
self.left = self._handle_px_and_percent_args(px, percent)
self.pad_mode = _handle_pad_mode_param(pad_mode)
# TODO enable ALL here, like in e.g. Affine
self.pad_cval = iap.handle_discrete_param(
pad_cval, "pad_cval", value_range=None, tuple_to_uniform=True,
list_to_choice=True, allow_floats=True)
self.keep_size = keep_size
self.sample_independently = sample_independently
# set these to None to use the same values as sampled for the
# images (not tested)
self._pad_mode_heatmaps = "constant"
self._pad_mode_segmentation_maps = "constant"
self._pad_cval_heatmaps = 0.0
self._pad_cval_segmentation_maps = 0
@classmethod
@iap._prefetchable
def _handle_px_and_percent_args(cls, px, percent):
# pylint: disable=invalid-name
all_sides = None
top, right, bottom, left = None, None, None, None
if px is None and percent is None:
mode = "noop"
elif px is not None and percent is not None:
raise Exception("Can only pad by pixels or percent, not both.")
elif px is not None:
mode = "px"
all_sides, top, right, bottom, left = cls._handle_px_arg(px)
else: # = elif percent is not None:
mode = "percent"
all_sides, top, right, bottom, left = cls._handle_percent_arg(
percent)
return mode, all_sides, top, right, bottom, left
@classmethod
def _handle_px_arg(cls, px):
# pylint: disable=invalid-name
all_sides = None
top, right, bottom, left = None, None, None, None
if ia.is_single_integer(px):
all_sides = iap.Deterministic(px)
elif isinstance(px, tuple):
assert len(px) in [2, 4], (
"Expected 'px' given as a tuple to contain 2 or 4 "
"entries, got %d." % (len(px),))
def handle_param(p):
if ia.is_single_integer(p):
return iap.Deterministic(p)
if isinstance(p, tuple):
assert len(p) == 2, (
"Expected tuple of 2 values, got %d." % (len(p)))
only_ints = (
ia.is_single_integer(p[0])
and ia.is_single_integer(p[1]))
assert only_ints, (
"Expected tuple of integers, got %s and %s." % (
type(p[0]), type(p[1])))
return iap.DiscreteUniform(p[0], p[1])
if isinstance(p, list):
assert len(p) > 0, (
"Expected non-empty list, but got empty one.")
assert all([ia.is_single_integer(val) for val in p]), (
"Expected list of ints, got types %s." % (
", ".join([str(type(v)) for v in p])))
return iap.Choice(p)
if isinstance(p, iap.StochasticParameter):
return p
raise Exception(
"Expected int, tuple of two ints, list of ints or "
"StochasticParameter, got type %s." % (type(p),))
if len(px) == 2:
all_sides = handle_param(px)
else: # len == 4
top = handle_param(px[0])
right = handle_param(px[1])
bottom = handle_param(px[2])
left = handle_param(px[3])
elif isinstance(px, iap.StochasticParameter):
top = right = bottom = left = px
else:
raise Exception(
"Expected int, tuple of 4 "
"ints/tuples/lists/StochasticParameters or "
"StochasticParameter, got type %s." % (type(px),))
return all_sides, top, right, bottom, left
@classmethod
def _handle_percent_arg(cls, percent):
all_sides = None
top, right, bottom, left = None, None, None, None
if ia.is_single_number(percent):
assert percent > -1.0, (
"Expected 'percent' to be >-1.0, got %.4f." % (percent,))
all_sides = iap.Deterministic(percent)
elif isinstance(percent, tuple):
assert len(percent) in [2, 4], (
"Expected 'percent' given as a tuple to contain 2 or 4 "
"entries, got %d." % (len(percent),))
def handle_param(p):
if ia.is_single_number(p):
return iap.Deterministic(p)
if isinstance(p, tuple):
assert len(p) == 2, (
"Expected tuple of 2 values, got %d." % (len(p),))
only_numbers = (
ia.is_single_number(p[0])
and ia.is_single_number(p[1]))
assert only_numbers, (
"Expected tuple of numbers, got %s and %s." % (
type(p[0]), type(p[1])))
assert p[0] > -1.0 and p[1] > -1.0, (
"Expected tuple of values >-1.0, got %.4f and "
"%.4f." % (p[0], p[1]))
return iap.Uniform(p[0], p[1])
if isinstance(p, list):
assert len(p) > 0, (
"Expected non-empty list, but got empty one.")
assert all([ia.is_single_number(val) for val in p]), (
"Expected list of numbers, got types %s." % (
", ".join([str(type(v)) for v in p])))
assert all([val > -1.0 for val in p]), (
"Expected list of values >-1.0, got values %s." % (
", ".join(["%.4f" % (v,) for v in p])))
return iap.Choice(p)
if isinstance(p, iap.StochasticParameter):
return p
raise Exception(
"Expected int, tuple of two ints, list of ints or "
"StochasticParameter, got type %s." % (type(p),))
if len(percent) == 2:
all_sides = handle_param(percent)
else: # len == 4
top = handle_param(percent[0])
right = handle_param(percent[1])
bottom = handle_param(percent[2])
left = handle_param(percent[3])
elif isinstance(percent, iap.StochasticParameter):
top = right = bottom = left = percent
else:
raise Exception(
"Expected number, tuple of 4 "
"numbers/tuples/lists/StochasticParameters or "
"StochasticParameter, got type %s." % (type(percent),))
return all_sides, top, right, bottom, left
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
shapes = batch.get_rowwise_shapes()
samples = self._draw_samples(random_state, shapes)
if batch.images is not None:
batch.images = self._augment_images_by_samples(batch.images,
samples)
if batch.heatmaps is not None:
batch.heatmaps = self._augment_maps_by_samples(
batch.heatmaps,
self._pad_mode_heatmaps, self._pad_cval_heatmaps,
samples)
if batch.segmentation_maps is not None:
batch.segmentation_maps = self._augment_maps_by_samples(
batch.segmentation_maps,
self._pad_mode_segmentation_maps,
self._pad_cval_segmentation_maps, samples)
for augm_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
samples=samples)
cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
setattr(batch, augm_name, cbaois)
return batch
# Added in 0.4.0.
def _augment_images_by_samples(self, images, samples):
result = []
for i, image in enumerate(images):
image_cr_pa = _crop_and_pad_arr(
image, samples.croppings(i), samples.paddings(i),
samples.pad_mode[i], samples.pad_cval[i], self.keep_size)
result.append(image_cr_pa)
if ia.is_np_array(images):
if self.keep_size:
result = np.array(result, dtype=images.dtype)
else:
nb_shapes = len({image.shape for image in result})
if nb_shapes == 1:
result = np.array(result, dtype=images.dtype)
return result
# Added in 0.4.0.
def _augment_maps_by_samples(self, augmentables, pad_mode, pad_cval,
samples):
result = []
for i, augmentable in enumerate(augmentables):
augmentable = _crop_and_pad_hms_or_segmaps_(
augmentable,
croppings_img=samples.croppings(i),
paddings_img=samples.paddings(i),
pad_mode=(pad_mode
if pad_mode is not None
else samples.pad_mode[i]),
pad_cval=(pad_cval
if pad_cval is not None
else samples.pad_cval[i]),
keep_size=self.keep_size
)
result.append(augmentable)
return result
# Added in 0.4.0.
def _augment_keypoints_by_samples(self, keypoints_on_images, samples):
result = []
for i, keypoints_on_image in enumerate(keypoints_on_images):
kpsoi_aug = _crop_and_pad_kpsoi_(
keypoints_on_image, croppings_img=samples.croppings(i),
paddings_img=samples.paddings(i), keep_size=self.keep_size)
result.append(kpsoi_aug)
return result
def _draw_samples(self, random_state, shapes):
nb_rows = len(shapes)
shapes_arr = np.array([shape[0:2] for shape in shapes],
dtype=np.int32)
heights = shapes_arr[:, 0]
widths = shapes_arr[:, 1]
if self.mode == "noop":
top = right = bottom = left = np.full((nb_rows,), 0,
dtype=np.int32)
else:
if self.all_sides is not None:
if self.sample_independently:
samples = self.all_sides.draw_samples(
(nb_rows, 4), random_state=random_state)
top = samples[:, 0]
right = samples[:, 1]
bottom = samples[:, 2]
left = samples[:, 3]
else:
sample = self.all_sides.draw_samples(
(nb_rows,), random_state=random_state)
top = right = bottom = left = sample
else:
top = self.top.draw_samples(
(nb_rows,), random_state=random_state)
right = self.right.draw_samples(
(nb_rows,), random_state=random_state)
bottom = self.bottom.draw_samples(
(nb_rows,), random_state=random_state)
left = self.left.draw_samples(
(nb_rows,), random_state=random_state)
if self.mode == "px":
# no change necessary for pixel values
pass
elif self.mode == "percent":
# percentage values have to be transformed to pixel values
heights_f = heights.astype(np.float32)
widths_f = widths.astype(np.float32)
top = np.round(heights_f * top).astype(np.int32)
right = np.round(widths_f * right).astype(np.int32)
bottom = np.round(heights_f * bottom).astype(np.int32)
left = np.round(widths_f * left).astype(np.int32)
else:
raise Exception("Invalid mode")
# np.maximum(., 0) is a bit faster than arr[arr < 0] = 0 and
# significantly faster than clip. The masks could be computed once
# along each side, but it doesn't look like that would improve things
# very much.
crop_top = np.maximum((-1) * top, 0)
crop_right = np.maximum((-1) * right, 0)
crop_bottom = np.maximum((-1) * bottom, 0)
crop_left = np.maximum((-1) * left, 0)
crop_top, crop_bottom = _prevent_zero_sizes_after_crops_(heights, crop_top,
crop_bottom)
crop_left, crop_right = _prevent_zero_sizes_after_crops_(widths, crop_left,
crop_right)
pad_top = np.maximum(top, 0)
pad_right = np.maximum(right, 0)
pad_bottom = np.maximum(bottom, 0)
pad_left = np.maximum(left, 0)
pad_mode = self.pad_mode.draw_samples((nb_rows,),
random_state=random_state)
pad_cval = self.pad_cval.draw_samples((nb_rows,),
random_state=random_state)
return _CropAndPadSamplingResult(
crop_top=crop_top,
crop_right=crop_right,
crop_bottom=crop_bottom,
crop_left=crop_left,
pad_top=pad_top,
pad_right=pad_right,
pad_bottom=pad_bottom,
pad_left=pad_left,
pad_mode=pad_mode,
pad_cval=pad_cval
)
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.all_sides, self.top, self.right, self.bottom, self.left,
self.pad_mode, self.pad_cval]
class Pad(CropAndPad):
"""Pad images, i.e. adds columns/rows of pixels to them.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropAndPad`.
Parameters
----------
px : None or int or imgaug.parameters.StochasticParameter or tuple, optional
The number of pixels to pad on each side of the image.
Expected value range is ``[0, inf)``.
Either this or the parameter `percent` may be set, not both at the same
time.
* If ``None``, then pixel-based padding will not be used.
* If ``int``, then that exact number of pixels will always be
padded.
* If ``StochasticParameter``, then that parameter will be used for
each image. Four samples will be drawn per image (top, right,
bottom, left), unless `sample_independently` is set to ``False``,
as then only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of two ``int`` s with values ``a`` and ``b``,
then each side will be padded by a random amount sampled
uniformly per image and side from the inteval ``[a, b]``. If
however `sample_independently` is set to ``False``, only one
value will be sampled per image and used for all sides.
* If a ``tuple`` of four entries, then the entries represent top,
right, bottom, left. Each entry may be a single ``int`` (always
pad by exactly that value), a ``tuple`` of two ``int`` s
``a`` and ``b`` (pad by an amount within ``[a, b]``), a
``list`` of ``int`` s (pad by a random value that is
contained in the ``list``) or a ``StochasticParameter`` (sample
the amount to pad from that parameter).
percent : None or int or float or imgaug.parameters.StochasticParameter or tuple, optional
The number of pixels to pad
on each side of the image given as a *fraction* of the image
height/width. E.g. if this is set to ``0.1``, the augmenter will
always pad ``10%`` of the image's height at both the top and the
bottom (both ``10%`` each), as well as ``10%`` of the width at the
right and left.
Expected value range is ``[0.0, inf)``.
Either this or the parameter `px` may be set, not both
at the same time.
* If ``None``, then fraction-based padding will not be
used.
* If ``number``, then that fraction will always be padded.
* If ``StochasticParameter``, then that parameter will be used for
each image. Four samples will be drawn per image (top, right,
bottom, left). If however `sample_independently` is set to
``False``, only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of two ``float`` s with values ``a`` and ``b``,
then each side will be padded by a random fraction
sampled uniformly per image and side from the interval
``[a, b]``. If however `sample_independently` is set to
``False``, only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of four entries, then the entries represent top,
right, bottom, left. Each entry may be a single ``float``
(always pad by exactly that fraction), a ``tuple`` of
two ``float`` s ``a`` and ``b`` (pad by a fraction from
``[a, b]``), a ``list`` of ``float`` s (pad by a random
value that is contained in the list) or a ``StochasticParameter``
(sample the percentage to pad from that parameter).
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
Padding mode to use. The available modes match the numpy padding modes,
i.e. ``constant``, ``edge``, ``linear_ramp``, ``maximum``, ``median``,
``minimum``, ``reflect``, ``symmetric``, ``wrap``. The modes
``constant`` and ``linear_ramp`` use extra values, which are provided
by ``pad_cval`` when necessary. See :func:`~imgaug.imgaug.pad` for
more details.
* If ``imgaug.ALL``, then a random mode from all available modes
will be sampled per image.
* If a ``str``, it will be used as the pad mode for all images.
* If a ``list`` of ``str``, a random one of these will be sampled
per image and used as the mode.
* If ``StochasticParameter``, a random mode will be sampled from
this parameter per image.
pad_cval : number or tuple of number list of number or imgaug.parameters.StochasticParameter, optional
The constant value to use if the pad mode is ``constant`` or the end
value to use if the mode is ``linear_ramp``.
See :func:`~imgaug.imgaug.pad` for more details.
* If ``number``, then that value will be used.
* If a ``tuple`` of two ``number`` s and at least one of them is
a ``float``, then a random number will be uniformly sampled per
image from the continuous interval ``[a, b]`` and used as the
value. If both ``number`` s are ``int`` s, the interval is
discrete.
* If a ``list`` of ``number``, then a random value will be chosen
from the elements of the ``list`` and used as the value.
* If ``StochasticParameter``, a random value will be sampled from
that parameter per image.
keep_size : bool, optional
After padding, the result image will usually have a
different height/width compared to the original input image. If this
parameter is set to ``True``, then the padded image will be
resized to the input image's size, i.e. the augmenter's output shape
is always identical to the input shape.
sample_independently : bool, optional
If ``False`` *and* the values for `px`/`percent` result in exactly
*one* probability distribution for all image sides, only one single
value will be sampled from that probability distribution and used for
all sides. I.e. the pad amount then is the same for all sides.
If ``True``, four values will be sampled independently, one per side.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Pad(px=(0, 10))
Pad each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[0..10]``. The padding happens by
zero-padding, i.e. it adds black pixels (default setting).
>>> aug = iaa.Pad(px=(0, 10), pad_mode="edge")
Pad each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[0..10]``. The padding uses the
``edge`` mode from numpy's pad function, i.e. the pixel colors around
the image sides are repeated.
>>> aug = iaa.Pad(px=(0, 10), pad_mode=["constant", "edge"])
Similar to the previous example, but uses zero-padding (``constant``) for
half of the images and ``edge`` padding for the other half.
>>> aug = iaa.Pad(px=(0, 10), pad_mode=ia.ALL, pad_cval=(0, 255))
Similar to the previous example, but uses any available padding mode.
In case the padding mode ends up being ``constant`` or ``linear_ramp``,
and random intensity is uniformly sampled (once per image) from the
discrete interval ``[0..255]`` and used as the intensity of the new
pixels.
>>> aug = iaa.Pad(px=(0, 10), sample_independently=False)
Pad each side by a random pixel value sampled uniformly once per image
from the discrete interval ``[0..10]``. Each sampled value is used
for *all* sides of the corresponding image.
>>> aug = iaa.Pad(px=(0, 10), keep_size=False)
Pad each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[0..10]``. Afterwards, do **not**
resize the padded image back to the input image's size. This will increase
the image's height and width by a maximum of ``20`` pixels.
>>> aug = iaa.Pad(px=((0, 10), (0, 5), (0, 10), (0, 5)))
Pad the top and bottom by a random pixel value sampled uniformly from the
discrete interval ``[0..10]``. Pad the left and right analogously by
a random value sampled from ``[0..5]``. Each value is always sampled
independently.
>>> aug = iaa.Pad(percent=(0, 0.1))
Pad each side by a random fraction sampled uniformly from the continuous
interval ``[0.0, 0.10]``. The fraction is sampled once per image and
side. E.g. a sampled fraction of ``0.1`` for the top side would pad by
``0.1*H``, where ``H`` is the height of the input image.
>>> aug = iaa.Pad(
>>> percent=([0.05, 0.1], [0.05, 0.1], [0.05, 0.1], [0.05, 0.1]))
Pads each side by either ``5%`` or ``10%``. The values are sampled
once per side and image.
"""
def __init__(self, px=None, percent=None, pad_mode="constant", pad_cval=0,
keep_size=True, sample_independently=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
def recursive_validate(value):
if value is None:
return value
if ia.is_single_number(value):
assert value >= 0, "Expected value >0, got %.4f" % (value,)
return value
if isinstance(value, iap.StochasticParameter):
return value
if isinstance(value, tuple):
return tuple([recursive_validate(v_) for v_ in value])
if isinstance(value, list):
return [recursive_validate(v_) for v_ in value]
raise Exception(
"Expected None or int or float or StochasticParameter or "
"list or tuple, got %s." % (type(value),))
if px is None and percent is None:
percent = (0.0, 0.1)
px = recursive_validate(px)
percent = recursive_validate(percent)
super(Pad, self).__init__(
px=px,
percent=percent,
pad_mode=pad_mode,
pad_cval=pad_cval,
keep_size=keep_size,
sample_independently=sample_independently,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class Crop(CropAndPad):
"""Crop images, i.e. remove columns/rows of pixels at the sides of images.
This augmenter allows to extract smaller-sized subimages from given
full-sized input images. The number of pixels to cut off may be defined
in absolute values or as fractions of the image sizes.
This augmenter will never crop images below a height or width of ``1``.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropAndPad`.
Parameters
----------
px : None or int or imgaug.parameters.StochasticParameter or tuple, optional
The number of pixels to crop on each side of the image.
Expected value range is ``[0, inf)``.
Either this or the parameter `percent` may be set, not both at the same
time.
* If ``None``, then pixel-based cropping will not be used.
* If ``int``, then that exact number of pixels will always be
cropped.
* If ``StochasticParameter``, then that parameter will be used for
each image. Four samples will be drawn per image (top, right,
bottom, left), unless `sample_independently` is set to ``False``,
as then only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of two ``int`` s with values ``a`` and ``b``,
then each side will be cropped by a random amount sampled
uniformly per image and side from the inteval ``[a, b]``. If
however `sample_independently` is set to ``False``, only one
value will be sampled per image and used for all sides.
* If a ``tuple`` of four entries, then the entries represent top,
right, bottom, left. Each entry may be a single ``int`` (always
crop by exactly that value), a ``tuple`` of two ``int`` s
``a`` and ``b`` (crop by an amount within ``[a, b]``), a
``list`` of ``int`` s (crop by a random value that is
contained in the ``list``) or a ``StochasticParameter`` (sample
the amount to crop from that parameter).
percent : None or int or float or imgaug.parameters.StochasticParameter or tuple, optional
The number of pixels to crop
on each side of the image given as a *fraction* of the image
height/width. E.g. if this is set to ``0.1``, the augmenter will
always crop ``10%`` of the image's height at both the top and the
bottom (both ``10%`` each), as well as ``10%`` of the width at the
right and left.
Expected value range is ``[0.0, 1.0)``.
Either this or the parameter `px` may be set, not both
at the same time.
* If ``None``, then fraction-based cropping will not be
used.
* If ``number``, then that fraction will always be cropped.
* If ``StochasticParameter``, then that parameter will be used for
each image. Four samples will be drawn per image (top, right,
bottom, left). If however `sample_independently` is set to
``False``, only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of two ``float`` s with values ``a`` and ``b``,
then each side will be cropped by a random fraction
sampled uniformly per image and side from the interval
``[a, b]``. If however `sample_independently` is set to
``False``, only one value will be sampled per image and used for
all sides.
* If a ``tuple`` of four entries, then the entries represent top,
right, bottom, left. Each entry may be a single ``float``
(always crop by exactly that fraction), a ``tuple`` of
two ``float`` s ``a`` and ``b`` (crop by a fraction from
``[a, b]``), a ``list`` of ``float`` s (crop by a random
value that is contained in the list) or a ``StochasticParameter``
(sample the percentage to crop from that parameter).
keep_size : bool, optional
After cropping, the result image will usually have a
different height/width compared to the original input image. If this
parameter is set to ``True``, then the cropped image will be
resized to the input image's size, i.e. the augmenter's output shape
is always identical to the input shape.
sample_independently : bool, optional
If ``False`` *and* the values for `px`/`percent` result in exactly
*one* probability distribution for all image sides, only one single
value will be sampled from that probability distribution and used for
all sides. I.e. the crop amount then is the same for all sides.
If ``True``, four values will be sampled independently, one per side.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Crop(px=(0, 10))
Crop each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[0..10]``.
>>> aug = iaa.Crop(px=(0, 10), sample_independently=False)
Crop each side by a random pixel value sampled uniformly once per image
from the discrete interval ``[0..10]``. Each sampled value is used
for *all* sides of the corresponding image.
>>> aug = iaa.Crop(px=(0, 10), keep_size=False)
Crop each side by a random pixel value sampled uniformly per image and
side from the discrete interval ``[0..10]``. Afterwards, do **not**
resize the cropped image back to the input image's size. This will decrease
the image's height and width by a maximum of ``20`` pixels.
>>> aug = iaa.Crop(px=((0, 10), (0, 5), (0, 10), (0, 5)))
Crop the top and bottom by a random pixel value sampled uniformly from the
discrete interval ``[0..10]``. Crop the left and right analogously by
a random value sampled from ``[0..5]``. Each value is always sampled
independently.
>>> aug = iaa.Crop(percent=(0, 0.1))
Crop each side by a random fraction sampled uniformly from the continuous
interval ``[0.0, 0.10]``. The fraction is sampled once per image and
side. E.g. a sampled fraction of ``0.1`` for the top side would crop by
``0.1*H``, where ``H`` is the height of the input image.
>>> aug = iaa.Crop(
>>> percent=([0.05, 0.1], [0.05, 0.1], [0.05, 0.1], [0.05, 0.1]))
Crops each side by either ``5%`` or ``10%``. The values are sampled
once per side and image.
"""
def __init__(self, px=None, percent=None, keep_size=True,
sample_independently=True,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
def recursive_negate(value):
if value is None:
return value
if ia.is_single_number(value):
assert value >= 0, "Expected value >0, got %.4f." % (value,)
return -value
if isinstance(value, iap.StochasticParameter):
return iap.Multiply(value, -1)
if isinstance(value, tuple):
return tuple([recursive_negate(v_) for v_ in value])
if isinstance(value, list):
return [recursive_negate(v_) for v_ in value]
raise Exception(
"Expected None or int or float or StochasticParameter or "
"list or tuple, got %s." % (type(value),))
if px is None and percent is None:
percent = (0.0, 0.1)
px = recursive_negate(px)
percent = recursive_negate(percent)
super(Crop, self).__init__(
px=px,
percent=percent,
keep_size=keep_size,
sample_independently=sample_independently,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO maybe rename this to PadToMinimumSize?
# TODO this is very similar to CropAndPad, maybe add a way to generate crop
# values imagewise via a callback in in CropAndPad?
# TODO why is padding mode and cval here called pad_mode, pad_cval but in other
# cases mode/cval?
class PadToFixedSize(meta.Augmenter):
"""Pad images to a predefined minimum width and/or height.
If images are already at the minimum width/height or are larger, they will
not be padded. Note that this also means that images will not be cropped if
they exceed the required width/height.
The augmenter randomly decides per image how to distribute the required
padding amounts over the image axis. E.g. if 2px have to be padded on the
left or right to reach the required width, the augmenter will sometimes
add 2px to the left and 0px to the right, sometimes add 2px to the right
and 0px to the left and sometimes add 1px to both sides. Set `position`
to ``center`` to prevent that.
**Supported dtypes**:
See :func:`~imgaug.augmenters.size.pad`.
Parameters
----------
width : int or None
Pad images up to this minimum width.
If ``None``, image widths will not be altered.
height : int or None
Pad images up to this minimum height.
If ``None``, image heights will not be altered.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.CropAndPad.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.CropAndPad.__init__`.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
Sets the center point of the padding, which determines how the
required padding amounts are distributed to each side. For a ``tuple``
``(a, b)``, both ``a`` and ``b`` are expected to be in range
``[0.0, 1.0]`` and describe the fraction of padding applied to the
left/right (low/high values for ``a``) and the fraction of padding
applied to the top/bottom (low/high values for ``b``). A padding
position at ``(0.5, 0.5)`` would be the center of the image and
distribute the padding equally to all sides. A padding position at
``(0.0, 1.0)`` would be the left-bottom and would apply 100% of the
required padding to the bottom and left sides of the image so that
the bottom left corner becomes more and more the new image
center (depending on how much is padded).
* If string ``uniform`` then the share of padding is randomly and
uniformly distributed over each side.
Equivalent to ``(Uniform(0.0, 1.0), Uniform(0.0, 1.0))``.
* If string ``normal`` then the share of padding is distributed
based on a normal distribution, leading to a focus on the
center of the images.
Equivalent to
``(Clip(Normal(0.5, 0.45/2), 0, 1),
Clip(Normal(0.5, 0.45/2), 0, 1))``.
* If string ``center`` then center point of the padding is
identical to the image center.
Equivalent to ``(0.5, 0.5)``.
* If a string matching regex
``^(left|center|right)-(top|center|bottom)$``, e.g. ``left-top``
or ``center-bottom`` then sets the center point of the padding
to the X-Y position matching that description.
* If a tuple of float, then expected to have exactly two entries
between ``0.0`` and ``1.0``, which will always be used as the
combination the position matching (x, y) form.
* If a ``StochasticParameter``, then that parameter will be queried
once per call to ``augment_*()`` to get ``Nx2`` center positions
in ``(x, y)`` form (with ``N`` the number of images).
* If a ``tuple`` of ``StochasticParameter``, then expected to have
exactly two entries that will both be queried per call to
``augment_*()``, each for ``(N,)`` values, to get the center
positions. First parameter is used for ``x`` coordinates,
second for ``y`` coordinates.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.PadToFixedSize(width=100, height=100)
For image sides smaller than ``100`` pixels, pad to ``100`` pixels. Do
nothing for the other edges. The padding is randomly (uniformly)
distributed over the sides, so that e.g. sometimes most of the required
padding is applied to the left, sometimes to the right (analogous
top/bottom).
>>> aug = iaa.PadToFixedSize(width=100, height=100, position="center")
For image sides smaller than ``100`` pixels, pad to ``100`` pixels. Do
nothing for the other image sides. The padding is always equally
distributed over the left/right and top/bottom sides.
>>> aug = iaa.PadToFixedSize(width=100, height=100, pad_mode=ia.ALL)
For image sides smaller than ``100`` pixels, pad to ``100`` pixels and
use any possible padding mode for that. Do nothing for the other image
sides. The padding is always equally distributed over the left/right and
top/bottom sides.
>>> aug = iaa.Sequential([
>>> iaa.PadToFixedSize(width=100, height=100),
>>> iaa.CropToFixedSize(width=100, height=100)
>>> ])
Pad images smaller than ``100x100`` until they reach ``100x100``.
Analogously, crop images larger than ``100x100`` until they reach
``100x100``. The output images therefore have a fixed size of ``100x100``.
"""
def __init__(self, width, height, pad_mode="constant", pad_cval=0,
position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(PadToFixedSize, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.size = (width, height)
# Position of where to pad. The further to the top left this is, the
# larger the share of pixels that will be added to the top and left
# sides. I.e. set to (Deterministic(0.0), Deterministic(0.0)) to only
# add at the top and left, (Deterministic(1.0), Deterministic(1.0))
# to only add at the bottom right. Analogously (0.5, 0.5) pads equally
# on both axis, (0.0, 1.0) pads left and bottom, (1.0, 0.0) pads right
# and top.
self.position = _handle_position_parameter(position)
self.pad_mode = _handle_pad_mode_param(pad_mode)
# TODO enable ALL here like in eg Affine
self.pad_cval = iap.handle_discrete_param(
pad_cval, "pad_cval", value_range=None, tuple_to_uniform=True,
list_to_choice=True, allow_floats=True)
# set these to None to use the same values as sampled for the
# images (not tested)
self._pad_mode_heatmaps = "constant"
self._pad_mode_segmentation_maps = "constant"
self._pad_cval_heatmaps = 0.0
self._pad_cval_segmentation_maps = 0
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
# Providing the whole batch to _draw_samples() would not be necessary
# for this augmenter. The number of rows would be sufficient. This
# formulation however enables derived augmenters to use rowwise shapes
# without having to compute them here for this augmenter.
samples = self._draw_samples(batch, random_state)
if batch.images is not None:
batch.images = self._augment_images_by_samples(batch.images,
samples)
if batch.heatmaps is not None:
batch.heatmaps = self._augment_maps_by_samples(
batch.heatmaps, samples, self._pad_mode_heatmaps,
self._pad_cval_heatmaps)
if batch.segmentation_maps is not None:
batch.segmentation_maps = self._augment_maps_by_samples(
batch.segmentation_maps, samples, self._pad_mode_heatmaps,
self._pad_cval_heatmaps)
for augm_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
samples=samples)
cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
setattr(batch, augm_name, cbaois)
return batch
# Added in 0.4.0.
def _augment_images_by_samples(self, images, samples):
result = []
sizes, pad_xs, pad_ys, pad_modes, pad_cvals = samples
for i, (image, size) in enumerate(zip(images, sizes)):
width_min, height_min = size
height_image, width_image = image.shape[:2]
paddings = self._calculate_paddings(height_image, width_image,
height_min, width_min,
pad_xs[i], pad_ys[i])
image = _crop_and_pad_arr(
image, (0, 0, 0, 0), paddings, pad_modes[i], pad_cvals[i],
keep_size=False)
result.append(image)
# TODO result is always a list. Should this be converted to an array
# if possible (not guaranteed that all images have same size,
# some might have been larger than desired height/width)
return result
# Added in 0.4.0.
def _augment_keypoints_by_samples(self, keypoints_on_images, samples):
result = []
sizes, pad_xs, pad_ys, _, _ = samples
for i, (kpsoi, size) in enumerate(zip(keypoints_on_images, sizes)):
width_min, height_min = size
height_image, width_image = kpsoi.shape[:2]
paddings_img = self._calculate_paddings(height_image, width_image,
height_min, width_min,
pad_xs[i], pad_ys[i])
keypoints_padded = _crop_and_pad_kpsoi_(
kpsoi, (0, 0, 0, 0), paddings_img,
keep_size=False)
result.append(keypoints_padded)
return result
# Added in 0.4.0.
def _augment_maps_by_samples(self, augmentables, samples, pad_mode,
pad_cval):
sizes, pad_xs, pad_ys, pad_modes, pad_cvals = samples
for i, (augmentable, size) in enumerate(zip(augmentables, sizes)):
width_min, height_min = size
height_img, width_img = augmentable.shape[:2]
paddings_img = self._calculate_paddings(
height_img, width_img, height_min, width_min,
pad_xs[i], pad_ys[i])
# TODO for the previous method (and likely the new/current one
# too):
# for 30x30 padded to 32x32 with 15x15 heatmaps this results
# in paddings of 1 on each side (assuming
# position=(0.5, 0.5)) giving 17x17 heatmaps when they should
# be 16x16. Error is due to each side getting projected 0.5
# padding which is rounded to 1. This doesn't seem right.
augmentables[i] = _crop_and_pad_hms_or_segmaps_(
augmentables[i],
(0, 0, 0, 0),
paddings_img,
pad_mode=pad_mode if pad_mode is not None else pad_modes[i],
pad_cval=pad_cval if pad_cval is not None else pad_cvals[i],
keep_size=False)
return augmentables
def _draw_samples(self, batch, random_state):
nb_images = batch.nb_rows
rngs = random_state.duplicate(4)
if isinstance(self.position, tuple):
pad_xs = self.position[0].draw_samples(nb_images,
random_state=rngs[0])
pad_ys = self.position[1].draw_samples(nb_images,
random_state=rngs[1])
else:
pads = self.position.draw_samples((nb_images, 2),
random_state=rngs[0])
pad_xs = pads[:, 0]
pad_ys = pads[:, 1]
pad_modes = self.pad_mode.draw_samples(nb_images,
random_state=rngs[2])
pad_cvals = self.pad_cval.draw_samples(nb_images,
random_state=rngs[3])
# We return here the sizes even though they are static as it allows
# derived augmenters to define image-specific heights/widths.
return [self.size] * nb_images, pad_xs, pad_ys, pad_modes, pad_cvals
@classmethod
def _calculate_paddings(cls, height_image, width_image,
height_min, width_min, pad_xs_i, pad_ys_i):
pad_top = 0
pad_right = 0
pad_bottom = 0
pad_left = 0
if width_min is not None and width_image < width_min:
pad_total_x = width_min - width_image
pad_left = int((1-pad_xs_i) * pad_total_x)
pad_right = pad_total_x - pad_left
if height_min is not None and height_image < height_min:
pad_total_y = height_min - height_image
pad_top = int((1-pad_ys_i) * pad_total_y)
pad_bottom = pad_total_y - pad_top
return pad_top, pad_right, pad_bottom, pad_left
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.size[0], self.size[1], self.pad_mode, self.pad_cval,
self.position]
class CenterPadToFixedSize(PadToFixedSize):
"""Pad images equally on all sides up to given minimum heights/widths.
This is an alias for :class:`~imgaug.augmenters.size.PadToFixedSize`
with ``position="center"``. It spreads the pad amounts equally over
all image sides, while :class:`~imgaug.augmenters.size.PadToFixedSize`
by defaults spreads them randomly.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
width : int or None
See :func:`PadToFixedSize.__init__`.
height : int or None
See :func:`PadToFixedSize.__init__`.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`PadToFixedSize.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`PadToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CenterPadToFixedSize(height=20, width=30)
Create an augmenter that pads images up to ``20x30``, with the padded
rows added *equally* on the top and bottom (analogous for the padded
columns).
"""
# Added in 0.4.0.
def __init__(self, width, height, pad_mode="constant", pad_cval=0,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterPadToFixedSize, self).__init__(
width=width, height=height, pad_mode=pad_mode, pad_cval=pad_cval,
position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO maybe rename this to CropToMaximumSize ?
# TODO this is very similar to CropAndPad, maybe add a way to generate crop
# values imagewise via a callback in in CropAndPad?
# TODO add crop() function in imgaug, similar to pad
class CropToFixedSize(meta.Augmenter):
"""Crop images down to a predefined maximum width and/or height.
If images are already at the maximum width/height or are smaller, they
will not be cropped. Note that this also means that images will not be
padded if they are below the required width/height.
The augmenter randomly decides per image how to distribute the required
cropping amounts over the image axis. E.g. if 2px have to be cropped on
the left or right to reach the required width, the augmenter will
sometimes remove 2px from the left and 0px from the right, sometimes
remove 2px from the right and 0px from the left and sometimes remove 1px
from both sides. Set `position` to ``center`` to prevent that.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested
* ``uint64``: yes; tested
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested
* ``int64``: yes; tested
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested
* ``bool``: yes; tested
Parameters
----------
width : int or None
Crop images down to this maximum width.
If ``None``, image widths will not be altered.
height : int or None
Crop images down to this maximum height.
If ``None``, image heights will not be altered.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
Sets the center point of the cropping, which determines how the
required cropping amounts are distributed to each side. For a
``tuple`` ``(a, b)``, both ``a`` and ``b`` are expected to be in
range ``[0.0, 1.0]`` and describe the fraction of cropping applied
to the left/right (low/high values for ``a``) and the fraction
of cropping applied to the top/bottom (low/high values for ``b``).
A cropping position at ``(0.5, 0.5)`` would be the center of the
image and distribute the cropping equally over all sides. A cropping
position at ``(1.0, 0.0)`` would be the right-top and would apply
100% of the required cropping to the right and top sides of the image.
* If string ``uniform`` then the share of cropping is randomly
and uniformly distributed over each side.
Equivalent to ``(Uniform(0.0, 1.0), Uniform(0.0, 1.0))``.
* If string ``normal`` then the share of cropping is distributed
based on a normal distribution, leading to a focus on the center
of the images.
Equivalent to
``(Clip(Normal(0.5, 0.45/2), 0, 1),
Clip(Normal(0.5, 0.45/2), 0, 1))``.
* If string ``center`` then center point of the cropping is
identical to the image center.
Equivalent to ``(0.5, 0.5)``.
* If a string matching regex
``^(left|center|right)-(top|center|bottom)$``, e.g.
``left-top`` or ``center-bottom`` then sets the center point of
the cropping to the X-Y position matching that description.
* If a tuple of float, then expected to have exactly two entries
between ``0.0`` and ``1.0``, which will always be used as the
combination the position matching (x, y) form.
* If a ``StochasticParameter``, then that parameter will be queried
once per call to ``augment_*()`` to get ``Nx2`` center positions
in ``(x, y)`` form (with ``N`` the number of images).
* If a ``tuple`` of ``StochasticParameter``, then expected to have
exactly two entries that will both be queried per call to
``augment_*()``, each for ``(N,)`` values, to get the center
positions. First parameter is used for ``x`` coordinates,
second for ``y`` coordinates.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CropToFixedSize(width=100, height=100)
For image sides larger than ``100`` pixels, crop to ``100`` pixels. Do
nothing for the other sides. The cropping amounts are randomly (and
uniformly) distributed over the sides of the image.
>>> aug = iaa.CropToFixedSize(width=100, height=100, position="center")
For sides larger than ``100`` pixels, crop to ``100`` pixels. Do nothing
for the other sides. The cropping amounts are always equally distributed
over the left/right sides of the image (and analogously for top/bottom).
>>> aug = iaa.Sequential([
>>> iaa.PadToFixedSize(width=100, height=100),
>>> iaa.CropToFixedSize(width=100, height=100)
>>> ])
Pad images smaller than ``100x100`` until they reach ``100x100``.
Analogously, crop images larger than ``100x100`` until they reach
``100x100``. The output images therefore have a fixed size of ``100x100``.
"""
def __init__(self, width, height, position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CropToFixedSize, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.size = (width, height)
# Position of where to crop. The further to the top left this is,
# the larger the share of pixels that will be cropped from the top
# and left sides. I.e. set to (Deterministic(0.0), Deterministic(0.0))
# to only crop at the top and left,
# (Deterministic(1.0), Deterministic(1.0)) to only crop at the bottom
# right. Analogously (0.5, 0.5) crops equally on both axis,
# (0.0, 1.0) crops left and bottom, (1.0, 0.0) crops right and top.
self.position = _handle_position_parameter(position)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
# Providing the whole batch to _draw_samples() would not be necessary
# for this augmenter. The number of rows would be sufficient. This
# formulation however enables derived augmenters to use rowwise shapes
# without having to compute them here for this augmenter.
samples = self._draw_samples(batch, random_state)
if batch.images is not None:
batch.images = self._augment_images_by_samples(batch.images,
samples)
if batch.heatmaps is not None:
batch.heatmaps = self._augment_maps_by_samples(
batch.heatmaps, samples)
if batch.segmentation_maps is not None:
batch.segmentation_maps = self._augment_maps_by_samples(
batch.segmentation_maps, samples)
for augm_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
func = functools.partial(
self._augment_keypoints_by_samples,
samples=samples)
cbaois = self._apply_to_cbaois_as_keypoints(augm_value, func)
setattr(batch, augm_name, cbaois)
return batch
# Added in 0.4.0.
def _augment_images_by_samples(self, images, samples):
result = []
sizes, offset_xs, offset_ys = samples
for i, (image, size) in enumerate(zip(images, sizes)):
w, h = size
height_image, width_image = image.shape[0:2]
croppings = self._calculate_crop_amounts(
height_image, width_image, h, w, offset_ys[i], offset_xs[i])
image_cropped = _crop_and_pad_arr(image, croppings, (0, 0, 0, 0),
keep_size=False)
result.append(image_cropped)
return result
# Added in 0.4.0.
def _augment_keypoints_by_samples(self, kpsois, samples):
result = []
sizes, offset_xs, offset_ys = samples
for i, (kpsoi, size) in enumerate(zip(kpsois, sizes)):
w, h = size
height_image, width_image = kpsoi.shape[0:2]
croppings_img = self._calculate_crop_amounts(
height_image, width_image, h, w, offset_ys[i], offset_xs[i])
kpsoi_cropped = _crop_and_pad_kpsoi_(
kpsoi, croppings_img, (0, 0, 0, 0), keep_size=False)
result.append(kpsoi_cropped)
return result
# Added in 0.4.0.
def _augment_maps_by_samples(self, augmentables, samples):
sizes, offset_xs, offset_ys = samples
for i, (augmentable, size) in enumerate(zip(augmentables, sizes)):
w, h = size
height_image, width_image = augmentable.shape[0:2]
croppings_img = self._calculate_crop_amounts(
height_image, width_image, h, w, offset_ys[i], offset_xs[i])
augmentables[i] = _crop_and_pad_hms_or_segmaps_(
augmentable, croppings_img, (0, 0, 0, 0), keep_size=False)
return augmentables
@classmethod
def _calculate_crop_amounts(cls, height_image, width_image,
height_max, width_max,
offset_y, offset_x):
crop_top = 0
crop_right = 0
crop_bottom = 0
crop_left = 0
if height_max is not None and height_image > height_max:
crop_top = int(offset_y * (height_image - height_max))
crop_bottom = height_image - height_max - crop_top
if width_max is not None and width_image > width_max:
crop_left = int(offset_x * (width_image - width_max))
crop_right = width_image - width_max - crop_left
return crop_top, crop_right, crop_bottom, crop_left
def _draw_samples(self, batch, random_state):
nb_images = batch.nb_rows
rngs = random_state.duplicate(2)
if isinstance(self.position, tuple):
offset_xs = self.position[0].draw_samples(nb_images,
random_state=rngs[0])
offset_ys = self.position[1].draw_samples(nb_images,
random_state=rngs[1])
else:
offsets = self.position.draw_samples((nb_images, 2),
random_state=rngs[0])
offset_xs = offsets[:, 0]
offset_ys = offsets[:, 1]
offset_xs = 1.0 - offset_xs
offset_ys = 1.0 - offset_ys
# We return here the sizes even though they are static as it allows
# derived augmenters to define image-specific heights/widths.
return [self.size] * nb_images, offset_xs, offset_ys
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.size[0], self.size[1], self.position]
class CenterCropToFixedSize(CropToFixedSize):
"""Take a crop from the center of each image.
This is an alias for :class:`~imgaug.augmenters.size.CropToFixedSize` with
``position="center"``.
.. note::
If images already have a width and/or height below the provided
width and/or height then this augmenter will do nothing for the
respective axis. Hence, resulting images can be smaller than the
provided axis sizes.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
width : int or None
See :func:`CropToFixedSize.__init__`.
height : int or None
See :func:`CropToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> crop = iaa.CenterCropToFixedSize(height=20, width=10)
Create an augmenter that takes ``20x10`` sized crops from the center of
images.
"""
# Added in 0.4.0.
def __init__(self, width, height,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterCropToFixedSize, self).__init__(
width=width, height=height, position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CropToMultiplesOf(CropToFixedSize):
"""Crop images down until their height/width is a multiple of a value.
.. note::
For a given axis size ``A`` and multiple ``M``, if ``A`` is in the
interval ``[0 .. M]``, the axis will not be changed.
As a result, this augmenter can still produce axis sizes that are
not multiples of the given values.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
width_multiple : int or None
Multiple for the width. Images will be cropped down until their
width is a multiple of this value.
If ``None``, image widths will not be altered.
height_multiple : int or None
Multiple for the height. Images will be cropped down until their
height is a multiple of this value.
If ``None``, image heights will not be altered.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
See :func:`CropToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CropToMultiplesOf(height_multiple=10, width_multiple=6)
Create an augmenter that crops images to multiples of ``10`` along
the y-axis (i.e. 10, 20, 30, ...) and to multiples of ``6`` along the
x-axis (i.e. 6, 12, 18, ...).
The rows to be cropped will be spread *randomly* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, width_multiple, height_multiple, position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CropToMultiplesOf, self).__init__(
width=None, height=None, position=position,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.width_multiple = width_multiple
self.height_multiple = height_multiple
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
_sizes, offset_xs, offset_ys = super(
CropToMultiplesOf, self
)._draw_samples(batch, random_state)
shapes = batch.get_rowwise_shapes()
sizes = []
for shape in shapes:
height, width = shape[0:2]
croppings = compute_croppings_to_reach_multiples_of(
shape,
height_multiple=self.height_multiple,
width_multiple=self.width_multiple)
# TODO change that
# note that these are not in the same order as shape tuples
# in CropToFixedSize
new_size = (
width - croppings[1] - croppings[3],
height - croppings[0] - croppings[2]
)
sizes.append(new_size)
return sizes, offset_xs, offset_ys
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.width_multiple, self.height_multiple, self.position]
class CenterCropToMultiplesOf(CropToMultiplesOf):
"""Crop images equally on all sides until H/W are multiples of given values.
This is the same as :class:`~imgaug.augmenters.size.CropToMultiplesOf`,
but uses ``position="center"`` by default, which spreads the crop amounts
equally over all image sides, while
:class:`~imgaug.augmenters.size.CropToMultiplesOf` by default spreads
them randomly.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
width_multiple : int or None
See :func:`CropToMultiplesOf.__init__`.
height_multiple : int or None
See :func:`CropToMultiplesOf.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CenterCropToMultiplesOf(height_multiple=10, width_multiple=6)
Create an augmenter that crops images to multiples of ``10`` along
the y-axis (i.e. 10, 20, 30, ...) and to multiples of ``6`` along the
x-axis (i.e. 6, 12, 18, ...).
The rows to be cropped will be spread *equally* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, width_multiple, height_multiple,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterCropToMultiplesOf, self).__init__(
width_multiple=width_multiple,
height_multiple=height_multiple,
position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class PadToMultiplesOf(PadToFixedSize):
"""Pad images until their height/width is a multiple of a value.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
width_multiple : int or None
Multiple for the width. Images will be padded until their
width is a multiple of this value.
If ``None``, image widths will not be altered.
height_multiple : int or None
Multiple for the height. Images will be padded until their
height is a multiple of this value.
If ``None``, image heights will not be altered.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToFixedSize.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToFixedSize.__init__`.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
See :func:`PadToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.PadToMultiplesOf(height_multiple=10, width_multiple=6)
Create an augmenter that pads images to multiples of ``10`` along
the y-axis (i.e. 10, 20, 30, ...) and to multiples of ``6`` along the
x-axis (i.e. 6, 12, 18, ...).
The rows to be padded will be spread *randomly* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, width_multiple, height_multiple,
pad_mode="constant", pad_cval=0,
position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(PadToMultiplesOf, self).__init__(
width=None, height=None, pad_mode=pad_mode, pad_cval=pad_cval,
position=position,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.width_multiple = width_multiple
self.height_multiple = height_multiple
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
_sizes, pad_xs, pad_ys, pad_modes, pad_cvals = super(
PadToMultiplesOf, self
)._draw_samples(batch, random_state)
shapes = batch.get_rowwise_shapes()
sizes = []
for shape in shapes:
height, width = shape[0:2]
paddings = compute_paddings_to_reach_multiples_of(
shape,
height_multiple=self.height_multiple,
width_multiple=self.width_multiple)
# TODO change that
# note that these are not in the same order as shape tuples
# in PadToFixedSize
new_size = (
width + paddings[1] + paddings[3],
height + paddings[0] + paddings[2]
)
sizes.append(new_size)
return sizes, pad_xs, pad_ys, pad_modes, pad_cvals
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.width_multiple, self.height_multiple,
self.pad_mode, self.pad_cval,
self.position]
class CenterPadToMultiplesOf(PadToMultiplesOf):
"""Pad images equally on all sides until H/W are multiples of given values.
This is the same as :class:`~imgaug.augmenters.size.PadToMultiplesOf`, but
uses ``position="center"`` by default, which spreads the pad amounts
equally over all image sides, while
:class:`~imgaug.augmenters.size.PadToMultiplesOf` by default spreads them
randomly.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
width_multiple : int or None
See :func:`PadToMultiplesOf.__init__`.
height_multiple : int or None
See :func:`PadToMultiplesOf.__init__`.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToMultiplesOf.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToMultiplesOf.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CenterPadToMultiplesOf(height_multiple=10, width_multiple=6)
Create an augmenter that pads images to multiples of ``10`` along
the y-axis (i.e. 10, 20, 30, ...) and to multiples of ``6`` along the
x-axis (i.e. 6, 12, 18, ...).
The rows to be padded will be spread *equally* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, width_multiple, height_multiple,
pad_mode="constant", pad_cval=0,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterPadToMultiplesOf, self).__init__(
width_multiple=width_multiple,
height_multiple=height_multiple,
pad_mode=pad_mode,
pad_cval=pad_cval,
position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CropToPowersOf(CropToFixedSize):
"""Crop images until their height/width is a power of a base.
This augmenter removes pixels from an axis with size ``S`` leading to the
new size ``S'`` until ``S' = B^E`` is fulfilled, where ``B`` is a
provided base (e.g. ``2``) and ``E`` is an exponent from the discrete
interval ``[1 .. inf)``.
.. note::
This augmenter does nothing for axes with size less than ``B^1 = B``.
If you have images with ``S < B^1``, it is recommended
to combine this augmenter with a padding augmenter that pads each
axis up to ``B``.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
width_base : int or None
Base for the width. Images will be cropped down until their
width fulfills ``width' = width_base ^ E`` with ``E`` being any
natural number.
If ``None``, image widths will not be altered.
height_base : int or None
Base for the height. Images will be cropped down until their
height fulfills ``height' = height_base ^ E`` with ``E`` being any
natural number.
If ``None``, image heights will not be altered.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
See :func:`CropToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CropToPowersOf(height_base=3, width_base=2)
Create an augmenter that crops each image down to powers of ``3`` along
the y-axis (i.e. 3, 9, 27, ...) and powers of ``2`` along the x-axis (i.e.
2, 4, 8, 16, ...).
The rows to be cropped will be spread *randomly* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, width_base, height_base, position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CropToPowersOf, self).__init__(
width=None, height=None, position=position,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.width_base = width_base
self.height_base = height_base
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
_sizes, offset_xs, offset_ys = super(
CropToPowersOf, self
)._draw_samples(batch, random_state)
shapes = batch.get_rowwise_shapes()
sizes = []
for shape in shapes:
height, width = shape[0:2]
croppings = compute_croppings_to_reach_powers_of(
shape,
height_base=self.height_base,
width_base=self.width_base)
# TODO change that
# note that these are not in the same order as shape tuples
# in CropToFixedSize
new_size = (
width - croppings[1] - croppings[3],
height - croppings[0] - croppings[2]
)
sizes.append(new_size)
return sizes, offset_xs, offset_ys
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.width_base, self.height_base, self.position]
class CenterCropToPowersOf(CropToPowersOf):
"""Crop images equally on all sides until H/W is a power of a base.
This is the same as :class:`~imgaug.augmenters.size.CropToPowersOf`, but
uses ``position="center"`` by default, which spreads the crop amounts
equally over all image sides, while
:class:`~imgaug.augmenters.size.CropToPowersOf` by default spreads them
randomly.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
width_base : int or None
See :func:`CropToPowersOf.__init__`.
height_base : int or None
See :func:`CropToPowersOf.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CropToPowersOf(height_base=3, width_base=2)
Create an augmenter that crops each image down to powers of ``3`` along
the y-axis (i.e. 3, 9, 27, ...) and powers of ``2`` along the x-axis (i.e.
2, 4, 8, 16, ...).
The rows to be cropped will be spread *equally* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, width_base, height_base,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterCropToPowersOf, self).__init__(
width_base=width_base, height_base=height_base, position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class PadToPowersOf(PadToFixedSize):
"""Pad images until their height/width is a power of a base.
This augmenter adds pixels to an axis with size ``S`` leading to the
new size ``S'`` until ``S' = B^E`` is fulfilled, where ``B`` is a
provided base (e.g. ``2``) and ``E`` is an exponent from the discrete
interval ``[1 .. inf)``.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
width_base : int or None
Base for the width. Images will be padded down until their
width fulfills ``width' = width_base ^ E`` with ``E`` being any
natural number.
If ``None``, image widths will not be altered.
height_base : int or None
Base for the height. Images will be padded until their
height fulfills ``height' = height_base ^ E`` with ``E`` being any
natural number.
If ``None``, image heights will not be altered.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToFixedSize.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToFixedSize.__init__`.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
See :func:`PadToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.PadToPowersOf(height_base=3, width_base=2)
Create an augmenter that pads each image to powers of ``3`` along the
y-axis (i.e. 3, 9, 27, ...) and powers of ``2`` along the x-axis (i.e. 2,
4, 8, 16, ...).
The rows to be padded will be spread *randomly* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, width_base, height_base,
pad_mode="constant", pad_cval=0,
position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(PadToPowersOf, self).__init__(
width=None, height=None, pad_mode=pad_mode, pad_cval=pad_cval,
position=position,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.width_base = width_base
self.height_base = height_base
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
_sizes, pad_xs, pad_ys, pad_modes, pad_cvals = super(
PadToPowersOf, self
)._draw_samples(batch, random_state)
shapes = batch.get_rowwise_shapes()
sizes = []
for shape in shapes:
height, width = shape[0:2]
paddings = compute_paddings_to_reach_powers_of(
shape,
height_base=self.height_base,
width_base=self.width_base)
# TODO change that
# note that these are not in the same order as shape tuples
# in PadToFixedSize
new_size = (
width + paddings[1] + paddings[3],
height + paddings[0] + paddings[2]
)
sizes.append(new_size)
return sizes, pad_xs, pad_ys, pad_modes, pad_cvals
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.width_base, self.height_base,
self.pad_mode, self.pad_cval,
self.position]
class CenterPadToPowersOf(PadToPowersOf):
"""Pad images equally on all sides until H/W is a power of a base.
This is the same as :class:`~imgaug.augmenters.size.PadToPowersOf`, but uses
``position="center"`` by default, which spreads the pad amounts equally
over all image sides, while :class:`~imgaug.augmenters.size.PadToPowersOf`
by default spreads them randomly.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
width_base : int or None
See :func:`PadToPowersOf.__init__`.
height_base : int or None
See :func:`PadToPowersOf.__init__`.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToPowersOf.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToPowersOf.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CenterPadToPowersOf(height_base=5, width_base=2)
Create an augmenter that pads each image to powers of ``3`` along the
y-axis (i.e. 3, 9, 27, ...) and powers of ``2`` along the x-axis (i.e. 2,
4, 8, 16, ...).
The rows to be padded will be spread *equally* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, width_base, height_base,
pad_mode="constant", pad_cval=0,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterPadToPowersOf, self).__init__(
width_base=width_base, height_base=height_base,
pad_mode=pad_mode, pad_cval=pad_cval,
position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CropToAspectRatio(CropToFixedSize):
"""Crop images until their width/height matches an aspect ratio.
This augmenter removes either rows or columns until the image reaches
the desired aspect ratio given in ``width / height``. The cropping
operation is stopped once the desired aspect ratio is reached or the image
side to crop reaches a size of ``1``. If any side of the image starts
with a size of ``0``, the image will not be changed.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
aspect_ratio : number
The desired aspect ratio, given as ``width/height``. E.g. a ratio
of ``2.0`` denotes an image that is twice as wide as it is high.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
See :func:`CropToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CropToAspectRatio(2.0)
Create an augmenter that crops each image until its aspect ratio is as
close as possible to ``2.0`` (i.e. two times as many pixels along the
x-axis than the y-axis).
The rows to be cropped will be spread *randomly* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, aspect_ratio, position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CropToAspectRatio, self).__init__(
width=None, height=None, position=position,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.aspect_ratio = aspect_ratio
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
_sizes, offset_xs, offset_ys = super(
CropToAspectRatio, self
)._draw_samples(batch, random_state)
shapes = batch.get_rowwise_shapes()
sizes = []
for shape in shapes:
height, width = shape[0:2]
if height == 0 or width == 0:
croppings = (0, 0, 0, 0)
else:
croppings = compute_croppings_to_reach_aspect_ratio(
shape,
aspect_ratio=self.aspect_ratio)
# TODO change that
# note that these are not in the same order as shape tuples
# in CropToFixedSize
new_size = (
width - croppings[1] - croppings[3],
height - croppings[0] - croppings[2]
)
sizes.append(new_size)
return sizes, offset_xs, offset_ys
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.aspect_ratio, self.position]
class CenterCropToAspectRatio(CropToAspectRatio):
"""Crop images equally on all sides until they reach an aspect ratio.
This is the same as :class:`~imgaug.augmenters.size.CropToAspectRatio`, but
uses ``position="center"`` by default, which spreads the crop amounts
equally over all image sides, while
:class:`~imgaug.augmenters.size.CropToAspectRatio` by default spreads
them randomly.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
aspect_ratio : number
See :func:`CropToAspectRatio.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CenterCropToAspectRatio(2.0)
Create an augmenter that crops each image until its aspect ratio is as
close as possible to ``2.0`` (i.e. two times as many pixels along the
x-axis than the y-axis).
The rows to be cropped will be spread *equally* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, aspect_ratio,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterCropToAspectRatio, self).__init__(
aspect_ratio=aspect_ratio, position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class PadToAspectRatio(PadToFixedSize):
"""Pad images until their width/height matches an aspect ratio.
This augmenter adds either rows or columns until the image reaches
the desired aspect ratio given in ``width / height``.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
aspect_ratio : number
The desired aspect ratio, given as ``width/height``. E.g. a ratio
of ``2.0`` denotes an image that is twice as wide as it is high.
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
See :func:`PadToFixedSize.__init__`.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToFixedSize.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.PadToAspectRatio(2.0)
Create an augmenter that pads each image until its aspect ratio is as
close as possible to ``2.0`` (i.e. two times as many pixels along the
x-axis than the y-axis).
The rows to be padded will be spread *randomly* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, aspect_ratio, pad_mode="constant", pad_cval=0,
position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(PadToAspectRatio, self).__init__(
width=None, height=None, pad_mode=pad_mode, pad_cval=pad_cval,
position=position,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.aspect_ratio = aspect_ratio
# Added in 0.4.0.
def _draw_samples(self, batch, random_state):
_sizes, pad_xs, pad_ys, pad_modes, pad_cvals = super(
PadToAspectRatio, self
)._draw_samples(batch, random_state)
shapes = batch.get_rowwise_shapes()
sizes = []
for shape in shapes:
height, width = shape[0:2]
paddings = compute_paddings_to_reach_aspect_ratio(
shape,
aspect_ratio=self.aspect_ratio)
# TODO change that
# note that these are not in the same order as shape tuples
# in PadToFixedSize
new_size = (
width + paddings[1] + paddings[3],
height + paddings[0] + paddings[2]
)
sizes.append(new_size)
return sizes, pad_xs, pad_ys, pad_modes, pad_cvals
# Added in 0.4.0.
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.aspect_ratio, self.pad_mode, self.pad_cval,
self.position]
class CenterPadToAspectRatio(PadToAspectRatio):
"""Pad images equally on all sides until H/W matches an aspect ratio.
This is the same as :class:`~imgaug.augmenters.size.PadToAspectRatio`, but
uses ``position="center"`` by default, which spreads the pad amounts
equally over all image sides, while
:class:`~imgaug.augmenters.size.PadToAspectRatio` by default spreads them
randomly.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
aspect_ratio : number
See :func:`PadToAspectRatio.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToAspectRatio.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToAspectRatio.__init__`.
deterministic : bool, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.PadToAspectRatio(2.0)
Create am augmenter that pads each image until its aspect ratio is as
close as possible to ``2.0`` (i.e. two times as many pixels along the
x-axis than the y-axis).
The rows to be padded will be spread *equally* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, aspect_ratio, pad_mode="constant", pad_cval=0,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterPadToAspectRatio, self).__init__(
aspect_ratio=aspect_ratio, position="center",
pad_mode=pad_mode, pad_cval=pad_cval,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CropToSquare(CropToAspectRatio):
"""Crop images until their width and height are identical.
This is identical to :class:`~imgaug.augmenters.size.CropToAspectRatio`
with ``aspect_ratio=1.0``.
Images with axis sizes of ``0`` will not be altered.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
See :func:`CropToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CropToSquare()
Create an augmenter that crops each image until its square, i.e. height
and width match.
The rows to be cropped will be spread *randomly* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CropToSquare, self).__init__(
aspect_ratio=1.0, position=position,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CenterCropToSquare(CropToSquare):
"""Crop images equally on all sides until their height/width are identical.
In contrast to :class:`~imgaug.augmenters.size.CropToSquare`, this
augmenter always tries to spread the columns/rows to remove equally over
both sides of the respective axis to be cropped.
:class:`~imgaug.augmenters.size.CropToAspectRatio` by default spreads the
croppings randomly.
This augmenter is identical to :class:`~imgaug.augmenters.size.CropToSquare`
with ``position="center"``, and thereby the same as
:class:`~imgaug.augmenters.size.CropToAspectRatio` with
``aspect_ratio=1.0, position="center"``.
Images with axis sizes of ``0`` will not be altered.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.CropToFixedSize`.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CenterCropToSquare()
Create an augmenter that crops each image until its square, i.e. height
and width match.
The rows to be cropped will be spread *equally* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterCropToSquare, self).__init__(
position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class PadToSquare(PadToAspectRatio):
"""Pad images until their height and width are identical.
This augmenter is identical to
:class:`~imgaug.augmenters.size.PadToAspectRatio` with ``aspect_ratio=1.0``.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
position : {'uniform', 'normal', 'center', 'left-top', 'left-center', 'left-bottom', 'center-top', 'center-center', 'center-bottom', 'right-top', 'right-center', 'right-bottom'} or tuple of float or StochasticParameter or tuple of StochasticParameter, optional
See :func:`PadToFixedSize.__init__`.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToFixedSize.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToFixedSize.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.PadToSquare()
Create an augmenter that pads each image until its square, i.e. height
and width match.
The rows to be padded will be spread *randomly* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, pad_mode="constant", pad_cval=0, position="uniform",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(PadToSquare, self).__init__(
aspect_ratio=1.0, pad_mode=pad_mode, pad_cval=pad_cval,
position=position,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class CenterPadToSquare(PadToSquare):
"""Pad images equally on all sides until their height & width are identical.
This is the same as :class:`~imgaug.augmenters.size.PadToSquare`, but uses
``position="center"`` by default, which spreads the pad amounts equally
over all image sides, while :class:`~imgaug.augmenters.size.PadToSquare`
by default spreads them randomly. This augmenter is thus also identical to
:class:`~imgaug.augmenters.size.PadToAspectRatio` with
``aspect_ratio=1.0, position="center"``.
Added in 0.4.0.
**Supported dtypes**:
See :class:`~imgaug.augmenters.size.PadToFixedSize`.
Parameters
----------
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
pad_mode : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToAspectRatio.__init__`.
pad_cval : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.augmenters.size.PadToAspectRatio.__init__`.
deterministic : bool, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.CenterPadToSquare()
Create an augmenter that pads each image until its square, i.e. height
and width match.
The rows to be padded will be spread *equally* over the top and bottom
sides (analogous for the left/right sides).
"""
# Added in 0.4.0.
def __init__(self, pad_mode="constant", pad_cval=0,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CenterPadToSquare, self).__init__(
pad_mode=pad_mode, pad_cval=pad_cval, position="center",
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class KeepSizeByResize(meta.Augmenter):
"""Resize images back to their input sizes after applying child augmenters.
Combining this with e.g. a cropping augmenter as the child will lead to
images being resized back to the input size after the crop operation was
applied. Some augmenters have a ``keep_size`` argument that achieves the
same goal (if set to ``True``), though this augmenter offers control over
the interpolation mode and which augmentables to resize (images, heatmaps,
segmentation maps).
**Supported dtypes**:
See :func:`~imgaug.imgaug.imresize_many_images`.
Parameters
----------
children : Augmenter or list of imgaug.augmenters.meta.Augmenter or None, optional
One or more augmenters to apply to images. These augmenters may change
the image size.
interpolation : KeepSizeByResize.NO_RESIZE or {'nearest', 'linear', 'area', 'cubic'} or {cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_AREA, cv2.INTER_CUBIC} or list of str or list of int or StochasticParameter, optional
The interpolation mode to use when resizing images.
Can take any value that :func:`~imgaug.imgaug.imresize_single_image`
accepts, e.g. ``cubic``.
* If this is ``KeepSizeByResize.NO_RESIZE`` then images will not
be resized.
* If this is a single ``str``, it is expected to have one of the
following values: ``nearest``, ``linear``, ``area``, ``cubic``.
* If this is a single integer, it is expected to have a value
identical to one of: ``cv2.INTER_NEAREST``,
``cv2.INTER_LINEAR``, ``cv2.INTER_AREA``, ``cv2.INTER_CUBIC``.
* If this is a ``list`` of ``str`` or ``int``, it is expected that
each ``str``/``int`` is one of the above mentioned valid ones.
A random one of these values will be sampled per image.
* If this is a ``StochasticParameter``, it will be queried once per
call to ``_augment_images()`` and must return ``N`` ``str`` s or
``int`` s (matching the above mentioned ones) for ``N`` images.
interpolation_heatmaps : KeepSizeByResize.SAME_AS_IMAGES or KeepSizeByResize.NO_RESIZE or {'nearest', 'linear', 'area', 'cubic'} or {cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_AREA, cv2.INTER_CUBIC} or list of str or list of int or StochasticParameter, optional
The interpolation mode to use when resizing heatmaps.
Meaning and valid values are similar to `interpolation`. This
parameter may also take the value ``KeepSizeByResize.SAME_AS_IMAGES``,
which will lead to copying the interpolation modes used for the
corresponding images. The value may also be returned on a per-image
basis if `interpolation_heatmaps` is provided as a
``StochasticParameter`` or may be one possible value if it is
provided as a ``list`` of ``str``.
interpolation_segmaps : KeepSizeByResize.SAME_AS_IMAGES or KeepSizeByResize.NO_RESIZE or {'nearest', 'linear', 'area', 'cubic'} or {cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_AREA, cv2.INTER_CUBIC} or list of str or list of int or StochasticParameter, optional
The interpolation mode to use when resizing segmentation maps.
Similar to `interpolation_heatmaps`.
**Note**: For segmentation maps, only ``NO_RESIZE`` or nearest
neighbour interpolation (i.e. ``nearest``) make sense in the vast
majority of all cases.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.KeepSizeByResize(
>>> iaa.Crop((20, 40), keep_size=False)
>>> )
Apply random cropping to input images, then resize them back to their
original input sizes. The resizing is done using this augmenter instead
of the corresponding internal resizing operation in ``Crop``.
>>> aug = iaa.KeepSizeByResize(
>>> iaa.Crop((20, 40), keep_size=False),
>>> interpolation="nearest"
>>> )
Same as in the previous example, but images are now always resized using
nearest neighbour interpolation.
>>> aug = iaa.KeepSizeByResize(
>>> iaa.Crop((20, 40), keep_size=False),
>>> interpolation=["nearest", "cubic"],
>>> interpolation_heatmaps=iaa.KeepSizeByResize.SAME_AS_IMAGES,
>>> interpolation_segmaps=iaa.KeepSizeByResize.NO_RESIZE
>>> )
Similar to the previous example, but images are now sometimes resized
using linear interpolation and sometimes using nearest neighbour
interpolation. Heatmaps are resized using the same interpolation as was
used for the corresponding image. Segmentation maps are not resized and
will therefore remain at their size after cropping.
"""
NO_RESIZE = "NO_RESIZE"
SAME_AS_IMAGES = "SAME_AS_IMAGES"
def __init__(self, children,
interpolation="cubic",
interpolation_heatmaps=SAME_AS_IMAGES,
interpolation_segmaps="nearest",
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(KeepSizeByResize, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.children = children
@iap._prefetchable_str
def _validate_param(val, allow_same_as_images):
valid_ips_and_resize = ia.IMRESIZE_VALID_INTERPOLATIONS \
+ [KeepSizeByResize.NO_RESIZE]
if allow_same_as_images and val == self.SAME_AS_IMAGES:
return self.SAME_AS_IMAGES
if val in valid_ips_and_resize:
return iap.Deterministic(val)
if isinstance(val, list):
assert len(val) > 0, (
"Expected a list of at least one interpolation method. "
"Got an empty list.")
valid_ips_here = valid_ips_and_resize
if allow_same_as_images:
valid_ips_here = valid_ips_here \
+ [KeepSizeByResize.SAME_AS_IMAGES]
only_valid_ips = all([ip in valid_ips_here for ip in val])
assert only_valid_ips, (
"Expected each interpolations to be one of '%s', got "
"'%s'." % (str(valid_ips_here), str(val)))
return iap.Choice(val)
if isinstance(val, iap.StochasticParameter):
return val
raise Exception(
"Expected interpolation to be one of '%s' or a list of "
"these values or a StochasticParameter. Got type %s." % (
str(ia.IMRESIZE_VALID_INTERPOLATIONS), type(val)))
self.children = meta.handle_children_list(children, self.name, "then")
self.interpolation = _validate_param(interpolation, False)
self.interpolation_heatmaps = _validate_param(interpolation_heatmaps,
True)
self.interpolation_segmaps = _validate_param(interpolation_segmaps,
True)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
with batch.propagation_hooks_ctx(self, hooks, parents):
images_were_array = None
if batch.images is not None:
images_were_array = ia.is_np_array(batch.images)
shapes_orig = self._get_shapes(batch)
samples = self._draw_samples(batch.nb_rows, random_state)
batch = self.children.augment_batch_(
batch, parents=parents + [self], hooks=hooks)
if batch.images is not None:
batch.images = self._keep_size_images(
batch.images, shapes_orig["images"], images_were_array,
samples)
if batch.heatmaps is not None:
# dont use shapes_orig["images"] because they might be None
batch.heatmaps = self._keep_size_maps(
batch.heatmaps, shapes_orig["heatmaps"],
shapes_orig["heatmaps_arr"], samples[1])
if batch.segmentation_maps is not None:
# dont use shapes_orig["images"] because they might be None
batch.segmentation_maps = self._keep_size_maps(
batch.segmentation_maps, shapes_orig["segmentation_maps"],
shapes_orig["segmentation_maps_arr"], samples[2])
for augm_name in ["keypoints", "bounding_boxes", "polygons",
"line_strings"]:
augm_value = getattr(batch, augm_name)
if augm_value is not None:
func = functools.partial(
self._keep_size_keypoints,
shapes_orig=shapes_orig[augm_name],
interpolations=samples[0])
cbaois = self._apply_to_cbaois_as_keypoints(augm_value,
func)
setattr(batch, augm_name, cbaois)
return batch
# Added in 0.4.0.
@classmethod
def _keep_size_images(cls, images, shapes_orig, images_were_array,
samples):
interpolations, _, _ = samples
gen = zip(images, interpolations, shapes_orig)
result = []
for image, interpolation, input_shape in gen:
if interpolation == KeepSizeByResize.NO_RESIZE:
result.append(image)
else:
result.append(
ia.imresize_single_image(image, input_shape[0:2],
interpolation))
if images_were_array:
# note here that NO_RESIZE can have led to different shapes
nb_shapes = len({image.shape for image in result})
if nb_shapes == 1:
# images.dtype does not necessarily work anymore, children
# might have turned 'images' into list
result = np.array(result, dtype=result[0].dtype)
return result
# Added in 0.4.0.
@classmethod
def _keep_size_maps(cls, augmentables, shapes_orig_images,
shapes_orig_arrs, interpolations):
result = []
gen = zip(augmentables, interpolations,
shapes_orig_arrs, shapes_orig_images)
for augmentable, interpolation, arr_shape_orig, img_shape_orig in gen:
if interpolation == "NO_RESIZE":
result.append(augmentable)
else:
augmentable = augmentable.resize(
arr_shape_orig[0:2], interpolation=interpolation)
augmentable.shape = img_shape_orig
result.append(augmentable)
return result
# Added in 0.4.0.
@classmethod
def _keep_size_keypoints(cls, kpsois_aug, shapes_orig, interpolations):
result = []
gen = zip(kpsois_aug, interpolations, shapes_orig)
for kpsoi_aug, interpolation, input_shape in gen:
if interpolation == KeepSizeByResize.NO_RESIZE:
result.append(kpsoi_aug)
else:
result.append(kpsoi_aug.on_(input_shape))
return result
# Added in 0.4.0.
@classmethod
def _get_shapes(cls, batch):
result = dict()
for column in batch.columns:
result[column.name] = [cell.shape for cell in column.value]
if batch.heatmaps is not None:
result["heatmaps_arr"] = [
cell.arr_0to1.shape for cell in batch.heatmaps]
if batch.segmentation_maps is not None:
result["segmentation_maps_arr"] = [
cell.arr.shape for cell in batch.segmentation_maps]
return result
def _draw_samples(self, nb_images, random_state):
rngs = random_state.duplicate(3)
interpolations = self.interpolation.draw_samples((nb_images,),
random_state=rngs[0])
if self.interpolation_heatmaps == KeepSizeByResize.SAME_AS_IMAGES:
interpolations_heatmaps = np.copy(interpolations)
else:
interpolations_heatmaps = self.interpolation_heatmaps.draw_samples(
(nb_images,), random_state=rngs[1]
)
# Note that `interpolations_heatmaps == self.SAME_AS_IMAGES`
# works here only if the datatype of the array is such that it
# may contain strings. It does not work properly for e.g.
# integer arrays and will produce a single bool output, even
# for arrays with more than one entry.
same_as_imgs_idx = [ip == self.SAME_AS_IMAGES
for ip in interpolations_heatmaps]
interpolations_heatmaps[same_as_imgs_idx] = \
interpolations[same_as_imgs_idx]
if self.interpolation_segmaps == KeepSizeByResize.SAME_AS_IMAGES:
interpolations_segmaps = np.copy(interpolations)
else:
# TODO This used previously the same seed as the heatmaps part
# leading to the same sampled values. Was that intentional?
# Doesn't look like it should be that way.
interpolations_segmaps = self.interpolation_segmaps.draw_samples(
(nb_images,), random_state=rngs[2]
)
# Note that `interpolations_heatmaps == self.SAME_AS_IMAGES`
# works here only if the datatype of the array is such that it
# may contain strings. It does not work properly for e.g.
# integer arrays and will produce a single bool output, even
# for arrays with more than one entry.
same_as_imgs_idx = [ip == self.SAME_AS_IMAGES
for ip in interpolations_segmaps]
interpolations_segmaps[same_as_imgs_idx] = \
interpolations[same_as_imgs_idx]
return interpolations, interpolations_heatmaps, interpolations_segmaps
def _to_deterministic(self):
aug = self.copy()
aug.children = aug.children.to_deterministic()
aug.deterministic = True
aug.random_state = self.random_state.derive_rng_()
return aug
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.interpolation, self.interpolation_heatmaps]
def get_children_lists(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_children_lists`."""
return [self.children]
def __str__(self):
pattern = (
"%s("
"interpolation=%s, "
"interpolation_heatmaps=%s, "
"name=%s, "
"children=%s, "
"deterministic=%s"
")")
return pattern % (
self.__class__.__name__, self.interpolation,
self.interpolation_heatmaps, self.name, self.children,
self.deterministic)
================================================
FILE: imgaug/augmenters/weather.py
================================================
"""
Augmenters that create weather effects.
List of augmenters:
* :class:`FastSnowyLandscape`
* :class:`CloudLayer`
* :class:`Clouds`
* :class:`Fog`
* :class:`SnowflakesLayer`
* :class:`Snowflakes`
* :class:`RainLayer`
* :class:`Rain`
"""
from __future__ import print_function, division, absolute_import
import numpy as np
import imgaug as ia
from . import meta, arithmetic, blur, contrast, color as colorlib
from .. import parameters as iap
from .. import dtypes as iadt
class FastSnowyLandscape(meta.Augmenter):
"""Convert non-snowy landscapes to snowy ones.
This augmenter expects to get an image that roughly shows a landscape.
This augmenter is based on the method proposed in
https://medium.freecodecamp.org/image-augmentation-make-it-rain-make-it-snow-how-to-modify-a-photo-with-machine-learning-163c0cb3843f?gi=bca4a13e634c
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no (1)
* ``uint32``: no (1)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (1)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) This augmenter is based on a colorspace conversion to HLS.
Hence, only RGB ``uint8`` inputs are sensible.
Parameters
----------
lightness_threshold : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
All pixels with lightness in HLS colorspace that is below this value
will have their lightness increased by `lightness_multiplier`.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the discrete interval ``[a..b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
lightness_multiplier : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Multiplier for pixel's lightness value in HLS colorspace.
Affects all pixels selected via `lightness_threshold`.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the discrete interval ``[a..b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
from_colorspace : str, optional
The source colorspace of the input images.
See :func:`~imgaug.augmenters.color.ChangeColorspace.__init__`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.FastSnowyLandscape(
>>> lightness_threshold=140,
>>> lightness_multiplier=2.5
>>> )
Search for all pixels in the image with a lightness value in HLS
colorspace of less than ``140`` and increase their lightness by a factor
of ``2.5``.
>>> aug = iaa.FastSnowyLandscape(
>>> lightness_threshold=[128, 200],
>>> lightness_multiplier=(1.5, 3.5)
>>> )
Search for all pixels in the image with a lightness value in HLS
colorspace of less than ``128`` or less than ``200`` (one of these
values is picked per image) and multiply their lightness by a factor
of ``x`` with ``x`` being sampled from ``uniform(1.5, 3.5)`` (once per
image).
>>> aug = iaa.FastSnowyLandscape(
>>> lightness_threshold=(100, 255),
>>> lightness_multiplier=(1.0, 4.0)
>>> )
Similar to the previous example, but the lightness threshold is sampled
from ``uniform(100, 255)`` (per image) and the multiplier
from ``uniform(1.0, 4.0)`` (per image). This seems to produce good and
varied results.
"""
def __init__(self, lightness_threshold=(100, 255),
lightness_multiplier=(1.0, 4.0),
from_colorspace=colorlib.CSPACE_RGB,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(FastSnowyLandscape, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.lightness_threshold = iap.handle_continuous_param(
lightness_threshold, "lightness_threshold",
value_range=(0, 255), tuple_to_uniform=True, list_to_choice=True)
self.lightness_multiplier = iap.handle_continuous_param(
lightness_multiplier, "lightness_multiplier",
value_range=(0, None), tuple_to_uniform=True, list_to_choice=True)
self.from_colorspace = from_colorspace
def _draw_samples(self, augmentables, random_state):
nb_augmentables = len(augmentables)
rss = random_state.duplicate(2)
thresh_samples = self.lightness_threshold.draw_samples(
(nb_augmentables,), rss[1])
lmul_samples = self.lightness_multiplier.draw_samples(
(nb_augmentables,), rss[0])
return thresh_samples, lmul_samples
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
thresh_samples, lmul_samples = self._draw_samples(images, random_state)
gen = enumerate(zip(images, thresh_samples, lmul_samples))
for i, (image, thresh, lmul) in gen:
image_hls = colorlib.change_colorspace_(
image, colorlib.CSPACE_HLS, self.from_colorspace)
cvt_dtype = image_hls.dtype
image_hls = image_hls.astype(np.float64)
lightness = image_hls[..., 1]
lightness[lightness < thresh] *= lmul
image_hls = iadt.restore_dtypes_(image_hls, cvt_dtype)
image_rgb = colorlib.change_colorspace_(
image_hls, self.from_colorspace, colorlib.CSPACE_HLS)
batch.images[i] = image_rgb
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.lightness_threshold, self.lightness_multiplier]
# TODO add examples and add these to the overview docs
# TODO add perspective transform to each cloud layer to make them look more
# distant?
# TODO alpha_mean and density overlap - remove one of them
class CloudLayer(meta.Augmenter):
"""Add a single layer of clouds to an image.
**Supported dtypes**:
* ``uint8``: yes; indirectly tested (1)
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: yes; not tested
* ``float32``: yes; not tested
* ``float64``: yes; not tested
* ``float128``: yes; not tested (2)
* ``bool``: no
- (1) Indirectly tested via tests for :class:`Clouds`` and :class:`Fog`
- (2) Note that random values are usually sampled as ``int64`` or
``float64``, which ``float128`` images would exceed. Note also
that random values might have to upscaled, which is done
via :func:`~imgaug.imgaug.imresize_many_images` and has its own
limited dtype support (includes however floats up to ``64bit``).
Parameters
----------
intensity_mean : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Mean intensity of the clouds (i.e. mean color).
Recommended to be in the interval ``[190, 255]``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly
sampled per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
intensity_freq_exponent : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Exponent of the frequency noise used to add fine intensity to the
mean intensity.
Recommended to be in the interval ``[-2.5, -1.5]``.
See :func:`~imgaug.parameters.FrequencyNoise.__init__` for details.
intensity_coarse_scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Standard deviation of the gaussian distribution used to add more
localized intensity to the mean intensity. Sampled in low resolution
space, i.e. affects final intensity on a coarse level.
Recommended to be in the interval ``(0, 10]``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
alpha_min : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Minimum alpha when blending cloud noise with the image.
High values will lead to clouds being "everywhere".
Recommended to usually be at around ``0.0`` for clouds and ``>0`` for
fog.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
alpha_multiplier : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Multiplier for the sampled alpha values. High values will lead to
denser clouds wherever they are visible.
Recommended to be in the interval ``[0.3, 1.0]``.
Note that this parameter currently overlaps with `density_multiplier`,
which is applied a bit later to the alpha mask.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
alpha_size_px_max : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Controls the image size at which the alpha mask is sampled.
Lower values will lead to coarser alpha masks and hence larger
clouds (and empty areas).
See :func:`~imgaug.parameters.FrequencyNoise.__init__` for details.
alpha_freq_exponent : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Exponent of the frequency noise used to sample the alpha mask.
Similarly to `alpha_size_max_px`, lower values will lead to coarser
alpha patterns.
Recommended to be in the interval ``[-4.0, -1.5]``.
See :func:`~imgaug.parameters.FrequencyNoise.__init__` for details.
sparsity : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Exponent applied late to the alpha mask. Lower values will lead to
coarser cloud patterns, higher values to finer patterns.
Recommended to be somewhere around ``1.0``.
Do not deviate far from that value, otherwise the alpha mask might
get weird patterns with sudden fall-offs to zero that look very
unnatural.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
density_multiplier : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Late multiplier for the alpha mask, similar to `alpha_multiplier`.
Set this higher to get "denser" clouds wherever they are visible.
Recommended to be around ``[0.5, 1.5]``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
"""
def __init__(self, intensity_mean, intensity_freq_exponent,
intensity_coarse_scale, alpha_min, alpha_multiplier,
alpha_size_px_max, alpha_freq_exponent, sparsity,
density_multiplier,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(CloudLayer, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.intensity_mean = iap.handle_continuous_param(
intensity_mean, "intensity_mean")
self.intensity_freq_exponent = intensity_freq_exponent
self.intensity_coarse_scale = intensity_coarse_scale
self.alpha_min = iap.handle_continuous_param(alpha_min, "alpha_min")
self.alpha_multiplier = iap.handle_continuous_param(
alpha_multiplier, "alpha_multiplier")
self.alpha_size_px_max = alpha_size_px_max
self.alpha_freq_exponent = alpha_freq_exponent
self.sparsity = iap.handle_continuous_param(sparsity, "sparsity")
self.density_multiplier = iap.handle_continuous_param(
density_multiplier, "density_multiplier")
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
rss = random_state.duplicate(len(images))
for i, (image, rs) in enumerate(zip(images, rss)):
batch.images[i] = self.draw_on_image(image, rs)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.intensity_mean,
self.alpha_min,
self.alpha_multiplier,
self.alpha_size_px_max,
self.alpha_freq_exponent,
self.intensity_freq_exponent,
self.sparsity,
self.density_multiplier,
self.intensity_coarse_scale]
def draw_on_image(self, image, random_state):
iadt.gate_dtypes_strs(
image,
allowed="uint8 float16 float32 float64 float128",
disallowed="bool uint16 uint32 uint64 int8 int16 int32 int64",
augmenter=self
)
alpha, intensity = self.generate_maps(image, random_state)
alpha = alpha[..., np.newaxis]
intensity = intensity[..., np.newaxis]
if image.dtype.kind == "f":
intensity = intensity.astype(image.dtype)
return (1 - alpha) * image + alpha * intensity
intensity = np.clip(intensity, 0, 255)
# TODO use blend_alpha_() here
return np.clip(
(1 - alpha) * image.astype(alpha.dtype)
+ alpha * intensity.astype(alpha.dtype),
0,
255
).astype(np.uint8)
def generate_maps(self, image, random_state):
intensity_mean_sample = self.intensity_mean.draw_sample(random_state)
alpha_min_sample = self.alpha_min.draw_sample(random_state)
alpha_multiplier_sample = \
self.alpha_multiplier.draw_sample(random_state)
alpha_size_px_max = self.alpha_size_px_max
intensity_freq_exponent = self.intensity_freq_exponent
alpha_freq_exponent = self.alpha_freq_exponent
sparsity_sample = self.sparsity.draw_sample(random_state)
density_multiplier_sample = \
self.density_multiplier.draw_sample(random_state)
height, width = image.shape[0:2]
rss_alpha, rss_intensity = random_state.duplicate(2)
intensity_coarse = self._generate_intensity_map_coarse(
height, width, intensity_mean_sample,
iap.Normal(0, scale=self.intensity_coarse_scale),
rss_intensity
)
intensity_fine = self._generate_intensity_map_fine(
height, width, intensity_mean_sample, intensity_freq_exponent,
rss_intensity)
intensity = intensity_coarse + intensity_fine
alpha = self._generate_alpha_mask(
height, width, alpha_min_sample, alpha_multiplier_sample,
alpha_freq_exponent, alpha_size_px_max, sparsity_sample,
density_multiplier_sample, rss_alpha)
return alpha, intensity
@classmethod
def _generate_intensity_map_coarse(cls, height, width, intensity_mean,
intensity_local_offset, random_state):
# TODO (8, 8) might be too simplistic for some image sizes
height_intensity, width_intensity = (8, 8)
intensity = (
intensity_mean
+ intensity_local_offset.draw_samples(
(height_intensity, width_intensity), random_state)
)
intensity = ia.imresize_single_image(
intensity, (height, width), interpolation="cubic")
return intensity
@classmethod
def _generate_intensity_map_fine(cls, height, width, intensity_mean,
exponent, random_state):
intensity_details_generator = iap.FrequencyNoise(
exponent=exponent,
size_px_max=max(height, width, 1), # 1 here for case H, W being 0
upscale_method="cubic"
)
intensity_details = intensity_details_generator.draw_samples(
(height, width), random_state)
return intensity_mean * ((2*intensity_details - 1.0)/5.0)
@classmethod
def _generate_alpha_mask(cls, height, width, alpha_min, alpha_multiplier,
exponent, alpha_size_px_max, sparsity,
density_multiplier, random_state):
alpha_generator = iap.FrequencyNoise(
exponent=exponent,
size_px_max=alpha_size_px_max,
upscale_method="cubic"
)
alpha_local = alpha_generator.draw_samples(
(height, width), random_state)
alpha = alpha_min + (alpha_multiplier * alpha_local)
alpha = (alpha ** sparsity) * density_multiplier
alpha = np.clip(alpha, 0.0, 1.0)
return alpha
# TODO add vertical gradient alpha to have clouds only at skylevel/groundlevel
# TODO add configurable parameters
class Clouds(meta.SomeOf):
"""
Add clouds to images.
This is a wrapper around :class:`~imgaug.augmenters.weather.CloudLayer`.
It executes 1 to 2 layers per image, leading to varying densities and
frequency patterns of clouds.
This augmenter seems to be fairly robust w.r.t. the image size. Tested
with ``96x128``, ``192x256`` and ``960x1280``.
**Supported dtypes**:
* ``uint8``: yes; tested
* ``uint16``: no (1)
* ``uint32``: no (1)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (1)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) Parameters of this augmenter are optimized for the value range
of ``uint8``. While other dtypes may be accepted, they will lead
to images augmented in ways inappropriate for the respective
dtype.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Clouds()
Create an augmenter that adds clouds to images.
"""
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
layers = [
CloudLayer(
intensity_mean=(196, 255),
intensity_freq_exponent=(-2.5, -2.0),
intensity_coarse_scale=10,
alpha_min=0,
alpha_multiplier=(0.25, 0.75),
alpha_size_px_max=(2, 8),
alpha_freq_exponent=(-2.5, -2.0),
sparsity=(0.8, 1.0),
density_multiplier=(0.5, 1.0),
seed=seed,
random_state=random_state,
deterministic=deterministic
),
CloudLayer(
intensity_mean=(196, 255),
intensity_freq_exponent=(-2.0, -1.0),
intensity_coarse_scale=10,
alpha_min=0,
alpha_multiplier=(0.5, 1.0),
alpha_size_px_max=(64, 128),
alpha_freq_exponent=(-2.0, -1.0),
sparsity=(1.0, 1.4),
density_multiplier=(0.8, 1.5),
seed=seed,
random_state=random_state,
deterministic=deterministic
)
]
super(Clouds, self).__init__(
(1, 2),
children=layers,
random_order=False,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO add vertical gradient alpha to have fog only at skylevel/groundlevel
# TODO add configurable parameters
class Fog(CloudLayer):
"""Add fog to images.
This is a wrapper around :class:`~imgaug.augmenters.weather.CloudLayer`.
It executes a single layer per image with a configuration leading to
fairly dense clouds with low-frequency patterns.
This augmenter seems to be fairly robust w.r.t. the image size. Tested
with ``96x128``, ``192x256`` and ``960x1280``.
**Supported dtypes**:
* ``uint8``: yes; tested
* ``uint16``: no (1)
* ``uint32``: no (1)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (1)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) Parameters of this augmenter are optimized for the value range
of ``uint8``. While other dtypes may be accepted, they will lead
to images augmented in ways inappropriate for the respective
dtype.
Parameters
----------
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Fog()
Create an augmenter that adds fog to images.
"""
def __init__(self,
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(Fog, self).__init__(
intensity_mean=(220, 255),
intensity_freq_exponent=(-2.0, -1.5),
intensity_coarse_scale=2,
alpha_min=(0.7, 0.9),
alpha_multiplier=0.3,
alpha_size_px_max=(2, 8),
alpha_freq_exponent=(-4.0, -2.0),
sparsity=0.9,
density_multiplier=(0.4, 0.9),
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# TODO add examples and add these to the overview docs
# TODO snowflakes are all almost 100% white, add some grayish tones and
# maybe color to them
class SnowflakesLayer(meta.Augmenter):
"""Add a single layer of falling snowflakes to images.
**Supported dtypes**:
* ``uint8``: yes; indirectly tested (1)
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
- (1) indirectly tested via tests for :class:`Snowflakes`
Parameters
----------
density : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Density of the snowflake layer, as a probability of each pixel in
low resolution space to be a snowflake.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended to be in the interval ``[0.01, 0.075]``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
density_uniformity : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Size uniformity of the snowflakes. Higher values denote more
similarly sized snowflakes.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended to be around ``0.5``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
flake_size : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Size of the snowflakes. This parameter controls the resolution at
which snowflakes are sampled. Higher values mean that the resolution
is closer to the input image's resolution and hence each sampled
snowflake will be smaller (because of the smaller pixel size).
Valid values are in the interval ``(0.0, 1.0]``.
Recommended values:
* On 96x128 a value of ``(0.1, 0.4)`` worked well.
* On 192x256 a value of ``(0.2, 0.7)`` worked well.
* On 960x1280 a value of ``(0.7, 0.95)`` worked well.
Datatype behaviour:
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
flake_size_uniformity : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Controls the size uniformity of the snowflakes. Higher values mean
that the snowflakes are more similarly sized.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended to be around ``0.5``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
angle : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Angle in degrees of motion blur applied to the snowflakes, where
``0.0`` is motion blur that points straight upwards.
Recommended to be in the interval ``[-30, 30]``.
See also :func:`~imgaug.augmenters.blur.MotionBlur.__init__`.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
speed : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Perceived falling speed of the snowflakes. This parameter controls the
motion blur's kernel size. It follows roughly the form
``kernel_size = image_size * speed``. Hence, values around ``1.0``
denote that the motion blur should "stretch" each snowflake over the
whole image.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended values:
* On 96x128 a value of ``(0.01, 0.05)`` worked well.
* On 192x256 a value of ``(0.007, 0.03)`` worked well.
* On 960x1280 a value of ``(0.001, 0.03)`` worked well.
Datatype behaviour:
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
blur_sigma_fraction : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Standard deviation (as a fraction of the image size) of gaussian blur
applied to the snowflakes.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended to be in the interval ``[0.0001, 0.001]``. May still
require tinkering based on image size.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
blur_sigma_limits : tuple of float, optional
Controls allowed min and max values of `blur_sigma_fraction`
after(!) multiplication with the image size. First value is the
minimum, second value is the maximum. Values outside of that range
will be clipped to be within that range. This prevents extreme
values for very small or large images.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
"""
def __init__(self, density, density_uniformity, flake_size,
flake_size_uniformity, angle, speed, blur_sigma_fraction,
blur_sigma_limits=(0.5, 3.75),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(SnowflakesLayer, self).__init__(
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
self.density = density
self.density_uniformity = iap.handle_continuous_param(
density_uniformity, "density_uniformity", value_range=(0.0, 1.0))
self.flake_size = iap.handle_continuous_param(
flake_size, "flake_size", value_range=(0.0+1e-4, 1.0))
self.flake_size_uniformity = iap.handle_continuous_param(
flake_size_uniformity, "flake_size_uniformity",
value_range=(0.0, 1.0))
self.angle = iap.handle_continuous_param(angle, "angle")
self.speed = iap.handle_continuous_param(
speed, "speed", value_range=(0.0, 1.0))
self.blur_sigma_fraction = iap.handle_continuous_param(
blur_sigma_fraction, "blur_sigma_fraction", value_range=(0.0, 1.0))
# (min, max), same for all images
self.blur_sigma_limits = blur_sigma_limits
# (height, width), same for all images
self.gate_noise_size = (8, 8)
# Added in 0.4.0.
def _augment_batch_(self, batch, random_state, parents, hooks):
if batch.images is None:
return batch
images = batch.images
rss = random_state.duplicate(len(images))
for i, (image, rs) in enumerate(zip(images, rss)):
batch.images[i] = self.draw_on_image(image, rs)
return batch
def get_parameters(self):
"""See :func:`~imgaug.augmenters.meta.Augmenter.get_parameters`."""
return [self.density,
self.density_uniformity,
self.flake_size,
self.flake_size_uniformity,
self.angle,
self.speed,
self.blur_sigma_fraction,
self.blur_sigma_limits,
self.gate_noise_size]
def draw_on_image(self, image, random_state):
assert image.ndim == 3, (
"Expected input image to be three-dimensional, "
"got %d dimensions." % (image.ndim,))
assert image.shape[2] in [1, 3], (
"Expected to get image with a channel axis of size 1 or 3, "
"got %d (shape: %s)" % (image.shape[2], image.shape))
rss = random_state.duplicate(2)
flake_size_sample = self.flake_size.draw_sample(random_state)
flake_size_uniformity_sample = self.flake_size_uniformity.draw_sample(
random_state)
angle_sample = self.angle.draw_sample(random_state)
speed_sample = self.speed.draw_sample(random_state)
blur_sigma_fraction_sample = self.blur_sigma_fraction.draw_sample(
random_state)
height, width, nb_channels = image.shape
downscale_factor = np.clip(1.0 - flake_size_sample, 0.001, 1.0)
height_down = max(1, int(height*downscale_factor))
width_down = max(1, int(width*downscale_factor))
noise = self._generate_noise(
height_down,
width_down,
self.density,
rss[0]
)
# gate the sampled noise via noise in range [0.0, 1.0]
# this leads to less flakes in some areas of the image and more in
# other areas
gate_noise = iap.Beta(1.0, 1.0 - self.density_uniformity)
noise = self._gate(noise, gate_noise, self.gate_noise_size, rss[1])
noise = ia.imresize_single_image(noise, (height, width),
interpolation="cubic")
# apply a bit of gaussian blur and then motion blur according to
# angle and speed
sigma = max(height, width) * blur_sigma_fraction_sample
sigma = np.clip(sigma,
self.blur_sigma_limits[0], self.blur_sigma_limits[1])
noise_small_blur = self._blur(noise, sigma)
noise_small_blur = self._motion_blur(noise_small_blur,
angle=angle_sample,
speed=speed_sample,
random_state=random_state)
noise_small_blur_rgb = self._postprocess_noise(
noise_small_blur, flake_size_uniformity_sample, nb_channels)
return self._blend(image, speed_sample, noise_small_blur_rgb)
@classmethod
def _generate_noise(cls, height, width, density, random_state):
noise = arithmetic.Salt(p=density, random_state=random_state)
return noise.augment_image(np.zeros((height, width), dtype=np.uint8))
@classmethod
def _gate(cls, noise, gate_noise, gate_size, random_state):
# the beta distribution here has most of its weight around 1.0 and
# will only rarely sample values around 0.0 the average of the
# sampled values seems to be at around 0.6-0.75
gate_noise = gate_noise.draw_samples(gate_size, random_state)
gate_noise_up = ia.imresize_single_image(gate_noise, noise.shape[0:2],
interpolation="cubic")
gate_noise_up = np.clip(gate_noise_up, 0.0, 1.0)
return np.clip(
noise.astype(np.float32) * gate_noise_up, 0, 255
).astype(np.uint8)
@classmethod
def _blur(cls, noise, sigma):
return blur.blur_gaussian_(noise, sigma=sigma)
@classmethod
def _motion_blur(cls, noise, angle, speed, random_state):
size = max(noise.shape[0:2])
k = int(speed * size)
if k <= 1:
return noise
# we use max(k, 3) here because MotionBlur errors for anything less
# than 3
blurer = blur.MotionBlur(
k=max(k, 3), angle=angle, direction=1.0, random_state=random_state)
return blurer.augment_image(noise)
# Added in 0.4.0.
@classmethod
def _postprocess_noise(cls, noise_small_blur,
flake_size_uniformity_sample, nb_channels):
# use contrast adjustment of noise to make the flake size a bit less
# uniform then readjust the noise values to make them more visible
# again
gain = 1.0 + 2*(1 - flake_size_uniformity_sample)
gain_adj = 1.0 + 5*(1 - flake_size_uniformity_sample)
noise_small_blur = contrast.GammaContrast(gain).augment_image(
noise_small_blur)
noise_small_blur = noise_small_blur.astype(np.float32) * gain_adj
noise_small_blur_rgb = np.tile(
noise_small_blur[..., np.newaxis], (1, 1, nb_channels))
return noise_small_blur_rgb
# Added in 0.4.0.
@classmethod
def _blend(cls, image, speed_sample, noise_small_blur_rgb):
# blend:
# sum for a bit of glowy, hardly visible flakes
# max for the main flakes
image_f32 = image.astype(np.float32)
image_f32 = cls._blend_by_sum(
image_f32, (0.1 + 20*speed_sample) * noise_small_blur_rgb)
image_f32 = cls._blend_by_max(
image_f32, (1.0 + 20*speed_sample) * noise_small_blur_rgb)
return image_f32
# TODO replace this by a function from module blend.py
@classmethod
def _blend_by_sum(cls, image_f32, noise_small_blur_rgb):
image_f32 = image_f32 + noise_small_blur_rgb
return np.clip(image_f32, 0, 255).astype(np.uint8)
# TODO replace this by a function from module blend.py
@classmethod
def _blend_by_max(cls, image_f32, noise_small_blur_rgb):
image_f32 = np.maximum(image_f32, noise_small_blur_rgb)
return np.clip(image_f32, 0, 255).astype(np.uint8)
class Snowflakes(meta.SomeOf):
"""Add falling snowflakes to images.
This is a wrapper around
:class:`~imgaug.augmenters.weather.SnowflakesLayer`. It executes 1 to 3
layers per image.
**Supported dtypes**:
* ``uint8``: yes; tested
* ``uint16``: no (1)
* ``uint32``: no (1)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (1)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) Parameters of this augmenter are optimized for the value range
of ``uint8``. While other dtypes may be accepted, they will lead
to images augmented in ways inappropriate for the respective
dtype.
Parameters
----------
density : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Density of the snowflake layer, as a probability of each pixel in
low resolution space to be a snowflake.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended to be in the interval ``[0.01, 0.075]``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
density_uniformity : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Size uniformity of the snowflakes. Higher values denote more
similarly sized snowflakes.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended to be around ``0.5``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
flake_size : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Size of the snowflakes. This parameter controls the resolution at
which snowflakes are sampled. Higher values mean that the resolution
is closer to the input image's resolution and hence each sampled
snowflake will be smaller (because of the smaller pixel size).
Valid values are in the interval ``(0.0, 1.0]``.
Recommended values:
* On ``96x128`` a value of ``(0.1, 0.4)`` worked well.
* On ``192x256`` a value of ``(0.2, 0.7)`` worked well.
* On ``960x1280`` a value of ``(0.7, 0.95)`` worked well.
Datatype behaviour:
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
flake_size_uniformity : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Controls the size uniformity of the snowflakes. Higher values mean
that the snowflakes are more similarly sized.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended to be around ``0.5``.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
angle : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Angle in degrees of motion blur applied to the snowflakes, where
``0.0`` is motion blur that points straight upwards.
Recommended to be in the interval ``[-30, 30]``.
See also :func:`~imgaug.augmenters.blur.MotionBlur.__init__`.
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
speed : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Perceived falling speed of the snowflakes. This parameter controls the
motion blur's kernel size. It follows roughly the form
``kernel_size = image_size * speed``. Hence, values around ``1.0``
denote that the motion blur should "stretch" each snowflake over
the whole image.
Valid values are in the interval ``[0.0, 1.0]``.
Recommended values:
* On ``96x128`` a value of ``(0.01, 0.05)`` worked well.
* On ``192x256`` a value of ``(0.007, 0.03)`` worked well.
* On ``960x1280`` a value of ``(0.001, 0.03)`` worked well.
Datatype behaviour:
* If a ``number``, then that value will always be used.
* If a ``tuple`` ``(a, b)``, then a value will be uniformly sampled
per image from the interval ``[a, b]``.
* If a ``list``, then a random value will be sampled from that
``list`` per image.
* If a ``StochasticParameter``, then a value will be sampled
per image from that parameter.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Snowflakes(flake_size=(0.1, 0.4), speed=(0.01, 0.05))
Add snowflakes to small images (around ``96x128``).
>>> aug = iaa.Snowflakes(flake_size=(0.2, 0.7), speed=(0.007, 0.03))
Add snowflakes to medium-sized images (around ``192x256``).
>>> aug = iaa.Snowflakes(flake_size=(0.7, 0.95), speed=(0.001, 0.03))
Add snowflakes to large images (around ``960x1280``).
"""
def __init__(self, density=(0.005, 0.075), density_uniformity=(0.3, 0.9),
flake_size=(0.2, 0.7), flake_size_uniformity=(0.4, 0.8),
angle=(-30, 30), speed=(0.007, 0.03),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
layer = SnowflakesLayer(
density=density,
density_uniformity=density_uniformity,
flake_size=flake_size,
flake_size_uniformity=flake_size_uniformity,
angle=angle,
speed=speed,
blur_sigma_fraction=(0.0001, 0.001),
seed=seed,
random_state=random_state,
deterministic=deterministic
)
super(Snowflakes, self).__init__(
(1, 3),
children=[layer.deepcopy() for _ in range(3)],
random_order=False,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
class RainLayer(SnowflakesLayer):
"""Add a single layer of falling raindrops to images.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; indirectly tested (1)
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
- (1) indirectly tested via tests for :class:`Rain`
Parameters
----------
density : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Same as in :class:`~imgaug.augmenters.weather.SnowflakesLayer`.
density_uniformity : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Same as in :class:`~imgaug.augmenters.weather.SnowflakesLayer`.
drop_size : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Same as `flake_size` in
:class:`~imgaug.augmenters.weather.SnowflakesLayer`.
drop_size_uniformity : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Same as `flake_size_uniformity` in
:class:`~imgaug.augmenters.weather.SnowflakesLayer`.
angle : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Same as in :class:`~imgaug.augmenters.weather.SnowflakesLayer`.
speed : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Same as in :class:`~imgaug.augmenters.weather.SnowflakesLayer`.
blur_sigma_fraction : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Same as in :class:`~imgaug.augmenters.weather.SnowflakesLayer`.
blur_sigma_limits : tuple of float, optional
Same as in :class:`~imgaug.augmenters.weather.SnowflakesLayer`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
"""
# Added in 0.4.0.
def __init__(self, density, density_uniformity, drop_size,
drop_size_uniformity, angle, speed, blur_sigma_fraction,
blur_sigma_limits=(0.5, 3.75),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
super(RainLayer, self).__init__(
density, density_uniformity, drop_size,
drop_size_uniformity, angle, speed, blur_sigma_fraction,
blur_sigma_limits=blur_sigma_limits,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
# Added in 0.4.0.
@classmethod
def _blur(cls, noise, sigma):
return noise
# Added in 0.4.0.
@classmethod
def _postprocess_noise(cls, noise_small_blur,
flake_size_uniformity_sample, nb_channels):
noise_small_blur_rgb = np.tile(
noise_small_blur[..., np.newaxis], (1, 1, nb_channels))
return noise_small_blur_rgb
# Added in 0.4.0.
@classmethod
def _blend(cls, image, speed_sample, noise_small_blur_rgb):
# We set the mean color based on the noise here. That's a pseudo-random
# approach that saves us from adding the random state as a parameter.
# Note that the sum of noise_small_blur_rgb can be 0 when at least one
# image axis size is 0.
noise_sum = np.sum(noise_small_blur_rgb.flat[0:1000])
noise_sum = noise_sum if noise_sum > 0 else 1
drop_mean_color = 110 + (240 - 110) % noise_sum
noise_small_blur_rgb = noise_small_blur_rgb / 255.0
# The 1.3 multiplier increases the visibility of drops a bit.
noise_small_blur_rgb = np.clip(1.3 * noise_small_blur_rgb, 0, 1.0)
image_f32 = image.astype(np.float32)
image_f32 = (
(1 - noise_small_blur_rgb) * image_f32
+ noise_small_blur_rgb * drop_mean_color
)
return np.clip(image_f32, 0, 255).astype(np.uint8)
class Rain(meta.SomeOf):
"""Add falling snowflakes to images.
This is a wrapper around
:class:`~imgaug.augmenters.weather.RainLayer`. It executes 1 to 3
layers per image.
.. note::
This augmenter currently seems to work best for medium-sized images
around ``192x256``. For smaller images, you may want to increase the
`speed` value to e.g. ``(0.1, 0.3)``, otherwise the drops tend to
look like snowflakes. For larger images, you may want to increase
the `drop_size` to e.g. ``(0.10, 0.20)``.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; tested
* ``uint16``: no (1)
* ``uint32``: no (1)
* ``uint64``: no (1)
* ``int8``: no (1)
* ``int16``: no (1)
* ``int32``: no (1)
* ``int64``: no (1)
* ``float16``: no (1)
* ``float32``: no (1)
* ``float64``: no (1)
* ``float128``: no (1)
* ``bool``: no (1)
- (1) Parameters of this augmenter are optimized for the value range
of ``uint8``. While other dtypes may be accepted, they will lead
to images augmented in ways inappropriate for the respective
dtype.
Parameters
----------
drop_size : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
See :class:`~imgaug.augmenters.weather.RainLayer`.
speed : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
See :class:`~imgaug.augmenters.weather.RainLayer`.
seed : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
name : None or str, optional
See :func:`~imgaug.augmenters.meta.Augmenter.__init__`.
random_state : None or int or imgaug.random.RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState, optional
Old name for parameter `seed`.
Its usage will not yet cause a deprecation warning,
but it is still recommended to use `seed` now.
Outdated since 0.4.0.
deterministic : bool, optional
Deprecated since 0.4.0.
See method ``to_deterministic()`` for an alternative and for
details about what the "deterministic mode" actually does.
Examples
--------
>>> import imgaug.augmenters as iaa
>>> aug = iaa.Rain(speed=(0.1, 0.3))
Add rain to small images (around ``96x128``).
>>> aug = iaa.Rain()
Add rain to medium sized images (around ``192x256``).
>>> aug = iaa.Rain(drop_size=(0.10, 0.20))
Add rain to large images (around ``960x1280``).
"""
# Added in 0.4.0.
def __init__(self, nb_iterations=(1, 3),
drop_size=(0.01, 0.02),
speed=(0.04, 0.20),
seed=None, name=None,
random_state="deprecated", deterministic="deprecated"):
layer = RainLayer(
density=(0.03, 0.14),
density_uniformity=(0.8, 1.0),
drop_size=drop_size,
drop_size_uniformity=(0.2, 0.5),
angle=(-15, 15),
speed=speed,
blur_sigma_fraction=(0.001, 0.001),
seed=seed,
random_state=random_state,
deterministic=deterministic
)
super(Rain, self).__init__(
nb_iterations,
children=[layer.deepcopy() for _ in range(3)],
random_order=False,
seed=seed, name=name,
random_state=random_state, deterministic=deterministic)
================================================
FILE: imgaug/data.py
================================================
"""Functions to generate example data, e.g. example images or segmaps.
Added in 0.5.0.
"""
from __future__ import print_function, division, absolute_import
import os
import json
import imageio
import numpy as np
# filepath to the quokka image, its annotations and depth map
# Added in 0.5.0.
_FILE_DIR = os.path.dirname(os.path.abspath(__file__))
# Added in 0.5.0.
_QUOKKA_FP = os.path.join(_FILE_DIR, "quokka.jpg")
# Added in 0.5.0.
_QUOKKA_ANNOTATIONS_FP = os.path.join(_FILE_DIR, "quokka_annotations.json")
# Added in 0.5.0.
_QUOKKA_DEPTH_MAP_HALFRES_FP = os.path.join(
_FILE_DIR, "quokka_depth_map_halfres.png")
def _quokka_normalize_extract(extract):
"""Generate a normalized rectangle for the standard quokka image.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
extract : 'square' or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage
Unnormalized representation of the image subarea to be extracted.
* If ``str`` ``square``, then a squared area
``(x: 0 to max 643, y: 0 to max 643)`` will be extracted from
the image.
* If a ``tuple``, then expected to contain four ``number`` s
denoting ``(x1, y1, x2, y2)``.
* If a :class:`~imgaug.augmentables.bbs.BoundingBox`, then that
bounding box's area will be extracted from the image.
* If a :class:`~imgaug.augmentables.bbs.BoundingBoxesOnImage`,
then expected to contain exactly one bounding box and a shape
matching the full image dimensions (i.e. ``(643, 960, *)``).
Then the one bounding box will be used similar to
``BoundingBox`` above.
Returns
-------
imgaug.augmentables.bbs.BoundingBox
Normalized representation of the area to extract from the standard
quokka image.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
if extract == "square":
bb = BoundingBox(x1=0, y1=0, x2=643, y2=643)
elif isinstance(extract, tuple) and len(extract) == 4:
bb = BoundingBox(x1=extract[0], y1=extract[1],
x2=extract[2], y2=extract[3])
elif isinstance(extract, BoundingBox):
bb = extract
elif isinstance(extract, BoundingBoxesOnImage):
assert len(extract.bounding_boxes) == 1, (
"Provided BoundingBoxesOnImage instance may currently only "
"contain a single bounding box.")
assert extract.shape[0:2] == (643, 960), (
"Expected BoundingBoxesOnImage instance on an image of shape "
"(643, 960, ?). Got shape %s." % (extract.shape,))
bb = extract.bounding_boxes[0]
else:
raise Exception(
"Expected 'square' or tuple of four entries or BoundingBox or "
"BoundingBoxesOnImage for parameter 'extract', "
"got %s." % (type(extract),)
)
return bb
# TODO is this the same as the project functions in augmentables?
def _compute_resized_shape(from_shape, to_shape):
"""Compute the intended new shape of an image-like array after resizing.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
from_shape : tuple or ndarray
Old shape of the array. Usually expected to be a ``tuple`` of form
``(H, W)`` or ``(H, W, C)`` or alternatively an array with two or
three dimensions.
to_shape : None or tuple of ints or tuple of floats or int or float or ndarray
New shape of the array.
* If ``None``, then `from_shape` will be used as the new shape.
* If an ``int`` ``V``, then the new shape will be ``(V, V, [C])``,
where ``C`` will be added if it is part of `from_shape`.
* If a ``float`` ``V``, then the new shape will be
``(H*V, W*V, [C])``, where ``H`` and ``W`` are the old
height/width.
* If a ``tuple`` ``(H', W', [C'])`` of ints, then ``H'`` and ``W'``
will be used as the new height and width.
* If a ``tuple`` ``(H', W', [C'])`` of floats (except ``C``), then
``H'`` and ``W'`` will be used as the new height and width.
* If a numpy array, then the array's shape will be used.
Returns
-------
tuple of int
New shape.
"""
from . import imgaug as ia
if ia.is_np_array(from_shape):
from_shape = from_shape.shape
if ia.is_np_array(to_shape):
to_shape = to_shape.shape
to_shape_computed = list(from_shape)
if to_shape is None:
pass
elif isinstance(to_shape, tuple):
assert len(from_shape) in [2, 3]
assert len(to_shape) in [2, 3]
if len(from_shape) == 3 and len(to_shape) == 3:
assert from_shape[2] == to_shape[2]
elif len(to_shape) == 3:
to_shape_computed.append(to_shape[2])
is_to_s_valid_values = all(
[v is None or ia.is_single_number(v) for v in to_shape[0:2]])
assert is_to_s_valid_values, (
"Expected the first two entries in to_shape to be None or "
"numbers, got types %s." % (
str([type(v) for v in to_shape[0:2]]),))
for i, from_shape_i in enumerate(from_shape[0:2]):
if to_shape[i] is None:
to_shape_computed[i] = from_shape_i
elif ia.is_single_integer(to_shape[i]):
to_shape_computed[i] = to_shape[i]
else: # float
to_shape_computed[i] = int(np.round(from_shape_i * to_shape[i]))
elif ia.is_single_integer(to_shape) or ia.is_single_float(to_shape):
to_shape_computed = _compute_resized_shape(
from_shape, (to_shape, to_shape))
else:
raise Exception(
"Expected to_shape to be None or ndarray or tuple of floats or "
"tuple of ints or single int or single float, "
"got %s." % (type(to_shape),))
return tuple(to_shape_computed)
def quokka(size=None, extract=None):
"""Return an image of a quokka as a numpy array.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
size : None or float or tuple of int, optional
Size of the output image. Input into
:func:`~imgaug.imgaug.imresize_single_image`. Usually expected to be a
``tuple`` ``(H, W)``, where ``H`` is the desired height and ``W`` is
the width. If ``None``, then the image will not be resized.
extract : None or 'square' or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage
Subarea of the quokka image to extract:
* If ``None``, then the whole image will be used.
* If ``str`` ``square``, then a squared area
``(x: 0 to max 643, y: 0 to max 643)`` will be extracted from
the image.
* If a ``tuple``, then expected to contain four ``number`` s
denoting ``(x1, y1, x2, y2)``.
* If a :class:`~imgaug.augmentables.bbs.BoundingBox`, then that
bounding box's area will be extracted from the image.
* If a :class:`~imgaug.augmentables.bbs.BoundingBoxesOnImage`,
then expected to contain exactly one bounding box and a shape
matching the full image dimensions (i.e. ``(643, 960, *)``).
Then the one bounding box will be used similar to
``BoundingBox`` above.
Returns
-------
(H,W,3) ndarray
The image array of dtype ``uint8``.
"""
from . import imgaug as ia
img = imageio.imread(_QUOKKA_FP, pilmode="RGB")
if extract is not None:
bb = _quokka_normalize_extract(extract)
img = bb.extract_from_image(img)
if size is not None:
shape_resized = _compute_resized_shape(img.shape, size)
img = ia.imresize_single_image(img, shape_resized[0:2])
return img
def quokka_square(size=None):
"""Return an (square) image of a quokka as a numpy array.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
size : None or float or tuple of int, optional
Size of the output image. Input into
:func:`~imgaug.imgaug.imresize_single_image`. Usually expected to be a
``tuple`` ``(H, W)``, where ``H`` is the desired height and ``W`` is
the width. If ``None``, then the image will not be resized.
Returns
-------
(H,W,3) ndarray
The image array of dtype ``uint8``.
"""
return quokka(size=size, extract="square")
def quokka_heatmap(size=None, extract=None):
"""Return a heatmap (here: depth map) for the standard example quokka image.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
size : None or float or tuple of int, optional
See :func:`~imgaug.imgaug.quokka`.
extract : None or 'square' or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage
See :func:`~imgaug.imgaug.quokka`.
Returns
-------
imgaug.augmentables.heatmaps.HeatmapsOnImage
Depth map as an heatmap object. Values close to ``0.0`` denote objects
that are close to the camera. Values close to ``1.0`` denote objects
that are furthest away (among all shown objects).
"""
# TODO get rid of this deferred import
from . import imgaug as ia
from imgaug.augmentables.heatmaps import HeatmapsOnImage
img = imageio.imread(_QUOKKA_DEPTH_MAP_HALFRES_FP, pilmode="RGB")
img = ia.imresize_single_image(img, (643, 960), interpolation="cubic")
if extract is not None:
bb = _quokka_normalize_extract(extract)
img = bb.extract_from_image(img)
if size is None:
size = img.shape[0:2]
shape_resized = _compute_resized_shape(img.shape, size)
img = ia.imresize_single_image(img, shape_resized[0:2])
img_0to1 = img[..., 0] # depth map was saved as 3-channel RGB
img_0to1 = img_0to1.astype(np.float32) / 255.0
img_0to1 = 1 - img_0to1 # depth map was saved as 0 being furthest away
return HeatmapsOnImage(img_0to1, shape=img_0to1.shape[0:2] + (3,))
def quokka_segmentation_map(size=None, extract=None):
"""Return a segmentation map for the standard example quokka image.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
size : None or float or tuple of int, optional
See :func:`~imgaug.imgaug.quokka`.
extract : None or 'square' or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage
See :func:`~imgaug.imgaug.quokka`.
Returns
-------
imgaug.augmentables.segmaps.SegmentationMapsOnImage
Segmentation map object.
"""
# pylint: disable=invalid-name
import skimage.draw
# TODO get rid of this deferred import
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
with open(_QUOKKA_ANNOTATIONS_FP, "r") as f:
json_dict = json.load(f)
xx = []
yy = []
for kp_dict in json_dict["polygons"][0]["keypoints"]:
x = kp_dict["x"]
y = kp_dict["y"]
xx.append(x)
yy.append(y)
img_seg = np.zeros((643, 960, 1), dtype=np.int32)
rr, cc = skimage.draw.polygon(
np.array(yy), np.array(xx), shape=img_seg.shape)
img_seg[rr, cc, 0] = 1
if extract is not None:
bb = _quokka_normalize_extract(extract)
img_seg = bb.extract_from_image(img_seg)
segmap = SegmentationMapsOnImage(img_seg, shape=img_seg.shape[0:2] + (3,))
if size is not None:
shape_resized = _compute_resized_shape(img_seg.shape, size)
segmap = segmap.resize(shape_resized[0:2])
segmap.shape = tuple(shape_resized[0:2]) + (3,)
return segmap
def quokka_keypoints(size=None, extract=None):
"""Return example keypoints on the standard example quokke image.
The keypoints cover the eyes, ears, nose and paws.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
size : None or float or tuple of int or tuple of float, optional
Size of the output image on which the keypoints are placed. If
``None``, then the keypoints are not projected to any new size
(positions on the original image are used). ``float`` s lead to
relative size changes, ``int`` s to absolute sizes in pixels.
extract : None or 'square' or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage
Subarea to extract from the image. See :func:`~imgaug.imgaug.quokka`.
Returns
-------
imgaug.augmentables.kps.KeypointsOnImage
Example keypoints on the quokka image.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
left, top = 0, 0
if extract is not None:
bb_extract = _quokka_normalize_extract(extract)
left = bb_extract.x1
top = bb_extract.y1
with open(_QUOKKA_ANNOTATIONS_FP, "r") as f:
json_dict = json.load(f)
keypoints = []
for kp_dict in json_dict["keypoints"]:
keypoints.append(Keypoint(x=kp_dict["x"] - left, y=kp_dict["y"] - top))
if extract is not None:
shape = (bb_extract.height, bb_extract.width, 3)
else:
shape = (643, 960, 3)
kpsoi = KeypointsOnImage(keypoints, shape=shape)
if size is not None:
shape_resized = _compute_resized_shape(shape, size)
kpsoi = kpsoi.on(shape_resized)
return kpsoi
def quokka_bounding_boxes(size=None, extract=None):
"""Return example bounding boxes on the standard example quokke image.
Currently only a single bounding box is returned that covers the quokka.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
size : None or float or tuple of int or tuple of float, optional
Size of the output image on which the BBs are placed. If ``None``, then
the BBs are not projected to any new size (positions on the original
image are used). ``float`` s lead to relative size changes, ``int`` s
to absolute sizes in pixels.
extract : None or 'square' or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage
Subarea to extract from the image. See :func:`~imgaug.imgaug.quokka`.
Returns
-------
imgaug.augmentables.bbs.BoundingBoxesOnImage
Example BBs on the quokka image.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
left, top = 0, 0
if extract is not None:
bb_extract = _quokka_normalize_extract(extract)
left = bb_extract.x1
top = bb_extract.y1
with open(_QUOKKA_ANNOTATIONS_FP, "r") as f:
json_dict = json.load(f)
bbs = []
for bb_dict in json_dict["bounding_boxes"]:
bbs.append(
BoundingBox(
x1=bb_dict["x1"] - left,
y1=bb_dict["y1"] - top,
x2=bb_dict["x2"] - left,
y2=bb_dict["y2"] - top
)
)
if extract is not None:
shape = (bb_extract.height, bb_extract.width, 3)
else:
shape = (643, 960, 3)
bbsoi = BoundingBoxesOnImage(bbs, shape=shape)
if size is not None:
shape_resized = _compute_resized_shape(shape, size)
bbsoi = bbsoi.on(shape_resized)
return bbsoi
def quokka_polygons(size=None, extract=None):
"""
Returns example polygons on the standard example quokke image.
The result contains one polygon, covering the quokka's outline.
Added in 0.5.0. (Moved from ``imgaug.imgaug``.)
Parameters
----------
size : None or float or tuple of int or tuple of float, optional
Size of the output image on which the polygons are placed. If ``None``,
then the polygons are not projected to any new size (positions on the
original image are used). ``float`` s lead to relative size changes,
``int`` s to absolute sizes in pixels.
extract : None or 'square' or tuple of number or imgaug.augmentables.bbs.BoundingBox or imgaug.augmentables.bbs.BoundingBoxesOnImage
Subarea to extract from the image. See :func:`~imgaug.imgaug.quokka`.
Returns
-------
imgaug.augmentables.polys.PolygonsOnImage
Example polygons on the quokka image.
"""
# TODO get rid of this deferred import
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
left, top = 0, 0
if extract is not None:
bb_extract = _quokka_normalize_extract(extract)
left = bb_extract.x1
top = bb_extract.y1
with open(_QUOKKA_ANNOTATIONS_FP, "r") as f:
json_dict = json.load(f)
polygons = []
for poly_json in json_dict["polygons"]:
polygons.append(
Polygon([(point["x"] - left, point["y"] - top)
for point in poly_json["keypoints"]])
)
if extract is not None:
shape = (bb_extract.height, bb_extract.width, 3)
else:
shape = (643, 960, 3)
psoi = PolygonsOnImage(polygons, shape=shape)
if size is not None:
shape_resized = _compute_resized_shape(shape, size)
psoi = psoi.on(shape_resized)
return psoi
================================================
FILE: imgaug/dtypes.py
================================================
"""Functions to interact/analyze with numpy dtypes."""
from __future__ import print_function, division, absolute_import
import numpy as np
import six.moves as sm
import imgaug as ia
KIND_TO_DTYPES = {
"i": ["int8", "int16", "int32", "int64"],
"u": ["uint8", "uint16", "uint32", "uint64"],
"b": ["bool"],
"f": ["float16", "float32", "float64", "float128"]
}
# Added in 0.5.0.
_DTYPE_STR_TO_DTYPES_CACHE = dict()
_UINT8_DTYPE = np.dtype("uint8") # Added in 0.5.0.
_UINT16_DTYPE = np.dtype("uint16") # Added in 0.5.0.
_UINT32_DTYPE = np.dtype("uint32") # Added in 0.5.0.
_UINT64_DTYPE = np.dtype("uint64") # Added in 0.5.0.
_INT8_DTYPE = np.dtype("int8") # Added in 0.5.0.
_INT16_DTYPE = np.dtype("int16") # Added in 0.5.0.
_INT32_DTYPE = np.dtype("int32") # Added in 0.5.0.
_INT64_DTYPE = np.dtype("int64") # Added in 0.5.0.
_FLOAT16_DTYPE = np.dtype("float16") # Added in 0.5.0.
_FLOAT32_DTYPE = np.dtype("float32") # Added in 0.5.0.
_FLOAT64_DTYPE = np.dtype("float64") # Added in 0.5.0.
_BOOL_DTYPE = np.dtype("bool") # Added in 0.5.0.
# Added in 0.5.0.
try:
_FLOAT128_DTYPE = np.dtype("float128")
except TypeError:
_FLOAT128_DTYPE = None
# Added in 0.5.0.
_DTYPE_NAME_TO_DTYPE = {
"uint8": _UINT8_DTYPE,
"uint16": _UINT16_DTYPE,
"uint32": _UINT32_DTYPE,
"uint64": _UINT64_DTYPE,
"int8": _INT8_DTYPE,
"int16": _INT16_DTYPE,
"int32": _INT32_DTYPE,
"int64": _INT64_DTYPE,
"float16": _FLOAT16_DTYPE,
"float32": _FLOAT32_DTYPE,
"float64": _FLOAT64_DTYPE,
"float128": _FLOAT128_DTYPE,
"bool": _BOOL_DTYPE
}
def normalize_dtypes(dtypes):
if not isinstance(dtypes, list):
return [normalize_dtype(dtypes)]
return [normalize_dtype(dtype) for dtype in dtypes]
def normalize_dtype(dtype):
assert not isinstance(dtype, list), (
"Expected a single dtype-like, got a list instead.")
return (
dtype.dtype
if ia.is_np_array(dtype) or ia.is_np_scalar(dtype)
else np.dtype(dtype)
)
def change_dtype_(arr, dtype, clip=True, round=True):
# pylint: disable=redefined-builtin
assert ia.is_np_array(arr), (
"Expected array as input, got type %s." % (type(arr),))
dtype = normalize_dtype(dtype)
if arr.dtype.name == dtype.name:
return arr
if round and arr.dtype.kind == "f" and dtype.kind in ["u", "i", "b"]:
arr = np.round(arr)
if clip:
min_value, _, max_value = get_value_range_of_dtype(dtype)
arr = clip_(arr, min_value, max_value)
return arr.astype(dtype, copy=False)
def change_dtypes_(images, dtypes, clip=True, round=True):
# pylint: disable=redefined-builtin
if ia.is_np_array(images):
if ia.is_iterable(dtypes):
dtypes = normalize_dtypes(dtypes)
n_distinct_dtypes = len({dt.name for dt in dtypes})
assert len(dtypes) == len(images), (
"If an iterable of dtypes is provided to "
"change_dtypes_(), it must contain as many dtypes as "
"there are images. Got %d dtypes and %d images." % (
len(dtypes), len(images))
)
assert n_distinct_dtypes == 1, (
"If an image array is provided to change_dtypes_(), the "
"provided 'dtypes' argument must either be a single dtype "
"or an iterable of N times the *same* dtype for N images. "
"Got %d distinct dtypes." % (n_distinct_dtypes,)
)
dtype = dtypes[0]
else:
dtype = normalize_dtype(dtypes)
result = change_dtype_(images, dtype, clip=clip, round=round)
elif ia.is_iterable(images):
dtypes = (
[normalize_dtype(dtypes)] * len(images)
if not isinstance(dtypes, list)
else normalize_dtypes(dtypes)
)
assert len(images) == len(dtypes), (
"Expected the provided images and dtypes to match, but got "
"iterables of size %d (images) %d (dtypes)." % (
len(images), len(dtypes)))
result = images
for i, (image, dtype) in enumerate(zip(images, dtypes)):
assert ia.is_np_array(image), (
"Expected each image to be an ndarray, got type %s "
"instead." % (type(image),))
result[i] = change_dtype_(image, dtype, clip=clip, round=round)
else:
raise Exception("Expected numpy array or iterable of numpy arrays, "
"got type '%s'." % (type(images),))
return result
# TODO replace this everywhere in the library with change_dtypes_
# TODO mark as deprecated
def restore_dtypes_(images, dtypes, clip=True, round=True):
# pylint: disable=redefined-builtin
return change_dtypes_(images, dtypes, clip=clip, round=round)
def copy_dtypes_for_restore(images, force_list=False):
if ia.is_np_array(images):
if force_list:
return [images.dtype for _ in sm.xrange(len(images))]
return images.dtype
return [image.dtype for image in images]
def increase_itemsize_of_dtype(dtype, factor):
dtype = normalize_dtype(dtype)
assert ia.is_single_integer(factor), (
"Expected 'factor' to be an integer, got type %s instead." % (
type(factor),))
# int8 -> int64 = factor 8
# uint8 -> uint64 = factor 8
# float16 -> float128 = factor 8
assert factor in [1, 2, 4, 8], (
"The itemsize may only be increased any of the following factors: "
"1, 2, 4 or 8. Got factor %d." % (factor,))
assert dtype.kind != "b", "Cannot increase the itemsize of boolean."
dt_high_name = "%s%d" % (dtype.kind, dtype.itemsize * factor)
try:
dt_high = np.dtype(dt_high_name)
return dt_high
except TypeError:
raise TypeError(
"Unable to create a numpy dtype matching the name '%s'. "
"This error was caused when trying to find a dtype "
"that increases the itemsize of dtype '%s' by a factor of %d."
"This error can be avoided by choosing arrays with lower "
"resolution dtypes as inputs, e.g. by reducing "
"float32 to float16." % (
dt_high_name,
dtype.name,
factor
)
)
def get_minimal_dtype(arrays, increase_itemsize_factor=1):
assert isinstance(arrays, list), (
"Expected a list of arrays or dtypes, got type %s." % (type(arrays),))
assert len(arrays) > 0, (
"Cannot estimate minimal dtype of an empty iterable.")
input_dts = normalize_dtypes(arrays)
# This loop construct handles (1) list of a single dtype, (2) list of two
# dtypes and (3) list of 3+ dtypes. Note that promote_dtypes() always
# expects exactly two dtypes.
promoted_dt = input_dts[0]
input_dts = input_dts[1:]
while len(input_dts) >= 1:
promoted_dt = np.promote_types(promoted_dt, input_dts[0])
input_dts = input_dts[1:]
if increase_itemsize_factor > 1:
assert isinstance(promoted_dt, np.dtype), (
"Expected numpy.dtype output from numpy.promote_dtypes, got type "
"%s." % (type(promoted_dt),))
return increase_itemsize_of_dtype(promoted_dt,
increase_itemsize_factor)
return promoted_dt
# TODO rename to: promote_arrays_to_minimal_dtype_
def promote_array_dtypes_(arrays, dtypes=None, increase_itemsize_factor=1):
if dtypes is None:
dtypes = normalize_dtypes(arrays)
elif not isinstance(dtypes, list):
dtypes = [dtypes]
dtype = get_minimal_dtype(dtypes,
increase_itemsize_factor=increase_itemsize_factor)
return change_dtypes_(arrays, dtype, clip=False, round=False)
def increase_array_resolutions_(arrays, factor):
dts = normalize_dtypes(arrays)
dts = [increase_itemsize_of_dtype(dt, factor) for dt in dts]
return change_dtypes_(arrays, dts, round=False, clip=False)
def get_value_range_of_dtype(dtype):
dtype = normalize_dtype(dtype)
if dtype.kind == "f":
finfo = np.finfo(dtype)
return finfo.min, 0.0, finfo.max
if dtype.kind == "u":
iinfo = np.iinfo(dtype)
return iinfo.min, iinfo.min + 0.5 * iinfo.max, iinfo.max
if dtype.kind == "i":
iinfo = np.iinfo(dtype)
return iinfo.min, -0.5, iinfo.max
if dtype.kind == "b":
return 0, None, 1
raise Exception("Cannot estimate value range of dtype '%s' "
"(type: %s)" % (str(dtype), type(dtype)))
# TODO call this function wherever data is clipped
def clip_(array, min_value, max_value):
# uint64 is disallowed, because numpy's clip seems to convert it to float64
# int64 is disallowed, because numpy's clip converts it to float64 since
# 1.17
# TODO find the cause for that
gate_dtypes_strs(
{array.dtype},
allowed="bool uint8 uint16 uint32 int8 int16 int32 "
"float16 float32 float64 float128",
disallowed="uint64 int64"
)
# If the min of the input value range is above the allowed min, we do not
# have to clip to the allowed min as we cannot exceed it anyways.
# Analogous for max. In fact, we must not clip then to min/max as that can
# lead to errors in numpy's clip. E.g.
# >>> arr = np.zeros((1,), dtype=np.int32)
# >>> np.clip(arr, 0, np.iinfo(np.dtype("uint32")).max)
# will return
# array([-1], dtype=int32)
# (observed on numpy version 1.15.2).
min_value_arrdt, _, max_value_arrdt = get_value_range_of_dtype(array.dtype)
if min_value is not None and min_value < min_value_arrdt:
min_value = None
if max_value is not None and max_value_arrdt < max_value:
max_value = None
if min_value is not None or max_value is not None:
# for scalar arrays, i.e. with shape = (), "out" is not a valid
# argument
if len(array.shape) == 0:
array = np.clip(array, min_value, max_value)
elif array.dtype.name == "int32":
# Since 1.17 (before maybe too?), numpy.clip() turns int32
# to float64. float64 should cover the whole value range of int32,
# so the dtype is not rejected here.
# TODO Verify this. Is rounding needed before conversion?
array = np.clip(array, min_value, max_value).astype(array.dtype)
else:
array = np.clip(array, min_value, max_value, out=array)
return array
def clip_to_dtype_value_range_(array, dtype, validate=True,
validate_values=None):
dtype = normalize_dtype(dtype)
min_value, _, max_value = get_value_range_of_dtype(dtype)
if validate:
array_val = array
if ia.is_single_integer(validate):
assert validate >= 1, (
"If 'validate' is an integer, it must have a value >=1, "
"got %d instead." % (validate,))
assert validate_values is None, (
"If 'validate' is an integer, 'validate_values' must be "
"None. Got type %s instead." % (type(validate_values),))
array_val = array.flat[0:validate]
if validate_values is not None:
min_value_found, max_value_found = validate_values
else:
min_value_found = np.min(array_val)
max_value_found = np.max(array_val)
assert min_value <= min_value_found <= max_value, (
"Minimum value of array is outside of allowed value range (%.4f "
"vs %.4f to %.4f)." % (min_value_found, min_value, max_value))
assert min_value <= max_value_found <= max_value, (
"Maximum value of array is outside of allowed value range (%.4f "
"vs %.4f to %.4f)." % (max_value_found, min_value, max_value))
return clip_(array, min_value, max_value)
def gate_dtypes_strs(dtypes, allowed, disallowed, augmenter=None):
"""Verify that input dtypes match allowed/disallowed dtype strings.
Added in 0.5.0.
Parameters
----------
dtypes : numpy.ndarray or iterable of numpy.ndarray or iterable of numpy.dtype
One or more input dtypes to verify.
allowed : str
Names of one or more allowed dtypes, separated by single spaces.
disallowed : str
Names of disallowed dtypes, separated by single spaces.
Must not intersect with allowed dtypes.
augmenter : None or imgaug.augmenters.meta.Augmenter, optional
If the gating happens for an augmenter, it should be provided
here. This information will be used to improve output error
messages and warnings.
"""
allowed, disallowed = _convert_gate_dtype_strs_to_types(
allowed, disallowed
)
return _gate_dtypes(dtypes, allowed, disallowed, augmenter=augmenter)
# Added in 0.5.0.
def _convert_gate_dtype_strs_to_types(allowed, disallowed):
allowed_types = _convert_dtype_strs_to_types(allowed)
disallowed_types = _convert_dtype_strs_to_types(disallowed)
intersection = allowed_types.intersection(disallowed_types)
nb_overlapping = len(intersection)
assert nb_overlapping == 0, (
"Expected 'allowed' and 'disallowed' dtypes to not contain the same "
"dtypes, but %d appeared in both arguments. Got allowed: %s, "
"disallowed: %s, intersection: %s" % (
nb_overlapping,
allowed,
disallowed,
intersection
)
)
return allowed_types, disallowed_types
# Added in 0.5.0.
def _convert_dtype_strs_to_types_cached(dtypes):
dtypes_parsed = _DTYPE_STR_TO_DTYPES_CACHE.get(dtypes, None)
if dtypes_parsed is None:
dtypes_parsed = _convert_dtype_strs_to_types_cached(dtypes)
_DTYPE_STR_TO_DTYPES_CACHE[dtypes] = dtypes_parsed
return dtypes_parsed
# Added in 0.5.0.
def _convert_dtype_strs_to_types(dtypes):
result = set()
for name in dtypes.split(" "):
name = name.strip()
if name:
dtype = _DTYPE_NAME_TO_DTYPE[name]
# this if ignores float128 if it is not available on the user
# system
if dtype is not None:
result.add(dtype)
return result
# Deprecated since 0.5.0.
@ia.deprecated("imgaug.dtypes.gate_dtypes_strs")
def gate_dtypes(dtypes, allowed, disallowed, augmenter=None):
def _cvt(dts):
normalized = set()
if not isinstance(dts, list):
dts = [dts]
for dtype in dts:
try:
dtype = normalize_dtype(dtype)
normalized.add(dtype)
except TypeError:
pass
return normalized
dtypes_norm = _cvt(dtypes)
allowed_norm = _cvt(allowed)
disallowed_norm = _cvt(disallowed)
return _gate_dtypes(
dtypes_norm, allowed_norm, disallowed_norm, augmenter=augmenter
)
def _gate_dtypes(dtypes, allowed, disallowed, augmenter=None):
"""Verify that input dtypes are among allowed and not disallowed dtypes.
Added in 0.5.0.
Parameters
----------
dtypes : numpy.ndarray or iterable of numpy.ndarray or iterable of numpy.dtype
One or more input dtypes to verify.
Must not be a dtype function (like ``np.int64``), only a proper
dtype (like ``np.dtype("int64")``). For performance reasons this is
not validated.
allowed : set of numpy.dtype
One or more allowed dtypes.
disallowed : None or set of numpy.dtype
Any number of disallowed dtypes. Should not intersect with allowed
dtypes.
augmenter : None or imgaug.augmenters.meta.Augmenter, optional
If the gating happens for an augmenter, it should be provided
here. This information will be used to improve output error
messages and warnings.
"""
if isinstance(dtypes, np.ndarray) or ia.is_np_scalar(dtypes):
dtypes = set([dtypes.dtype])
elif isinstance(dtypes, list):
dtypes = {arr.dtype for arr in dtypes}
dts_not_explicitly_allowed = dtypes - allowed
all_allowed = (not dts_not_explicitly_allowed)
if all_allowed:
return
if disallowed is None:
disallowed = set()
dts_explicitly_disallowed = dts_not_explicitly_allowed.intersection(
disallowed
)
dts_undefined = dts_not_explicitly_allowed - disallowed
if dts_explicitly_disallowed:
for dtype in dts_explicitly_disallowed:
if augmenter is None:
raise ValueError(
"Got dtype '%s', which is a forbidden dtype (%s)." % (
np.dtype(dtype).name,
_dtype_names_to_string(disallowed)
))
raise ValueError(
"Got dtype '%s' in augmenter '%s' (class '%s'), which "
"is a forbidden dtype (%s)." % (
np.dtype(dtype).name,
augmenter.name,
augmenter.__class__.__name__,
_dtype_names_to_string(disallowed),
))
if dts_undefined:
for dtype in dts_undefined:
if augmenter is None:
ia.warn(
"Got dtype '%s', which was neither explicitly allowed "
"(%s), nor explicitly disallowed (%s). Generated "
"outputs may contain errors." % (
dtype.name,
_dtype_names_to_string(allowed),
_dtype_names_to_string(disallowed),
)
)
else:
ia.warn(
"Got dtype '%s' in augmenter '%s' (class '%s'), which was "
"neither explicitly allowed (%s), nor explicitly "
"disallowed (%s). Generated outputs may contain "
"errors." % (
dtype.name,
augmenter.name,
augmenter.__class__.__name__,
_dtype_names_to_string(allowed),
_dtype_names_to_string(disallowed),
)
)
# Added in 0.5.0.
def _dtype_names_to_string(dtypes):
if isinstance(dtypes, set):
dtypes = list(sorted(dtypes))
return ", ".join([np.dtype(dt).name for dt in dtypes])
def allow_only_uint8(dtypes, augmenter=None):
"""Verify that input dtypes are uint8.
Added in 0.5.0.
Parameters
----------
dtypes : numpy.ndarray or iterable of numpy.ndarray or iterable of numpy.dtype
One or more input dtypes to verify.
augmenter : None or imgaug.augmenters.meta.Augmenter, optional
If the gating happens for an augmenter, it should be provided
here. This information will be used to improve output error
messages and warnings.
"""
return gate_dtypes_strs(
dtypes,
allowed="uint8",
disallowed="uint16 uint32 uint64 "
"int8 int16 int32 int64 "
"float16 float32 float64 float128 "
"bool",
augmenter=augmenter
)
================================================
FILE: imgaug/external/README.md
================================================
This directory is intended for libraries that are required, but not added to the dependencies.
That is e.g. the case to avoid bloating up the dependencies for small things
or because the libraries had to be somehow modified.
* `opensimplex.py`: https://github.com/lmas/opensimplex
* `poly_point_isect.py`: https://github.com/ideasman42/isect_segments-bentley_ottmann
* `poly_point_isect_py2py3.py`: Same as `poly_point_isect.py`, but modified to also be compatible with python 2.7.
================================================
FILE: imgaug/external/__init__.py
================================================
================================================
FILE: imgaug/external/opensimplex.py
================================================
"""
This is a copy of the OpenSimplex library,
based on commit d861cb290531ad15825f21dc4cc35c5d4f407259 from 20.07.2017.
"""
# Based on: https://gist.github.com/KdotJPG/b1270127455a94ac5d19
import sys
from ctypes import c_long
from math import floor as _floor
if sys.version_info[0] < 3:
def floor(num):
return int(_floor(num))
else:
floor = _floor
STRETCH_CONSTANT_2D = -0.211324865405187 # (1/Math.sqrt(2+1)-1)/2
SQUISH_CONSTANT_2D = 0.366025403784439 # (Math.sqrt(2+1)-1)/2
STRETCH_CONSTANT_3D = -1.0 / 6 # (1/Math.sqrt(3+1)-1)/3
SQUISH_CONSTANT_3D = 1.0 / 3 # (Math.sqrt(3+1)-1)/3
STRETCH_CONSTANT_4D = -0.138196601125011 # (1/Math.sqrt(4+1)-1)/4
SQUISH_CONSTANT_4D = 0.309016994374947 # (Math.sqrt(4+1)-1)/4
NORM_CONSTANT_2D = 47
NORM_CONSTANT_3D = 103
NORM_CONSTANT_4D = 30
DEFAULT_SEED = 0
# Gradients for 2D. They approximate the directions to the
# vertices of an octagon from the center.
GRADIENTS_2D = (
5, 2, 2, 5,
-5, 2, -2, 5,
5, -2, 2, -5,
-5, -2, -2, -5,
)
# Gradients for 3D. They approximate the directions to the
# vertices of a rhombicuboctahedron from the center, skewed so
# that the triangular and square facets can be inscribed inside
# circles of the same radius.
GRADIENTS_3D = (
-11, 4, 4, -4, 11, 4, -4, 4, 11,
11, 4, 4, 4, 11, 4, 4, 4, 11,
-11, -4, 4, -4, -11, 4, -4, -4, 11,
11, -4, 4, 4, -11, 4, 4, -4, 11,
-11, 4, -4, -4, 11, -4, -4, 4, -11,
11, 4, -4, 4, 11, -4, 4, 4, -11,
-11, -4, -4, -4, -11, -4, -4, -4, -11,
11, -4, -4, 4, -11, -4, 4, -4, -11,
)
# Gradients for 4D. They approximate the directions to the
# vertices of a disprismatotesseractihexadecachoron from the center,
# skewed so that the tetrahedral and cubic facets can be inscribed inside
# spheres of the same radius.
GRADIENTS_4D = (
3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3, 1, 1, 1, 1, 3,
-3, 1, 1, 1, -1, 3, 1, 1, -1, 1, 3, 1, -1, 1, 1, 3,
3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3, 1, 1, -1, 1, 3,
-3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3,
3, 1, -1, 1, 1, 3, -1, 1, 1, 1, -3, 1, 1, 1, -1, 3,
-3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3, 1, -1, 1, -1, 3,
3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3, 1, 1, -1, -1, 3,
-3, -1, -1, 1, -1, -3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3,
3, 1, 1, -1, 1, 3, 1, -1, 1, 1, 3, -1, 1, 1, 1, -3,
-3, 1, 1, -1, -1, 3, 1, -1, -1, 1, 3, -1, -1, 1, 1, -3,
3, -1, 1, -1, 1, -3, 1, -1, 1, -1, 3, -1, 1, -1, 1, -3,
-3, -1, 1, -1, -1, -3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3,
3, 1, -1, -1, 1, 3, -1, -1, 1, 1, -3, -1, 1, 1, -1, -3,
-3, 1, -1, -1, -1, 3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3,
3, -1, -1, -1, 1, -3, -1, -1, 1, -1, -3, -1, 1, -1, -1, -3,
-3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3, -1, -1, -1, -1, -3,
)
def overflow(x):
# Since normal python ints and longs can be quite humongous we have to use
# this hack to make them be able to overflow
return c_long(x).value
class OpenSimplex(object):
"""
OpenSimplex n-dimensional gradient noise functions.
"""
def __init__(self, seed=DEFAULT_SEED):
"""
Initiate the class and generate permutation arrays from a seed number.
"""
# Initializes the class using a permutation array generated from a 64-bit seed.
# Generates a proper permutation (i.e. doesn't merely perform N
# successive pair swaps on a base array)
perm = self._perm = [0] * 256 # Have to zero fill so we can properly loop over it later
perm_grad_index_3D = self._perm_grad_index_3D = [0] * 256
source = [i for i in range(0, 256)]
seed = overflow(seed * 6364136223846793005 + 1442695040888963407)
seed = overflow(seed * 6364136223846793005 + 1442695040888963407)
seed = overflow(seed * 6364136223846793005 + 1442695040888963407)
for i in range(255, -1, -1):
seed = overflow(seed * 6364136223846793005 + 1442695040888963407)
r = int((seed + 31) % (i + 1))
if r < 0:
r += i + 1
perm[i] = source[r]
perm_grad_index_3D[i] = int((perm[i] % (len(GRADIENTS_3D) / 3)) * 3)
source[r] = source[i]
def _extrapolate2d(self, xsb, ysb, dx, dy):
perm = self._perm
index = perm[(perm[xsb & 0xFF] + ysb) & 0xFF] & 0x0E
g1, g2 = GRADIENTS_2D[index:index + 2]
return g1 * dx + g2 * dy
def _extrapolate3d(self, xsb, ysb, zsb, dx, dy, dz):
perm = self._perm
index = self._perm_grad_index_3D[
(perm[(perm[xsb & 0xFF] + ysb) & 0xFF] + zsb) & 0xFF
]
g1, g2, g3 = GRADIENTS_3D[index:index + 3]
return g1 * dx + g2 * dy + g3 * dz
def _extrapolate4d(self, xsb, ysb, zsb, wsb, dx, dy, dz, dw):
perm = self._perm
index = perm[(
perm[(
perm[(perm[xsb & 0xFF] + ysb) & 0xFF] + zsb
) & 0xFF] + wsb
) & 0xFF] & 0xFC
g1, g2, g3, g4 = GRADIENTS_4D[index:index + 4]
return g1 * dx + g2 * dy + g3 * dz + g4 * dw
def noise2d(self, x, y):
"""
Generate 2D OpenSimplex noise from X,Y coordinates.
"""
# Place input coordinates onto grid.
stretch_offset = (x + y) * STRETCH_CONSTANT_2D
xs = x + stretch_offset
ys = y + stretch_offset
# Floor to get grid coordinates of rhombus (stretched square) super-cell origin.
xsb = floor(xs)
ysb = floor(ys)
# Skew out to get actual coordinates of rhombus origin. We'll need these later.
squish_offset = (xsb + ysb) * SQUISH_CONSTANT_2D
xb = xsb + squish_offset
yb = ysb + squish_offset
# Compute grid coordinates relative to rhombus origin.
xins = xs - xsb
yins = ys - ysb
# Sum those together to get a value that determines which region we're in.
in_sum = xins + yins
# Positions relative to origin point.
dx0 = x - xb
dy0 = y - yb
value = 0
# Contribution (1,0)
dx1 = dx0 - 1 - SQUISH_CONSTANT_2D
dy1 = dy0 - 0 - SQUISH_CONSTANT_2D
attn1 = 2 - dx1 * dx1 - dy1 * dy1
extrapolate = self._extrapolate2d
if attn1 > 0:
attn1 *= attn1
value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, dx1, dy1)
# Contribution (0,1)
dx2 = dx0 - 0 - SQUISH_CONSTANT_2D
dy2 = dy0 - 1 - SQUISH_CONSTANT_2D
attn2 = 2 - dx2 * dx2 - dy2 * dy2
if attn2 > 0:
attn2 *= attn2
value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, dx2, dy2)
if in_sum <= 1: # We're inside the triangle (2-Simplex) at (0,0)
zins = 1 - in_sum
if zins > xins or zins > yins: # (0,0) is one of the closest two triangular vertices
if xins > yins:
xsv_ext = xsb + 1
ysv_ext = ysb - 1
dx_ext = dx0 - 1
dy_ext = dy0 + 1
else:
xsv_ext = xsb - 1
ysv_ext = ysb + 1
dx_ext = dx0 + 1
dy_ext = dy0 - 1
else: # (1,0) and (0,1) are the closest two vertices.
xsv_ext = xsb + 1
ysv_ext = ysb + 1
dx_ext = dx0 - 1 - 2 * SQUISH_CONSTANT_2D
dy_ext = dy0 - 1 - 2 * SQUISH_CONSTANT_2D
else: # We're inside the triangle (2-Simplex) at (1,1)
zins = 2 - in_sum
if zins < xins or zins < yins: # (0,0) is one of the closest two triangular vertices
if xins > yins:
xsv_ext = xsb + 2
ysv_ext = ysb + 0
dx_ext = dx0 - 2 - 2 * SQUISH_CONSTANT_2D
dy_ext = dy0 + 0 - 2 * SQUISH_CONSTANT_2D
else:
xsv_ext = xsb + 0
ysv_ext = ysb + 2
dx_ext = dx0 + 0 - 2 * SQUISH_CONSTANT_2D
dy_ext = dy0 - 2 - 2 * SQUISH_CONSTANT_2D
else: # (1,0) and (0,1) are the closest two vertices.
dx_ext = dx0
dy_ext = dy0
xsv_ext = xsb
ysv_ext = ysb
xsb += 1
ysb += 1
dx0 = dx0 - 1 - 2 * SQUISH_CONSTANT_2D
dy0 = dy0 - 1 - 2 * SQUISH_CONSTANT_2D
# Contribution (0,0) or (1,1)
attn0 = 2 - dx0 * dx0 - dy0 * dy0
if attn0 > 0:
attn0 *= attn0
value += attn0 * attn0 * extrapolate(xsb, ysb, dx0, dy0)
# Extra Vertex
attn_ext = 2 - dx_ext * dx_ext - dy_ext * dy_ext
if attn_ext > 0:
attn_ext *= attn_ext
value += attn_ext * attn_ext * extrapolate(xsv_ext, ysv_ext, dx_ext, dy_ext)
return value / NORM_CONSTANT_2D
def noise3d(self, x, y, z):
"""
Generate 3D OpenSimplex noise from X,Y,Z coordinates.
"""
# Place input coordinates on simplectic honeycomb.
stretch_offset = (x + y + z) * STRETCH_CONSTANT_3D
xs = x + stretch_offset
ys = y + stretch_offset
zs = z + stretch_offset
# Floor to get simplectic honeycomb coordinates of rhombohedron (stretched cube) super-cell origin.
xsb = floor(xs)
ysb = floor(ys)
zsb = floor(zs)
# Skew out to get actual coordinates of rhombohedron origin. We'll need these later.
squish_offset = (xsb + ysb + zsb) * SQUISH_CONSTANT_3D
xb = xsb + squish_offset
yb = ysb + squish_offset
zb = zsb + squish_offset
# Compute simplectic honeycomb coordinates relative to rhombohedral origin.
xins = xs - xsb
yins = ys - ysb
zins = zs - zsb
# Sum those together to get a value that determines which region we're in.
in_sum = xins + yins + zins
# Positions relative to origin point.
dx0 = x - xb
dy0 = y - yb
dz0 = z - zb
value = 0
extrapolate = self._extrapolate3d
if in_sum <= 1: # We're inside the tetrahedron (3-Simplex) at (0,0,0)
# Determine which two of (0,0,1), (0,1,0), (1,0,0) are closest.
a_point = 0x01
a_score = xins
b_point = 0x02
b_score = yins
if a_score >= b_score and zins > b_score:
b_score = zins
b_point = 0x04
elif a_score < b_score and zins > a_score:
a_score = zins
a_point = 0x04
# Now we determine the two lattice points not part of the tetrahedron that may contribute.
# This depends on the closest two tetrahedral vertices, including (0,0,0)
wins = 1 - in_sum
if wins > a_score or wins > b_score: # (0,0,0) is one of the closest two tetrahedral vertices.
c = b_point if (b_score > a_score) else a_point # Our other closest vertex is the closest out of a and b.
if (c & 0x01) == 0:
xsv_ext0 = xsb - 1
xsv_ext1 = xsb
dx_ext0 = dx0 + 1
dx_ext1 = dx0
else:
xsv_ext0 = xsv_ext1 = xsb + 1
dx_ext0 = dx_ext1 = dx0 - 1
if (c & 0x02) == 0:
ysv_ext0 = ysv_ext1 = ysb
dy_ext0 = dy_ext1 = dy0
if (c & 0x01) == 0:
ysv_ext1 -= 1
dy_ext1 += 1
else:
ysv_ext0 -= 1
dy_ext0 += 1
else:
ysv_ext0 = ysv_ext1 = ysb + 1
dy_ext0 = dy_ext1 = dy0 - 1
if (c & 0x04) == 0:
zsv_ext0 = zsb
zsv_ext1 = zsb - 1
dz_ext0 = dz0
dz_ext1 = dz0 + 1
else:
zsv_ext0 = zsv_ext1 = zsb + 1
dz_ext0 = dz_ext1 = dz0 - 1
else: # (0,0,0) is not one of the closest two tetrahedral vertices.
c = (a_point | b_point) # Our two extra vertices are determined by the closest two.
if (c & 0x01) == 0:
xsv_ext0 = xsb
xsv_ext1 = xsb - 1
dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_3D
dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_3D
else:
xsv_ext0 = xsv_ext1 = xsb + 1
dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D
dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D
if (c & 0x02) == 0:
ysv_ext0 = ysb
ysv_ext1 = ysb - 1
dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_3D
dy_ext1 = dy0 + 1 - SQUISH_CONSTANT_3D
else:
ysv_ext0 = ysv_ext1 = ysb + 1
dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D
if (c & 0x04) == 0:
zsv_ext0 = zsb
zsv_ext1 = zsb - 1
dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_3D
dz_ext1 = dz0 + 1 - SQUISH_CONSTANT_3D
else:
zsv_ext0 = zsv_ext1 = zsb + 1
dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D
# Contribution (0,0,0)
attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0
if attn0 > 0:
attn0 *= attn0
value += attn0 * attn0 * extrapolate(xsb + 0, ysb + 0, zsb + 0, dx0, dy0, dz0)
# Contribution (1,0,0)
dx1 = dx0 - 1 - SQUISH_CONSTANT_3D
dy1 = dy0 - 0 - SQUISH_CONSTANT_3D
dz1 = dz0 - 0 - SQUISH_CONSTANT_3D
attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1
if attn1 > 0:
attn1 *= attn1
value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, dx1, dy1, dz1)
# Contribution (0,1,0)
dx2 = dx0 - 0 - SQUISH_CONSTANT_3D
dy2 = dy0 - 1 - SQUISH_CONSTANT_3D
dz2 = dz1
attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2
if attn2 > 0:
attn2 *= attn2
value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, dx2, dy2, dz2)
# Contribution (0,0,1)
dx3 = dx2
dy3 = dy1
dz3 = dz0 - 1 - SQUISH_CONSTANT_3D
attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3
if attn3 > 0:
attn3 *= attn3
value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, dx3, dy3, dz3)
elif in_sum >= 2: # We're inside the tetrahedron (3-Simplex) at (1,1,1)
# Determine which two tetrahedral vertices are the closest, out of (1,1,0), (1,0,1), (0,1,1) but not (1,1,1).
a_point = 0x06
a_score = xins
b_point = 0x05
b_score = yins
if a_score <= b_score and zins < b_score:
b_score = zins
b_point = 0x03
elif a_score > b_score and zins < a_score:
a_score = zins
a_point = 0x03
# Now we determine the two lattice points not part of the tetrahedron that may contribute.
# This depends on the closest two tetrahedral vertices, including (1,1,1)
wins = 3 - in_sum
if wins < a_score or wins < b_score: # (1,1,1) is one of the closest two tetrahedral vertices.
c = b_point if (b_score < a_score) else a_point # Our other closest vertex is the closest out of a and b.
if (c & 0x01) != 0:
xsv_ext0 = xsb + 2
xsv_ext1 = xsb + 1
dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_3D
dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D
else:
xsv_ext0 = xsv_ext1 = xsb
dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_3D
if (c & 0x02) != 0:
ysv_ext0 = ysv_ext1 = ysb + 1
dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D
if (c & 0x01) != 0:
ysv_ext1 += 1
dy_ext1 -= 1
else:
ysv_ext0 += 1
dy_ext0 -= 1
else:
ysv_ext0 = ysv_ext1 = ysb
dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_3D
if (c & 0x04) != 0:
zsv_ext0 = zsb + 1
zsv_ext1 = zsb + 2
dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 2 - 3 * SQUISH_CONSTANT_3D
else:
zsv_ext0 = zsv_ext1 = zsb
dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_3D
else: # (1,1,1) is not one of the closest two tetrahedral vertices.
c = (a_point & b_point) # Our two extra vertices are determined by the closest two.
if (c & 0x01) != 0:
xsv_ext0 = xsb + 1
xsv_ext1 = xsb + 2
dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D
dx_ext1 = dx0 - 2 - 2 * SQUISH_CONSTANT_3D
else:
xsv_ext0 = xsv_ext1 = xsb
dx_ext0 = dx0 - SQUISH_CONSTANT_3D
dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D
if (c & 0x02) != 0:
ysv_ext0 = ysb + 1
ysv_ext1 = ysb + 2
dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 2 - 2 * SQUISH_CONSTANT_3D
else:
ysv_ext0 = ysv_ext1 = ysb
dy_ext0 = dy0 - SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D
if (c & 0x04) != 0:
zsv_ext0 = zsb + 1
zsv_ext1 = zsb + 2
dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 2 - 2 * SQUISH_CONSTANT_3D
else:
zsv_ext0 = zsv_ext1 = zsb
dz_ext0 = dz0 - SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D
# Contribution (1,1,0)
dx3 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D
dy3 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D
dz3 = dz0 - 0 - 2 * SQUISH_CONSTANT_3D
attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3
if attn3 > 0:
attn3 *= attn3
value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, dx3, dy3, dz3)
# Contribution (1,0,1)
dx2 = dx3
dy2 = dy0 - 0 - 2 * SQUISH_CONSTANT_3D
dz2 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D
attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2
if attn2 > 0:
attn2 *= attn2
value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, dx2, dy2, dz2)
# Contribution (0,1,1)
dx1 = dx0 - 0 - 2 * SQUISH_CONSTANT_3D
dy1 = dy3
dz1 = dz2
attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1
if attn1 > 0:
attn1 *= attn1
value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, dx1, dy1, dz1)
# Contribution (1,1,1)
dx0 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D
dy0 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D
dz0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D
attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0
if attn0 > 0:
attn0 *= attn0
value += attn0 * attn0 * extrapolate(xsb + 1, ysb + 1, zsb + 1, dx0, dy0, dz0)
else: # We're inside the octahedron (Rectified 3-Simplex) in between.
# Decide between point (0,0,1) and (1,1,0) as closest
p1 = xins + yins
if p1 > 1:
a_score = p1 - 1
a_point = 0x03
a_is_further_side = True
else:
a_score = 1 - p1
a_point = 0x04
a_is_further_side = False
# Decide between point (0,1,0) and (1,0,1) as closest
p2 = xins + zins
if p2 > 1:
b_score = p2 - 1
b_point = 0x05
b_is_further_side = True
else:
b_score = 1 - p2
b_point = 0x02
b_is_further_side = False
# The closest out of the two (1,0,0) and (0,1,1) will replace the furthest out of the two decided above, if closer.
p3 = yins + zins
if p3 > 1:
score = p3 - 1
if a_score <= b_score and a_score < score:
a_point = 0x06
a_is_further_side = True
elif a_score > b_score and b_score < score:
b_point = 0x06
b_is_further_side = True
else:
score = 1 - p3
if a_score <= b_score and a_score < score:
a_point = 0x01
a_is_further_side = False
elif a_score > b_score and b_score < score:
b_point = 0x01
b_is_further_side = False
# Where each of the two closest points are determines how the extra two vertices are calculated.
if a_is_further_side == b_is_further_side:
if a_is_further_side: # Both closest points on (1,1,1) side
# One of the two extra points is (1,1,1)
dx_ext0 = dx0 - 1 - 3 * SQUISH_CONSTANT_3D
dy_ext0 = dy0 - 1 - 3 * SQUISH_CONSTANT_3D
dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_3D
xsv_ext0 = xsb + 1
ysv_ext0 = ysb + 1
zsv_ext0 = zsb + 1
# Other extra point is based on the shared axis.
c = (a_point & b_point)
if (c & 0x01) != 0:
dx_ext1 = dx0 - 2 - 2 * SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D
xsv_ext1 = xsb + 2
ysv_ext1 = ysb
zsv_ext1 = zsb
elif (c & 0x02) != 0:
dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 2 - 2 * SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D
xsv_ext1 = xsb
ysv_ext1 = ysb + 2
zsv_ext1 = zsb
else:
dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 2 - 2 * SQUISH_CONSTANT_3D
xsv_ext1 = xsb
ysv_ext1 = ysb
zsv_ext1 = zsb + 2
else:# Both closest points on (0,0,0) side
# One of the two extra points is (0,0,0)
dx_ext0 = dx0
dy_ext0 = dy0
dz_ext0 = dz0
xsv_ext0 = xsb
ysv_ext0 = ysb
zsv_ext0 = zsb
# Other extra point is based on the omitted axis.
c = (a_point | b_point)
if (c & 0x01) == 0:
dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D
xsv_ext1 = xsb - 1
ysv_ext1 = ysb + 1
zsv_ext1 = zsb + 1
elif (c & 0x02) == 0:
dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D
dy_ext1 = dy0 + 1 - SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_3D
xsv_ext1 = xsb + 1
ysv_ext1 = ysb - 1
zsv_ext1 = zsb + 1
else:
dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_3D
dz_ext1 = dz0 + 1 - SQUISH_CONSTANT_3D
xsv_ext1 = xsb + 1
ysv_ext1 = ysb + 1
zsv_ext1 = zsb - 1
else: # One point on (0,0,0) side, one point on (1,1,1) side
if a_is_further_side:
c1 = a_point
c2 = b_point
else:
c1 = b_point
c2 = a_point
# One contribution is a _permutation of (1,1,-1)
if (c1 & 0x01) == 0:
dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_3D
dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D
dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D
xsv_ext0 = xsb - 1
ysv_ext0 = ysb + 1
zsv_ext0 = zsb + 1
elif (c1 & 0x02) == 0:
dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D
dy_ext0 = dy0 + 1 - SQUISH_CONSTANT_3D
dz_ext0 = dz0 - 1 - SQUISH_CONSTANT_3D
xsv_ext0 = xsb + 1
ysv_ext0 = ysb - 1
zsv_ext0 = zsb + 1
else:
dx_ext0 = dx0 - 1 - SQUISH_CONSTANT_3D
dy_ext0 = dy0 - 1 - SQUISH_CONSTANT_3D
dz_ext0 = dz0 + 1 - SQUISH_CONSTANT_3D
xsv_ext0 = xsb + 1
ysv_ext0 = ysb + 1
zsv_ext0 = zsb - 1
# One contribution is a _permutation of (0,0,2)
dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_3D
dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_3D
dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_3D
xsv_ext1 = xsb
ysv_ext1 = ysb
zsv_ext1 = zsb
if (c2 & 0x01) != 0:
dx_ext1 -= 2
xsv_ext1 += 2
elif (c2 & 0x02) != 0:
dy_ext1 -= 2
ysv_ext1 += 2
else:
dz_ext1 -= 2
zsv_ext1 += 2
# Contribution (1,0,0)
dx1 = dx0 - 1 - SQUISH_CONSTANT_3D
dy1 = dy0 - 0 - SQUISH_CONSTANT_3D
dz1 = dz0 - 0 - SQUISH_CONSTANT_3D
attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1
if attn1 > 0:
attn1 *= attn1
value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, dx1, dy1, dz1)
# Contribution (0,1,0)
dx2 = dx0 - 0 - SQUISH_CONSTANT_3D
dy2 = dy0 - 1 - SQUISH_CONSTANT_3D
dz2 = dz1
attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2
if attn2 > 0:
attn2 *= attn2
value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, dx2, dy2, dz2)
# Contribution (0,0,1)
dx3 = dx2
dy3 = dy1
dz3 = dz0 - 1 - SQUISH_CONSTANT_3D
attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3
if attn3 > 0:
attn3 *= attn3
value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, dx3, dy3, dz3)
# Contribution (1,1,0)
dx4 = dx0 - 1 - 2 * SQUISH_CONSTANT_3D
dy4 = dy0 - 1 - 2 * SQUISH_CONSTANT_3D
dz4 = dz0 - 0 - 2 * SQUISH_CONSTANT_3D
attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4
if attn4 > 0:
attn4 *= attn4
value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 0, dx4, dy4, dz4)
# Contribution (1,0,1)
dx5 = dx4
dy5 = dy0 - 0 - 2 * SQUISH_CONSTANT_3D
dz5 = dz0 - 1 - 2 * SQUISH_CONSTANT_3D
attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5
if attn5 > 0:
attn5 *= attn5
value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 0, zsb + 1, dx5, dy5, dz5)
# Contribution (0,1,1)
dx6 = dx0 - 0 - 2 * SQUISH_CONSTANT_3D
dy6 = dy4
dz6 = dz5
attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6
if attn6 > 0:
attn6 *= attn6
value += attn6 * attn6 * extrapolate(xsb + 0, ysb + 1, zsb + 1, dx6, dy6, dz6)
# First extra vertex
attn_ext0 = 2 - dx_ext0 * dx_ext0 - dy_ext0 * dy_ext0 - dz_ext0 * dz_ext0
if attn_ext0 > 0:
attn_ext0 *= attn_ext0
value += attn_ext0 * attn_ext0 * extrapolate(xsv_ext0, ysv_ext0, zsv_ext0, dx_ext0, dy_ext0, dz_ext0)
# Second extra vertex
attn_ext1 = 2 - dx_ext1 * dx_ext1 - dy_ext1 * dy_ext1 - dz_ext1 * dz_ext1
if attn_ext1 > 0:
attn_ext1 *= attn_ext1
value += attn_ext1 * attn_ext1 * extrapolate(xsv_ext1, ysv_ext1, zsv_ext1, dx_ext1, dy_ext1, dz_ext1)
return value / NORM_CONSTANT_3D
def noise4d(self, x, y, z, w):
"""
Generate 4D OpenSimplex noise from X,Y,Z,W coordinates.
"""
# Place input coordinates on simplectic honeycomb.
stretch_offset = (x + y + z + w) * STRETCH_CONSTANT_4D
xs = x + stretch_offset
ys = y + stretch_offset
zs = z + stretch_offset
ws = w + stretch_offset
# Floor to get simplectic honeycomb coordinates of rhombo-hypercube super-cell origin.
xsb = floor(xs)
ysb = floor(ys)
zsb = floor(zs)
wsb = floor(ws)
# Skew out to get actual coordinates of stretched rhombo-hypercube origin. We'll need these later.
squish_offset = (xsb + ysb + zsb + wsb) * SQUISH_CONSTANT_4D
xb = xsb + squish_offset
yb = ysb + squish_offset
zb = zsb + squish_offset
wb = wsb + squish_offset
# Compute simplectic honeycomb coordinates relative to rhombo-hypercube origin.
xins = xs - xsb
yins = ys - ysb
zins = zs - zsb
wins = ws - wsb
# Sum those together to get a value that determines which region we're in.
in_sum = xins + yins + zins + wins
# Positions relative to origin po.
dx0 = x - xb
dy0 = y - yb
dz0 = z - zb
dw0 = w - wb
value = 0
extrapolate = self._extrapolate4d
if in_sum <= 1: # We're inside the pentachoron (4-Simplex) at (0,0,0,0)
# Determine which two of (0,0,0,1), (0,0,1,0), (0,1,0,0), (1,0,0,0) are closest.
a_po = 0x01
a_score = xins
b_po = 0x02
b_score = yins
if a_score >= b_score and zins > b_score:
b_score = zins
b_po = 0x04
elif a_score < b_score and zins > a_score:
a_score = zins
a_po = 0x04
if a_score >= b_score and wins > b_score:
b_score = wins
b_po = 0x08
elif a_score < b_score and wins > a_score:
a_score = wins
a_po = 0x08
# Now we determine the three lattice pos not part of the pentachoron that may contribute.
# This depends on the closest two pentachoron vertices, including (0,0,0,0)
uins = 1 - in_sum
if uins > a_score or uins > b_score: # (0,0,0,0) is one of the closest two pentachoron vertices.
c = b_po if (b_score > a_score) else a_po # Our other closest vertex is the closest out of a and b.
if (c & 0x01) == 0:
xsv_ext0 = xsb - 1
xsv_ext1 = xsv_ext2 = xsb
dx_ext0 = dx0 + 1
dx_ext1 = dx_ext2 = dx0
else:
xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb + 1
dx_ext0 = dx_ext1 = dx_ext2 = dx0 - 1
if (c & 0x02) == 0:
ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb
dy_ext0 = dy_ext1 = dy_ext2 = dy0
if (c & 0x01) == 0x01:
ysv_ext0 -= 1
dy_ext0 += 1
else:
ysv_ext1 -= 1
dy_ext1 += 1
else:
ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1
dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 1
if (c & 0x04) == 0:
zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb
dz_ext0 = dz_ext1 = dz_ext2 = dz0
if (c & 0x03) != 0:
if (c & 0x03) == 0x03:
zsv_ext0 -= 1
dz_ext0 += 1
else:
zsv_ext1 -= 1
dz_ext1 += 1
else:
zsv_ext2 -= 1
dz_ext2 += 1
else:
zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1
dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 1
if (c & 0x08) == 0:
wsv_ext0 = wsv_ext1 = wsb
wsv_ext2 = wsb - 1
dw_ext0 = dw_ext1 = dw0
dw_ext2 = dw0 + 1
else:
wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb + 1
dw_ext0 = dw_ext1 = dw_ext2 = dw0 - 1
else: # (0,0,0,0) is not one of the closest two pentachoron vertices.
c = (a_po | b_po) # Our three extra vertices are determined by the closest two.
if (c & 0x01) == 0:
xsv_ext0 = xsv_ext2 = xsb
xsv_ext1 = xsb - 1
dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_4D
dx_ext1 = dx0 + 1 - SQUISH_CONSTANT_4D
dx_ext2 = dx0 - SQUISH_CONSTANT_4D
else:
xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb + 1
dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dx_ext1 = dx_ext2 = dx0 - 1 - SQUISH_CONSTANT_4D
if (c & 0x02) == 0:
ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb
dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_4D
dy_ext1 = dy_ext2 = dy0 - SQUISH_CONSTANT_4D
if (c & 0x01) == 0x01:
ysv_ext1 -= 1
dy_ext1 += 1
else:
ysv_ext2 -= 1
dy_ext2 += 1
else:
ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1
dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dy_ext1 = dy_ext2 = dy0 - 1 - SQUISH_CONSTANT_4D
if (c & 0x04) == 0:
zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb
dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_4D
dz_ext1 = dz_ext2 = dz0 - SQUISH_CONSTANT_4D
if (c & 0x03) == 0x03:
zsv_ext1 -= 1
dz_ext1 += 1
else:
zsv_ext2 -= 1
dz_ext2 += 1
else:
zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1
dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dz_ext1 = dz_ext2 = dz0 - 1 - SQUISH_CONSTANT_4D
if (c & 0x08) == 0:
wsv_ext0 = wsv_ext1 = wsb
wsv_ext2 = wsb - 1
dw_ext0 = dw0 - 2 * SQUISH_CONSTANT_4D
dw_ext1 = dw0 - SQUISH_CONSTANT_4D
dw_ext2 = dw0 + 1 - SQUISH_CONSTANT_4D
else:
wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb + 1
dw_ext0 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
dw_ext1 = dw_ext2 = dw0 - 1 - SQUISH_CONSTANT_4D
# Contribution (0,0,0,0)
attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0 - dw0 * dw0
if attn0 > 0:
attn0 *= attn0
value += attn0 * attn0 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 0, dx0, dy0, dz0, dw0)
# Contribution (1,0,0,0)
dx1 = dx0 - 1 - SQUISH_CONSTANT_4D
dy1 = dy0 - 0 - SQUISH_CONSTANT_4D
dz1 = dz0 - 0 - SQUISH_CONSTANT_4D
dw1 = dw0 - 0 - SQUISH_CONSTANT_4D
attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1
if attn1 > 0:
attn1 *= attn1
value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 0, dx1, dy1, dz1, dw1)
# Contribution (0,1,0,0)
dx2 = dx0 - 0 - SQUISH_CONSTANT_4D
dy2 = dy0 - 1 - SQUISH_CONSTANT_4D
dz2 = dz1
dw2 = dw1
attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2
if attn2 > 0:
attn2 *= attn2
value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 0, dx2, dy2, dz2, dw2)
# Contribution (0,0,1,0)
dx3 = dx2
dy3 = dy1
dz3 = dz0 - 1 - SQUISH_CONSTANT_4D
dw3 = dw1
attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3
if attn3 > 0:
attn3 *= attn3
value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 0, dx3, dy3, dz3, dw3)
# Contribution (0,0,0,1)
dx4 = dx2
dy4 = dy1
dz4 = dz1
dw4 = dw0 - 1 - SQUISH_CONSTANT_4D
attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4
if attn4 > 0:
attn4 *= attn4
value += attn4 * attn4 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 1, dx4, dy4, dz4, dw4)
elif in_sum >= 3: # We're inside the pentachoron (4-Simplex) at (1,1,1,1)
# Determine which two of (1,1,1,0), (1,1,0,1), (1,0,1,1), (0,1,1,1) are closest.
a_po = 0x0E
a_score = xins
b_po = 0x0D
b_score = yins
if a_score <= b_score and zins < b_score:
b_score = zins
b_po = 0x0B
elif a_score > b_score and zins < a_score:
a_score = zins
a_po = 0x0B
if a_score <= b_score and wins < b_score:
b_score = wins
b_po = 0x07
elif a_score > b_score and wins < a_score:
a_score = wins
a_po = 0x07
# Now we determine the three lattice pos not part of the pentachoron that may contribute.
# This depends on the closest two pentachoron vertices, including (0,0,0,0)
uins = 4 - in_sum
if uins < a_score or uins < b_score: # (1,1,1,1) is one of the closest two pentachoron vertices.
c = b_po if (b_score < a_score) else a_po # Our other closest vertex is the closest out of a and b.
if (c & 0x01) != 0:
xsv_ext0 = xsb + 2
xsv_ext1 = xsv_ext2 = xsb + 1
dx_ext0 = dx0 - 2 - 4 * SQUISH_CONSTANT_4D
dx_ext1 = dx_ext2 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D
else:
xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb
dx_ext0 = dx_ext1 = dx_ext2 = dx0 - 4 * SQUISH_CONSTANT_4D
if (c & 0x02) != 0:
ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1
dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D
if (c & 0x01) != 0:
ysv_ext1 += 1
dy_ext1 -= 1
else:
ysv_ext0 += 1
dy_ext0 -= 1
else:
ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb
dy_ext0 = dy_ext1 = dy_ext2 = dy0 - 4 * SQUISH_CONSTANT_4D
if (c & 0x04) != 0:
zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1
dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D
if (c & 0x03) != 0x03:
if (c & 0x03) == 0:
zsv_ext0 += 1
dz_ext0 -= 1
else:
zsv_ext1 += 1
dz_ext1 -= 1
else:
zsv_ext2 += 1
dz_ext2 -= 1
else:
zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb
dz_ext0 = dz_ext1 = dz_ext2 = dz0 - 4 * SQUISH_CONSTANT_4D
if (c & 0x08) != 0:
wsv_ext0 = wsv_ext1 = wsb + 1
wsv_ext2 = wsb + 2
dw_ext0 = dw_ext1 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D
dw_ext2 = dw0 - 2 - 4 * SQUISH_CONSTANT_4D
else:
wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb
dw_ext0 = dw_ext1 = dw_ext2 = dw0 - 4 * SQUISH_CONSTANT_4D
else: # (1,1,1,1) is not one of the closest two pentachoron vertices.
c = (a_po & b_po) # Our three extra vertices are determined by the closest two.
if (c & 0x01) != 0:
xsv_ext0 = xsv_ext2 = xsb + 1
xsv_ext1 = xsb + 2
dx_ext0 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dx_ext1 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D
dx_ext2 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D
else:
xsv_ext0 = xsv_ext1 = xsv_ext2 = xsb
dx_ext0 = dx0 - 2 * SQUISH_CONSTANT_4D
dx_ext1 = dx_ext2 = dx0 - 3 * SQUISH_CONSTANT_4D
if (c & 0x02) != 0:
ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb + 1
dy_ext0 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dy_ext1 = dy_ext2 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D
if (c & 0x01) != 0:
ysv_ext2 += 1
dy_ext2 -= 1
else:
ysv_ext1 += 1
dy_ext1 -= 1
else:
ysv_ext0 = ysv_ext1 = ysv_ext2 = ysb
dy_ext0 = dy0 - 2 * SQUISH_CONSTANT_4D
dy_ext1 = dy_ext2 = dy0 - 3 * SQUISH_CONSTANT_4D
if (c & 0x04) != 0:
zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb + 1
dz_ext0 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dz_ext1 = dz_ext2 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D
if (c & 0x03) != 0:
zsv_ext2 += 1
dz_ext2 -= 1
else:
zsv_ext1 += 1
dz_ext1 -= 1
else:
zsv_ext0 = zsv_ext1 = zsv_ext2 = zsb
dz_ext0 = dz0 - 2 * SQUISH_CONSTANT_4D
dz_ext1 = dz_ext2 = dz0 - 3 * SQUISH_CONSTANT_4D
if (c & 0x08) != 0:
wsv_ext0 = wsv_ext1 = wsb + 1
wsv_ext2 = wsb + 2
dw_ext0 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
dw_ext1 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D
dw_ext2 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D
else:
wsv_ext0 = wsv_ext1 = wsv_ext2 = wsb
dw_ext0 = dw0 - 2 * SQUISH_CONSTANT_4D
dw_ext1 = dw_ext2 = dw0 - 3 * SQUISH_CONSTANT_4D
# Contribution (1,1,1,0)
dx4 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D
dy4 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D
dz4 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D
dw4 = dw0 - 3 * SQUISH_CONSTANT_4D
attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4
if attn4 > 0:
attn4 *= attn4
value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 0, dx4, dy4, dz4, dw4)
# Contribution (1,1,0,1)
dx3 = dx4
dy3 = dy4
dz3 = dz0 - 3 * SQUISH_CONSTANT_4D
dw3 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D
attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3
if attn3 > 0:
attn3 *= attn3
value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 1, dx3, dy3, dz3, dw3)
# Contribution (1,0,1,1)
dx2 = dx4
dy2 = dy0 - 3 * SQUISH_CONSTANT_4D
dz2 = dz4
dw2 = dw3
attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2
if attn2 > 0:
attn2 *= attn2
value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 1, dx2, dy2, dz2, dw2)
# Contribution (0,1,1,1)
dx1 = dx0 - 3 * SQUISH_CONSTANT_4D
dz1 = dz4
dy1 = dy4
dw1 = dw3
attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1
if attn1 > 0:
attn1 *= attn1
value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 1, dx1, dy1, dz1, dw1)
# Contribution (1,1,1,1)
dx0 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D
dy0 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D
dz0 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D
dw0 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D
attn0 = 2 - dx0 * dx0 - dy0 * dy0 - dz0 * dz0 - dw0 * dw0
if attn0 > 0:
attn0 *= attn0
value += attn0 * attn0 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 1, dx0, dy0, dz0, dw0)
elif in_sum <= 2: # We're inside the first dispentachoron (Rectified 4-Simplex)
a_is_bigger_side = True
b_is_bigger_side = True
# Decide between (1,1,0,0) and (0,0,1,1)
if xins + yins > zins + wins:
a_score = xins + yins
a_po = 0x03
else:
a_score = zins + wins
a_po = 0x0C
# Decide between (1,0,1,0) and (0,1,0,1)
if xins + zins > yins + wins:
b_score = xins + zins
b_po = 0x05
else:
b_score = yins + wins
b_po = 0x0A
# Closer between (1,0,0,1) and (0,1,1,0) will replace the further of a and b, if closer.
if xins + wins > yins + zins:
score = xins + wins
if a_score >= b_score and score > b_score:
b_score = score
b_po = 0x09
elif a_score < b_score and score > a_score:
a_score = score
a_po = 0x09
else:
score = yins + zins
if a_score >= b_score and score > b_score:
b_score = score
b_po = 0x06
elif a_score < b_score and score > a_score:
a_score = score
a_po = 0x06
# Decide if (1,0,0,0) is closer.
p1 = 2 - in_sum + xins
if a_score >= b_score and p1 > b_score:
b_score = p1
b_po = 0x01
b_is_bigger_side = False
elif a_score < b_score and p1 > a_score:
a_score = p1
a_po = 0x01
a_is_bigger_side = False
# Decide if (0,1,0,0) is closer.
p2 = 2 - in_sum + yins
if a_score >= b_score and p2 > b_score:
b_score = p2
b_po = 0x02
b_is_bigger_side = False
elif a_score < b_score and p2 > a_score:
a_score = p2
a_po = 0x02
a_is_bigger_side = False
# Decide if (0,0,1,0) is closer.
p3 = 2 - in_sum + zins
if a_score >= b_score and p3 > b_score:
b_score = p3
b_po = 0x04
b_is_bigger_side = False
elif a_score < b_score and p3 > a_score:
a_score = p3
a_po = 0x04
a_is_bigger_side = False
# Decide if (0,0,0,1) is closer.
p4 = 2 - in_sum + wins
if a_score >= b_score and p4 > b_score:
b_po = 0x08
b_is_bigger_side = False
elif a_score < b_score and p4 > a_score:
a_po = 0x08
a_is_bigger_side = False
# Where each of the two closest pos are determines how the extra three vertices are calculated.
if a_is_bigger_side == b_is_bigger_side:
if a_is_bigger_side: # Both closest pos on the bigger side
c1 = (a_po | b_po)
c2 = (a_po & b_po)
if (c1 & 0x01) == 0:
xsv_ext0 = xsb
xsv_ext1 = xsb - 1
dx_ext0 = dx0 - 3 * SQUISH_CONSTANT_4D
dx_ext1 = dx0 + 1 - 2 * SQUISH_CONSTANT_4D
else:
xsv_ext0 = xsv_ext1 = xsb + 1
dx_ext0 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D
dx_ext1 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
if (c1 & 0x02) == 0:
ysv_ext0 = ysb
ysv_ext1 = ysb - 1
dy_ext0 = dy0 - 3 * SQUISH_CONSTANT_4D
dy_ext1 = dy0 + 1 - 2 * SQUISH_CONSTANT_4D
else:
ysv_ext0 = ysv_ext1 = ysb + 1
dy_ext0 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D
dy_ext1 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
if (c1 & 0x04) == 0:
zsv_ext0 = zsb
zsv_ext1 = zsb - 1
dz_ext0 = dz0 - 3 * SQUISH_CONSTANT_4D
dz_ext1 = dz0 + 1 - 2 * SQUISH_CONSTANT_4D
else:
zsv_ext0 = zsv_ext1 = zsb + 1
dz_ext0 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D
dz_ext1 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
if (c1 & 0x08) == 0:
wsv_ext0 = wsb
wsv_ext1 = wsb - 1
dw_ext0 = dw0 - 3 * SQUISH_CONSTANT_4D
dw_ext1 = dw0 + 1 - 2 * SQUISH_CONSTANT_4D
else:
wsv_ext0 = wsv_ext1 = wsb + 1
dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D
dw_ext1 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
# One combination is a _permutation of (0,0,0,2) based on c2
xsv_ext2 = xsb
ysv_ext2 = ysb
zsv_ext2 = zsb
wsv_ext2 = wsb
dx_ext2 = dx0 - 2 * SQUISH_CONSTANT_4D
dy_ext2 = dy0 - 2 * SQUISH_CONSTANT_4D
dz_ext2 = dz0 - 2 * SQUISH_CONSTANT_4D
dw_ext2 = dw0 - 2 * SQUISH_CONSTANT_4D
if (c2 & 0x01) != 0:
xsv_ext2 += 2
dx_ext2 -= 2
elif (c2 & 0x02) != 0:
ysv_ext2 += 2
dy_ext2 -= 2
elif (c2 & 0x04) != 0:
zsv_ext2 += 2
dz_ext2 -= 2
else:
wsv_ext2 += 2
dw_ext2 -= 2
else: # Both closest pos on the smaller side
# One of the two extra pos is (0,0,0,0)
xsv_ext2 = xsb
ysv_ext2 = ysb
zsv_ext2 = zsb
wsv_ext2 = wsb
dx_ext2 = dx0
dy_ext2 = dy0
dz_ext2 = dz0
dw_ext2 = dw0
# Other two pos are based on the omitted axes.
c = (a_po | b_po)
if (c & 0x01) == 0:
xsv_ext0 = xsb - 1
xsv_ext1 = xsb
dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_4D
dx_ext1 = dx0 - SQUISH_CONSTANT_4D
else:
xsv_ext0 = xsv_ext1 = xsb + 1
dx_ext0 = dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_4D
if (c & 0x02) == 0:
ysv_ext0 = ysv_ext1 = ysb
dy_ext0 = dy_ext1 = dy0 - SQUISH_CONSTANT_4D
if (c & 0x01) == 0x01:
ysv_ext0 -= 1
dy_ext0 += 1
else:
ysv_ext1 -= 1
dy_ext1 += 1
else:
ysv_ext0 = ysv_ext1 = ysb + 1
dy_ext0 = dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_4D
if (c & 0x04) == 0:
zsv_ext0 = zsv_ext1 = zsb
dz_ext0 = dz_ext1 = dz0 - SQUISH_CONSTANT_4D
if (c & 0x03) == 0x03:
zsv_ext0 -= 1
dz_ext0 += 1
else:
zsv_ext1 -= 1
dz_ext1 += 1
else:
zsv_ext0 = zsv_ext1 = zsb + 1
dz_ext0 = dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_4D
if (c & 0x08) == 0:
wsv_ext0 = wsb
wsv_ext1 = wsb - 1
dw_ext0 = dw0 - SQUISH_CONSTANT_4D
dw_ext1 = dw0 + 1 - SQUISH_CONSTANT_4D
else:
wsv_ext0 = wsv_ext1 = wsb + 1
dw_ext0 = dw_ext1 = dw0 - 1 - SQUISH_CONSTANT_4D
else: # One po on each "side"
if a_is_bigger_side:
c1 = a_po
c2 = b_po
else:
c1 = b_po
c2 = a_po
# Two contributions are the bigger-sided po with each 0 replaced with -1.
if (c1 & 0x01) == 0:
xsv_ext0 = xsb - 1
xsv_ext1 = xsb
dx_ext0 = dx0 + 1 - SQUISH_CONSTANT_4D
dx_ext1 = dx0 - SQUISH_CONSTANT_4D
else:
xsv_ext0 = xsv_ext1 = xsb + 1
dx_ext0 = dx_ext1 = dx0 - 1 - SQUISH_CONSTANT_4D
if (c1 & 0x02) == 0:
ysv_ext0 = ysv_ext1 = ysb
dy_ext0 = dy_ext1 = dy0 - SQUISH_CONSTANT_4D
if (c1 & 0x01) == 0x01:
ysv_ext0 -= 1
dy_ext0 += 1
else:
ysv_ext1 -= 1
dy_ext1 += 1
else:
ysv_ext0 = ysv_ext1 = ysb + 1
dy_ext0 = dy_ext1 = dy0 - 1 - SQUISH_CONSTANT_4D
if (c1 & 0x04) == 0:
zsv_ext0 = zsv_ext1 = zsb
dz_ext0 = dz_ext1 = dz0 - SQUISH_CONSTANT_4D
if (c1 & 0x03) == 0x03:
zsv_ext0 -= 1
dz_ext0 += 1
else:
zsv_ext1 -= 1
dz_ext1 += 1
else:
zsv_ext0 = zsv_ext1 = zsb + 1
dz_ext0 = dz_ext1 = dz0 - 1 - SQUISH_CONSTANT_4D
if (c1 & 0x08) == 0:
wsv_ext0 = wsb
wsv_ext1 = wsb - 1
dw_ext0 = dw0 - SQUISH_CONSTANT_4D
dw_ext1 = dw0 + 1 - SQUISH_CONSTANT_4D
else:
wsv_ext0 = wsv_ext1 = wsb + 1
dw_ext0 = dw_ext1 = dw0 - 1 - SQUISH_CONSTANT_4D
# One contribution is a _permutation of (0,0,0,2) based on the smaller-sided po
xsv_ext2 = xsb
ysv_ext2 = ysb
zsv_ext2 = zsb
wsv_ext2 = wsb
dx_ext2 = dx0 - 2 * SQUISH_CONSTANT_4D
dy_ext2 = dy0 - 2 * SQUISH_CONSTANT_4D
dz_ext2 = dz0 - 2 * SQUISH_CONSTANT_4D
dw_ext2 = dw0 - 2 * SQUISH_CONSTANT_4D
if (c2 & 0x01) != 0:
xsv_ext2 += 2
dx_ext2 -= 2
elif (c2 & 0x02) != 0:
ysv_ext2 += 2
dy_ext2 -= 2
elif (c2 & 0x04) != 0:
zsv_ext2 += 2
dz_ext2 -= 2
else:
wsv_ext2 += 2
dw_ext2 -= 2
# Contribution (1,0,0,0)
dx1 = dx0 - 1 - SQUISH_CONSTANT_4D
dy1 = dy0 - 0 - SQUISH_CONSTANT_4D
dz1 = dz0 - 0 - SQUISH_CONSTANT_4D
dw1 = dw0 - 0 - SQUISH_CONSTANT_4D
attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1
if attn1 > 0:
attn1 *= attn1
value += attn1 * attn1 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 0, dx1, dy1, dz1, dw1)
# Contribution (0,1,0,0)
dx2 = dx0 - 0 - SQUISH_CONSTANT_4D
dy2 = dy0 - 1 - SQUISH_CONSTANT_4D
dz2 = dz1
dw2 = dw1
attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2
if attn2 > 0:
attn2 *= attn2
value += attn2 * attn2 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 0, dx2, dy2, dz2, dw2)
# Contribution (0,0,1,0)
dx3 = dx2
dy3 = dy1
dz3 = dz0 - 1 - SQUISH_CONSTANT_4D
dw3 = dw1
attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3
if attn3 > 0:
attn3 *= attn3
value += attn3 * attn3 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 0, dx3, dy3, dz3, dw3)
# Contribution (0,0,0,1)
dx4 = dx2
dy4 = dy1
dz4 = dz1
dw4 = dw0 - 1 - SQUISH_CONSTANT_4D
attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4
if attn4 > 0:
attn4 *= attn4
value += attn4 * attn4 * extrapolate(xsb + 0, ysb + 0, zsb + 0, wsb + 1, dx4, dy4, dz4, dw4)
# Contribution (1,1,0,0)
dx5 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dy5 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dz5 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D
dw5 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D
attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5 - dw5 * dw5
if attn5 > 0:
attn5 *= attn5
value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 0, dx5, dy5, dz5, dw5)
# Contribution (1,0,1,0)
dx6 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dy6 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D
dz6 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dw6 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D
attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6 - dw6 * dw6
if attn6 > 0:
attn6 *= attn6
value += attn6 * attn6 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 0, dx6, dy6, dz6, dw6)
# Contribution (1,0,0,1)
dx7 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dy7 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D
dz7 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D
dw7 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
attn7 = 2 - dx7 * dx7 - dy7 * dy7 - dz7 * dz7 - dw7 * dw7
if attn7 > 0:
attn7 *= attn7
value += attn7 * attn7 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 1, dx7, dy7, dz7, dw7)
# Contribution (0,1,1,0)
dx8 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D
dy8 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dz8 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dw8 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D
attn8 = 2 - dx8 * dx8 - dy8 * dy8 - dz8 * dz8 - dw8 * dw8
if attn8 > 0:
attn8 *= attn8
value += attn8 * attn8 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 0, dx8, dy8, dz8, dw8)
# Contribution (0,1,0,1)
dx9 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D
dy9 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dz9 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D
dw9 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
attn9 = 2 - dx9 * dx9 - dy9 * dy9 - dz9 * dz9 - dw9 * dw9
if attn9 > 0:
attn9 *= attn9
value += attn9 * attn9 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 1, dx9, dy9, dz9, dw9)
# Contribution (0,0,1,1)
dx10 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D
dy10 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D
dz10 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dw10 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
attn10 = 2 - dx10 * dx10 - dy10 * dy10 - dz10 * dz10 - dw10 * dw10
if attn10 > 0:
attn10 *= attn10
value += attn10 * attn10 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 1, dx10, dy10, dz10, dw10)
else: # We're inside the second dispentachoron (Rectified 4-Simplex)
a_is_bigger_side = True
b_is_bigger_side = True
# Decide between (0,0,1,1) and (1,1,0,0)
if xins + yins < zins + wins:
a_score = xins + yins
a_po = 0x0C
else:
a_score = zins + wins
a_po = 0x03
# Decide between (0,1,0,1) and (1,0,1,0)
if xins + zins < yins + wins:
b_score = xins + zins
b_po = 0x0A
else:
b_score = yins + wins
b_po = 0x05
# Closer between (0,1,1,0) and (1,0,0,1) will replace the further of a and b, if closer.
if xins + wins < yins + zins:
score = xins + wins
if a_score <= b_score and score < b_score:
b_score = score
b_po = 0x06
elif a_score > b_score and score < a_score:
a_score = score
a_po = 0x06
else:
score = yins + zins
if a_score <= b_score and score < b_score:
b_score = score
b_po = 0x09
elif a_score > b_score and score < a_score:
a_score = score
a_po = 0x09
# Decide if (0,1,1,1) is closer.
p1 = 3 - in_sum + xins
if a_score <= b_score and p1 < b_score:
b_score = p1
b_po = 0x0E
b_is_bigger_side = False
elif a_score > b_score and p1 < a_score:
a_score = p1
a_po = 0x0E
a_is_bigger_side = False
# Decide if (1,0,1,1) is closer.
p2 = 3 - in_sum + yins
if a_score <= b_score and p2 < b_score:
b_score = p2
b_po = 0x0D
b_is_bigger_side = False
elif a_score > b_score and p2 < a_score:
a_score = p2
a_po = 0x0D
a_is_bigger_side = False
# Decide if (1,1,0,1) is closer.
p3 = 3 - in_sum + zins
if a_score <= b_score and p3 < b_score:
b_score = p3
b_po = 0x0B
b_is_bigger_side = False
elif a_score > b_score and p3 < a_score:
a_score = p3
a_po = 0x0B
a_is_bigger_side = False
# Decide if (1,1,1,0) is closer.
p4 = 3 - in_sum + wins
if a_score <= b_score and p4 < b_score:
b_po = 0x07
b_is_bigger_side = False
elif a_score > b_score and p4 < a_score:
a_po = 0x07
a_is_bigger_side = False
# Where each of the two closest pos are determines how the extra three vertices are calculated.
if a_is_bigger_side == b_is_bigger_side:
if a_is_bigger_side: # Both closest pos on the bigger side
c1 = (a_po & b_po)
c2 = (a_po | b_po)
# Two contributions are _permutations of (0,0,0,1) and (0,0,0,2) based on c1
xsv_ext0 = xsv_ext1 = xsb
ysv_ext0 = ysv_ext1 = ysb
zsv_ext0 = zsv_ext1 = zsb
wsv_ext0 = wsv_ext1 = wsb
dx_ext0 = dx0 - SQUISH_CONSTANT_4D
dy_ext0 = dy0 - SQUISH_CONSTANT_4D
dz_ext0 = dz0 - SQUISH_CONSTANT_4D
dw_ext0 = dw0 - SQUISH_CONSTANT_4D
dx_ext1 = dx0 - 2 * SQUISH_CONSTANT_4D
dy_ext1 = dy0 - 2 * SQUISH_CONSTANT_4D
dz_ext1 = dz0 - 2 * SQUISH_CONSTANT_4D
dw_ext1 = dw0 - 2 * SQUISH_CONSTANT_4D
if (c1 & 0x01) != 0:
xsv_ext0 += 1
dx_ext0 -= 1
xsv_ext1 += 2
dx_ext1 -= 2
elif (c1 & 0x02) != 0:
ysv_ext0 += 1
dy_ext0 -= 1
ysv_ext1 += 2
dy_ext1 -= 2
elif (c1 & 0x04) != 0:
zsv_ext0 += 1
dz_ext0 -= 1
zsv_ext1 += 2
dz_ext1 -= 2
else:
wsv_ext0 += 1
dw_ext0 -= 1
wsv_ext1 += 2
dw_ext1 -= 2
# One contribution is a _permutation of (1,1,1,-1) based on c2
xsv_ext2 = xsb + 1
ysv_ext2 = ysb + 1
zsv_ext2 = zsb + 1
wsv_ext2 = wsb + 1
dx_ext2 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dy_ext2 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dz_ext2 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dw_ext2 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
if (c2 & 0x01) == 0:
xsv_ext2 -= 2
dx_ext2 += 2
elif (c2 & 0x02) == 0:
ysv_ext2 -= 2
dy_ext2 += 2
elif (c2 & 0x04) == 0:
zsv_ext2 -= 2
dz_ext2 += 2
else:
wsv_ext2 -= 2
dw_ext2 += 2
else: # Both closest pos on the smaller side
# One of the two extra pos is (1,1,1,1)
xsv_ext2 = xsb + 1
ysv_ext2 = ysb + 1
zsv_ext2 = zsb + 1
wsv_ext2 = wsb + 1
dx_ext2 = dx0 - 1 - 4 * SQUISH_CONSTANT_4D
dy_ext2 = dy0 - 1 - 4 * SQUISH_CONSTANT_4D
dz_ext2 = dz0 - 1 - 4 * SQUISH_CONSTANT_4D
dw_ext2 = dw0 - 1 - 4 * SQUISH_CONSTANT_4D
# Other two pos are based on the shared axes.
c = (a_po & b_po)
if (c & 0x01) != 0:
xsv_ext0 = xsb + 2
xsv_ext1 = xsb + 1
dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D
dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D
else:
xsv_ext0 = xsv_ext1 = xsb
dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_4D
if (c & 0x02) != 0:
ysv_ext0 = ysv_ext1 = ysb + 1
dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D
if (c & 0x01) == 0:
ysv_ext0 += 1
dy_ext0 -= 1
else:
ysv_ext1 += 1
dy_ext1 -= 1
else:
ysv_ext0 = ysv_ext1 = ysb
dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_4D
if (c & 0x04) != 0:
zsv_ext0 = zsv_ext1 = zsb + 1
dz_ext0 = dz_ext1 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D
if (c & 0x03) == 0:
zsv_ext0 += 1
dz_ext0 -= 1
else:
zsv_ext1 += 1
dz_ext1 -= 1
else:
zsv_ext0 = zsv_ext1 = zsb
dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_4D
if (c & 0x08) != 0:
wsv_ext0 = wsb + 1
wsv_ext1 = wsb + 2
dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D
dw_ext1 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D
else:
wsv_ext0 = wsv_ext1 = wsb
dw_ext0 = dw_ext1 = dw0 - 3 * SQUISH_CONSTANT_4D
else: # One po on each "side"
if a_is_bigger_side:
c1 = a_po
c2 = b_po
else:
c1 = b_po
c2 = a_po
# Two contributions are the bigger-sided po with each 1 replaced with 2.
if (c1 & 0x01) != 0:
xsv_ext0 = xsb + 2
xsv_ext1 = xsb + 1
dx_ext0 = dx0 - 2 - 3 * SQUISH_CONSTANT_4D
dx_ext1 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D
else:
xsv_ext0 = xsv_ext1 = xsb
dx_ext0 = dx_ext1 = dx0 - 3 * SQUISH_CONSTANT_4D
if (c1 & 0x02) != 0:
ysv_ext0 = ysv_ext1 = ysb + 1
dy_ext0 = dy_ext1 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D
if (c1 & 0x01) == 0:
ysv_ext0 += 1
dy_ext0 -= 1
else:
ysv_ext1 += 1
dy_ext1 -= 1
else:
ysv_ext0 = ysv_ext1 = ysb
dy_ext0 = dy_ext1 = dy0 - 3 * SQUISH_CONSTANT_4D
if (c1 & 0x04) != 0:
zsv_ext0 = zsv_ext1 = zsb + 1
dz_ext0 = dz_ext1 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D
if (c1 & 0x03) == 0:
zsv_ext0 += 1
dz_ext0 -= 1
else:
zsv_ext1 += 1
dz_ext1 -= 1
else:
zsv_ext0 = zsv_ext1 = zsb
dz_ext0 = dz_ext1 = dz0 - 3 * SQUISH_CONSTANT_4D
if (c1 & 0x08) != 0:
wsv_ext0 = wsb + 1
wsv_ext1 = wsb + 2
dw_ext0 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D
dw_ext1 = dw0 - 2 - 3 * SQUISH_CONSTANT_4D
else:
wsv_ext0 = wsv_ext1 = wsb
dw_ext0 = dw_ext1 = dw0 - 3 * SQUISH_CONSTANT_4D
# One contribution is a _permutation of (1,1,1,-1) based on the smaller-sided po
xsv_ext2 = xsb + 1
ysv_ext2 = ysb + 1
zsv_ext2 = zsb + 1
wsv_ext2 = wsb + 1
dx_ext2 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dy_ext2 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dz_ext2 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dw_ext2 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
if (c2 & 0x01) == 0:
xsv_ext2 -= 2
dx_ext2 += 2
elif (c2 & 0x02) == 0:
ysv_ext2 -= 2
dy_ext2 += 2
elif (c2 & 0x04) == 0:
zsv_ext2 -= 2
dz_ext2 += 2
else:
wsv_ext2 -= 2
dw_ext2 += 2
# Contribution (1,1,1,0)
dx4 = dx0 - 1 - 3 * SQUISH_CONSTANT_4D
dy4 = dy0 - 1 - 3 * SQUISH_CONSTANT_4D
dz4 = dz0 - 1 - 3 * SQUISH_CONSTANT_4D
dw4 = dw0 - 3 * SQUISH_CONSTANT_4D
attn4 = 2 - dx4 * dx4 - dy4 * dy4 - dz4 * dz4 - dw4 * dw4
if attn4 > 0:
attn4 *= attn4
value += attn4 * attn4 * extrapolate(xsb + 1, ysb + 1, zsb + 1, wsb + 0, dx4, dy4, dz4, dw4)
# Contribution (1,1,0,1)
dx3 = dx4
dy3 = dy4
dz3 = dz0 - 3 * SQUISH_CONSTANT_4D
dw3 = dw0 - 1 - 3 * SQUISH_CONSTANT_4D
attn3 = 2 - dx3 * dx3 - dy3 * dy3 - dz3 * dz3 - dw3 * dw3
if attn3 > 0:
attn3 *= attn3
value += attn3 * attn3 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 1, dx3, dy3, dz3, dw3)
# Contribution (1,0,1,1)
dx2 = dx4
dy2 = dy0 - 3 * SQUISH_CONSTANT_4D
dz2 = dz4
dw2 = dw3
attn2 = 2 - dx2 * dx2 - dy2 * dy2 - dz2 * dz2 - dw2 * dw2
if attn2 > 0:
attn2 *= attn2
value += attn2 * attn2 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 1, dx2, dy2, dz2, dw2)
# Contribution (0,1,1,1)
dx1 = dx0 - 3 * SQUISH_CONSTANT_4D
dz1 = dz4
dy1 = dy4
dw1 = dw3
attn1 = 2 - dx1 * dx1 - dy1 * dy1 - dz1 * dz1 - dw1 * dw1
if attn1 > 0:
attn1 *= attn1
value += attn1 * attn1 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 1, dx1, dy1, dz1, dw1)
# Contribution (1,1,0,0)
dx5 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dy5 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dz5 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D
dw5 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D
attn5 = 2 - dx5 * dx5 - dy5 * dy5 - dz5 * dz5 - dw5 * dw5
if attn5 > 0:
attn5 *= attn5
value += attn5 * attn5 * extrapolate(xsb + 1, ysb + 1, zsb + 0, wsb + 0, dx5, dy5, dz5, dw5)
# Contribution (1,0,1,0)
dx6 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dy6 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D
dz6 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dw6 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D
attn6 = 2 - dx6 * dx6 - dy6 * dy6 - dz6 * dz6 - dw6 * dw6
if attn6 > 0:
attn6 *= attn6
value += attn6 * attn6 * extrapolate(xsb + 1, ysb + 0, zsb + 1, wsb + 0, dx6, dy6, dz6, dw6)
# Contribution (1,0,0,1)
dx7 = dx0 - 1 - 2 * SQUISH_CONSTANT_4D
dy7 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D
dz7 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D
dw7 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
attn7 = 2 - dx7 * dx7 - dy7 * dy7 - dz7 * dz7 - dw7 * dw7
if attn7 > 0:
attn7 *= attn7
value += attn7 * attn7 * extrapolate(xsb + 1, ysb + 0, zsb + 0, wsb + 1, dx7, dy7, dz7, dw7)
# Contribution (0,1,1,0)
dx8 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D
dy8 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dz8 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dw8 = dw0 - 0 - 2 * SQUISH_CONSTANT_4D
attn8 = 2 - dx8 * dx8 - dy8 * dy8 - dz8 * dz8 - dw8 * dw8
if attn8 > 0:
attn8 *= attn8
value += attn8 * attn8 * extrapolate(xsb + 0, ysb + 1, zsb + 1, wsb + 0, dx8, dy8, dz8, dw8)
# Contribution (0,1,0,1)
dx9 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D
dy9 = dy0 - 1 - 2 * SQUISH_CONSTANT_4D
dz9 = dz0 - 0 - 2 * SQUISH_CONSTANT_4D
dw9 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
attn9 = 2 - dx9 * dx9 - dy9 * dy9 - dz9 * dz9 - dw9 * dw9
if attn9 > 0:
attn9 *= attn9
value += attn9 * attn9 * extrapolate(xsb + 0, ysb + 1, zsb + 0, wsb + 1, dx9, dy9, dz9, dw9)
# Contribution (0,0,1,1)
dx10 = dx0 - 0 - 2 * SQUISH_CONSTANT_4D
dy10 = dy0 - 0 - 2 * SQUISH_CONSTANT_4D
dz10 = dz0 - 1 - 2 * SQUISH_CONSTANT_4D
dw10 = dw0 - 1 - 2 * SQUISH_CONSTANT_4D
attn10 = 2 - dx10 * dx10 - dy10 * dy10 - dz10 * dz10 - dw10 * dw10
if attn10 > 0:
attn10 *= attn10
value += attn10 * attn10 * extrapolate(xsb + 0, ysb + 0, zsb + 1, wsb + 1, dx10, dy10, dz10, dw10)
# First extra vertex
attn_ext0 = 2 - dx_ext0 * dx_ext0 - dy_ext0 * dy_ext0 - dz_ext0 * dz_ext0 - dw_ext0 * dw_ext0
if attn_ext0 > 0:
attn_ext0 *= attn_ext0
value += attn_ext0 * attn_ext0 * extrapolate(xsv_ext0, ysv_ext0, zsv_ext0, wsv_ext0, dx_ext0, dy_ext0, dz_ext0, dw_ext0)
# Second extra vertex
attn_ext1 = 2 - dx_ext1 * dx_ext1 - dy_ext1 * dy_ext1 - dz_ext1 * dz_ext1 - dw_ext1 * dw_ext1
if attn_ext1 > 0:
attn_ext1 *= attn_ext1
value += attn_ext1 * attn_ext1 * extrapolate(xsv_ext1, ysv_ext1, zsv_ext1, wsv_ext1, dx_ext1, dy_ext1, dz_ext1, dw_ext1)
# Third extra vertex
attn_ext2 = 2 - dx_ext2 * dx_ext2 - dy_ext2 * dy_ext2 - dz_ext2 * dz_ext2 - dw_ext2 * dw_ext2
if attn_ext2 > 0:
attn_ext2 *= attn_ext2
value += attn_ext2 * attn_ext2 * extrapolate(xsv_ext2, ysv_ext2, zsv_ext2, wsv_ext2, dx_ext2, dy_ext2, dz_ext2, dw_ext2)
return value / NORM_CONSTANT_4D
================================================
FILE: imgaug/external/poly_point_isect_py2py3.py
================================================
# BentleyOttmann sweep-line implementation
# (for finding all intersections in a set of line segments)
__all__ = (
"isect_segments",
"isect_polygon",
# same as above but includes segments with each intersections
"isect_segments_include_segments",
"isect_polygon_include_segments",
# for testing only (correct but slow)
"isect_segments__naive",
"isect_polygon__naive",
)
# ----------------------------------------------------------------------------
# Main Poly Intersection
# Defines to change behavior.
#
# Whether to ignore intersections of line segments when both
# their end points form the intersection point.
USE_IGNORE_SEGMENT_ENDINGS = True
USE_DEBUG = True
USE_VERBOSE = False
# checks we should NOT need,
# but do them in case we find a test-case that fails.
USE_PARANOID = False
# Support vertical segments,
# (the bentley-ottmann method doesn't support this).
# We use the term 'START_VERTICAL' for a vertical segment,
# to differentiate it from START/END/INTERSECTION
USE_VERTICAL = True
# end defines!
# ------------
# ---------
# Constants
X, Y = 0, 1
# -----------------------------------------------------------------------------
# Switchable Number Implementation
NUMBER_TYPE = 'native'
if NUMBER_TYPE == 'native':
Real = float
########################################################################
# decreased 1e-10 to 1e-4 here, otherwise large float values of 10k+
# caused not found errors in remove()
NUM_EPS = Real("1e-4")
########################################################################
NUM_INF = Real(float("inf"))
elif NUMBER_TYPE == 'decimal':
# Not passing tests!
import decimal
Real = decimal.Decimal
decimal.getcontext().prec = 80
NUM_EPS = Real("1e-10")
NUM_INF = Real(float("inf"))
elif NUMBER_TYPE == 'numpy':
import numpy
Real = numpy.float64
del numpy
NUM_EPS = Real("1e-10")
NUM_INF = Real(float("inf"))
elif NUMBER_TYPE == 'gmpy2':
# Not passing tests!
import gmpy2
gmpy2.set_context(gmpy2.ieee(128))
Real = gmpy2.mpz
NUM_EPS = Real(float("1e-10"))
NUM_INF = gmpy2.get_emax_max()
del gmpy2
else:
raise Exception("Type not found")
NUM_EPS_SQ = NUM_EPS * NUM_EPS
NUM_ZERO = Real(0.0)
NUM_ONE = Real(1.0)
class Event:
__slots__ = (
"type",
"point",
"segment",
# this is just cache,
# we may remove or calculate slope on the fly
"slope",
"span",
) + (() if not USE_DEBUG else (
# debugging only
"other",
"in_sweep",
))
class Type:
END = 0
INTERSECTION = 1
START = 2
if USE_VERTICAL:
START_VERTICAL = 3
def __init__(self, type, point, segment, slope):
assert(isinstance(point, tuple))
self.type = type
self.point = point
self.segment = segment
# will be None for INTERSECTION
self.slope = slope
if segment is not None:
self.span = segment[1][X] - segment[0][X]
if USE_DEBUG:
self.other = None
self.in_sweep = False
# note that this isn't essential,
# it just avoids non-deterministic ordering, see #9.
def __hash__(self):
return hash(self.point)
def is_vertical(self):
# return self.segment[0][X] == self.segment[1][X]
return self.span == NUM_ZERO
def y_intercept_x(self, x):
# vertical events only for comparison (above_all check)
# never added into the binary-tree its self
if USE_VERTICAL:
if self.is_vertical():
return None
if x <= self.segment[0][X]:
return self.segment[0][Y]
elif x >= self.segment[1][X]:
return self.segment[1][Y]
# use the largest to avoid float precision error with nearly vertical lines.
delta_x0 = x - self.segment[0][X]
delta_x1 = self.segment[1][X] - x
if delta_x0 > delta_x1:
ifac = delta_x0 / self.span
fac = NUM_ONE - ifac
else:
fac = delta_x1 / self.span
ifac = NUM_ONE - fac
assert(fac <= NUM_ONE)
return (self.segment[0][Y] * fac) + (self.segment[1][Y] * ifac)
@staticmethod
def Compare(sweep_line, this, that):
if this is that:
return 0
if USE_DEBUG:
if this.other is that:
return 0
current_point_x = sweep_line._current_event_point_x
this_y = this.y_intercept_x(current_point_x)
that_y = that.y_intercept_x(current_point_x)
# print(this_y, that_y)
if USE_VERTICAL:
if this_y is None:
this_y = this.point[Y]
if that_y is None:
that_y = that.point[Y]
delta_y = this_y - that_y
assert((delta_y < NUM_ZERO) == (this_y < that_y))
# NOTE, VERY IMPORTANT TO USE EPSILON HERE!
# otherwise w/ float precision errors we get incorrect comparisons
# can get very strange & hard to debug output without this.
if abs(delta_y) > NUM_EPS:
return -1 if (delta_y < NUM_ZERO) else 1
else:
this_slope = this.slope
that_slope = that.slope
if this_slope != that_slope:
if sweep_line._before:
return -1 if (this_slope > that_slope) else 1
else:
return 1 if (this_slope > that_slope) else -1
delta_x_p1 = this.segment[0][X] - that.segment[0][X]
if delta_x_p1 != NUM_ZERO:
return -1 if (delta_x_p1 < NUM_ZERO) else 1
delta_x_p2 = this.segment[1][X] - that.segment[1][X]
if delta_x_p2 != NUM_ZERO:
return -1 if (delta_x_p2 < NUM_ZERO) else 1
return 0
def __repr__(self):
if self.segment is not None:
return ("Event(0x%x, s0=%r, s1=%r, p=%r, type=%d, slope=%r)" % (
id(self),
self.segment[0], self.segment[1],
self.point,
self.type,
self.slope,
))
else:
return ("Event(0x%x, s0=%r, s1=%r, p=%r, type=%d, slope=%r)" % (
id(self),
"None", "None",
self.point,
self.type,
self.slope,
))
class SweepLine:
__slots__ = (
# A map holding all intersection points mapped to the Events
# that form these intersections.
# {Point: set(Event, ...), ...}
"intersections",
"queue",
# Events (sorted set of ordered events, no values)
#
# note: START & END events are considered the same so checking if an event is in the tree
# will return true if its opposite side is found.
# This is essential for the algorithm to work, and why we don't explicitly remove START events.
# Instead, the END events are never added to the current sweep, and removing them also removes the start.
"_events_current_sweep",
# The point of the current Event.
"_current_event_point_x",
# A flag to indicate if we're slightly before or after the line.
"_before",
)
def __init__(self):
self.intersections = {}
self._current_event_point_x = None
self._events_current_sweep = RBTree(cmp=Event.Compare, cmp_data=self)
self._before = True
def get_intersections(self):
"""
Return a list of unordered intersection points.
"""
if Real is float:
return list(self.intersections.keys())
else:
return [(float(p[0]), float(p[1])) for p in self.intersections.keys()]
# Not essential for implementing this algorithm, but useful.
def get_intersections_with_segments(self):
"""
Return a list of unordered intersection '(point, segment)' pairs,
where segments may contain 2 or more values.
"""
if Real is float:
return [
(p, [event.segment for event in event_set])
for p, event_set in self.intersections.items()
]
else:
return [
(
(float(p[0]), float(p[1])),
[((float(event.segment[0][0]), float(event.segment[0][1])),
(float(event.segment[1][0]), float(event.segment[1][1])))
for event in event_set],
)
for p, event_set in self.intersections.items()
]
# Checks if an intersection exists between two Events 'a' and 'b'.
def _check_intersection(self, a, b):
# Return immediately in case either of the events is null, or
# if one of them is an INTERSECTION event.
if ((a is None or b is None) or
(a.type == Event.Type.INTERSECTION) or
(b.type == Event.Type.INTERSECTION)):
return
if a is b:
return
# Get the intersection point between 'a' and 'b'.
p = isect_seg_seg_v2_point(
a.segment[0], a.segment[1],
b.segment[0], b.segment[1])
# No intersection exists.
if p is None:
return
# If the intersection is formed by both the segment endings, AND
# USE_IGNORE_SEGMENT_ENDINGS is true,
# return from this method.
if USE_IGNORE_SEGMENT_ENDINGS:
if ((len_squared_v2v2(p, a.segment[0]) < NUM_EPS_SQ or
len_squared_v2v2(p, a.segment[1]) < NUM_EPS_SQ) and
(len_squared_v2v2(p, b.segment[0]) < NUM_EPS_SQ or
len_squared_v2v2(p, b.segment[1]) < NUM_EPS_SQ)):
return
# Add the intersection.
events_for_point = self.intersections.pop(p, set())
is_new = len(events_for_point) == 0
events_for_point.add(a)
events_for_point.add(b)
self.intersections[p] = events_for_point
# If the intersection occurs to the right of the sweep line, OR
# if the intersection is on the sweep line and it's above the
# current event-point, add it as a new Event to the queue.
if is_new and p[X] >= self._current_event_point_x:
event_isect = Event(Event.Type.INTERSECTION, p, None, None)
self.queue.offer(p, event_isect)
def _sweep_to(self, p):
if p[X] == self._current_event_point_x:
# happens in rare cases,
# we can safely ignore
return
self._current_event_point_x = p[X]
def insert(self, event):
assert(event not in self._events_current_sweep)
assert(not USE_VERTICAL or event.type != Event.Type.START_VERTICAL)
if USE_DEBUG:
assert(event.in_sweep == False)
assert(event.other.in_sweep == False)
self._events_current_sweep.insert(event, None)
if USE_DEBUG:
event.in_sweep = True
event.other.in_sweep = True
def remove(self, event):
try:
self._events_current_sweep.remove(event)
if USE_DEBUG:
assert(event.in_sweep == True)
assert(event.other.in_sweep == True)
event.in_sweep = False
event.other.in_sweep = False
return True
except KeyError:
if USE_DEBUG:
assert(event.in_sweep == False)
assert(event.other.in_sweep == False)
return False
def above(self, event):
return self._events_current_sweep.succ_key(event, None)
def below(self, event):
return self._events_current_sweep.prev_key(event, None)
'''
def above_all(self, event):
while True:
event = self.above(event)
if event is None:
break
yield event
'''
def above_all(self, event):
# assert(event not in self._events_current_sweep)
return self._events_current_sweep.key_slice(event, None, reverse=False)
def handle(self, p, events_current):
if len(events_current) == 0:
return
# done already
# self._sweep_to(events_current[0])
assert(p[0] == self._current_event_point_x)
if not USE_IGNORE_SEGMENT_ENDINGS:
if len(events_current) > 1:
for i in range(0, len(events_current) - 1):
for j in range(i + 1, len(events_current)):
self._check_intersection(
events_current[i], events_current[j])
for e in events_current:
self.handle_event(e)
def handle_event(self, event):
t = event.type
if t == Event.Type.START:
# print(" START")
self._before = False
self.insert(event)
e_above = self.above(event)
e_below = self.below(event)
self._check_intersection(event, e_above)
self._check_intersection(event, e_below)
if USE_PARANOID:
self._check_intersection(e_above, e_below)
elif t == Event.Type.END:
# print(" END")
self._before = True
e_above = self.above(event)
e_below = self.below(event)
self.remove(event)
self._check_intersection(e_above, e_below)
if USE_PARANOID:
self._check_intersection(event, e_above)
self._check_intersection(event, e_below)
elif t == Event.Type.INTERSECTION:
# print(" INTERSECTION")
self._before = True
event_set = self.intersections[event.point]
# note: events_current aren't sorted.
reinsert_stack = [] # Stack
for e in event_set:
# Since we know the Event wasn't already removed,
# we want to insert it later on.
if self.remove(e):
reinsert_stack.append(e)
self._before = False
# Insert all Events that we were able to remove.
while reinsert_stack:
e = reinsert_stack.pop()
self.insert(e)
e_above = self.above(e)
e_below = self.below(e)
self._check_intersection(e, e_above)
self._check_intersection(e, e_below)
if USE_PARANOID:
self._check_intersection(e_above, e_below)
elif (USE_VERTICAL and
(t == Event.Type.START_VERTICAL)):
# just check sanity
assert(event.segment[0][X] == event.segment[1][X])
assert(event.segment[0][Y] <= event.segment[1][Y])
# In this case we only need to find all segments in this span.
y_above_max = event.segment[1][Y]
# self.insert(event)
for e_above in self.above_all(event):
if e_above.type == Event.Type.START_VERTICAL:
continue
y_above = e_above.y_intercept_x(
self._current_event_point_x)
if USE_IGNORE_SEGMENT_ENDINGS:
if y_above >= y_above_max - NUM_EPS:
break
else:
if y_above > y_above_max:
break
# We know this intersects,
# so we could use a faster function now:
# ix = (self._current_event_point_x, y_above)
# ...however best use existing functions
# since it does all sanity checks on endpoints... etc.
self._check_intersection(event, e_above)
# self.remove(event)
class EventQueue:
__slots__ = (
# note: we only ever pop_min, this could use a 'heap' structure.
# The sorted map holding the points -> event list
# [Point: Event] (tree)
"events_scan",
)
def __init__(self, segments, line):
self.events_scan = RBTree()
# segments = [s for s in segments if s[0][0] != s[1][0] and s[0][1] != s[1][1]]
for s in segments:
assert(s[0][X] <= s[1][X])
slope = slope_v2v2(*s)
if s[0] == s[1]:
pass
elif USE_VERTICAL and (s[0][X] == s[1][X]):
e_start = Event(Event.Type.START_VERTICAL, s[0], s, slope)
if USE_DEBUG:
e_start.other = e_start # FAKE, avoid error checking
self.offer(s[0], e_start)
else:
e_start = Event(Event.Type.START, s[0], s, slope)
e_end = Event(Event.Type.END, s[1], s, slope)
if USE_DEBUG:
e_start.other = e_end
e_end.other = e_start
self.offer(s[0], e_start)
self.offer(s[1], e_end)
line.queue = self
def offer(self, p, e):
"""
Offer a new event ``s`` at point ``p`` in this queue.
"""
existing = self.events_scan.setdefault(
p, ([], [], [], []) if USE_VERTICAL else
([], [], []))
# Can use double linked-list for easy insertion at beginning/end
'''
if e.type == Event.Type.END:
existing.insert(0, e)
else:
existing.append(e)
'''
existing[e.type].append(e)
# return a set of events
def poll(self):
"""
Get, and remove, the first (lowest) item from this queue.
:return: the first (lowest) item from this queue.
:rtype: Point, Event pair.
"""
assert(len(self.events_scan) != 0)
p, events_current = self.events_scan.pop_min()
return p, events_current
def isect_segments_impl(segments, include_segments=False):
# order points left -> right
if Real is float:
segments = [
# in nearly all cases, comparing X is enough,
# but compare Y too for vertical lines
(s[0], s[1]) if (s[0] <= s[1]) else
(s[1], s[0])
for s in segments]
else:
segments = [
# in nearly all cases, comparing X is enough,
# but compare Y too for vertical lines
(
(Real(s[0][0]), Real(s[0][1])),
(Real(s[1][0]), Real(s[1][1])),
) if (s[0] <= s[1]) else
(
(Real(s[1][0]), Real(s[1][1])),
(Real(s[0][0]), Real(s[0][1])),
)
for s in segments]
sweep_line = SweepLine()
queue = EventQueue(segments, sweep_line)
while len(queue.events_scan) > 0:
if USE_VERBOSE:
print(len(queue.events_scan), sweep_line._current_event_point_x)
p, e_ls = queue.poll()
for events_current in e_ls:
if events_current:
sweep_line._sweep_to(p)
sweep_line.handle(p, events_current)
if include_segments is False:
return sweep_line.get_intersections()
else:
return sweep_line.get_intersections_with_segments()
def isect_polygon_impl(points, include_segments=False):
n = len(points)
segments = [
(tuple(points[i]), tuple(points[(i + 1) % n]))
for i in range(n)]
return isect_segments_impl(segments, include_segments=include_segments)
def isect_segments(segments):
return isect_segments_impl(segments, include_segments=False)
def isect_polygon(segments):
return isect_polygon_impl(segments, include_segments=False)
def isect_segments_include_segments(segments):
return isect_segments_impl(segments, include_segments=True)
def isect_polygon_include_segments(segments):
return isect_polygon_impl(segments, include_segments=True)
# ----------------------------------------------------------------------------
# 2D math utilities
def slope_v2v2(p1, p2):
if p1[X] == p2[X]:
if p1[Y] < p2[Y]:
return NUM_INF
else:
return -NUM_INF
else:
return (p2[Y] - p1[Y]) / (p2[X] - p1[X])
def sub_v2v2(a, b):
return (
a[0] - b[0],
a[1] - b[1])
def dot_v2v2(a, b):
return (
(a[0] * b[0]) +
(a[1] * b[1]))
def len_squared_v2v2(a, b):
c = sub_v2v2(a, b)
return dot_v2v2(c, c)
def line_point_factor_v2(p, l1, l2, default=NUM_ZERO):
u = sub_v2v2(l2, l1)
h = sub_v2v2(p, l1)
dot = dot_v2v2(u, u)
return (dot_v2v2(u, h) / dot) if dot != NUM_ZERO else default
def isect_seg_seg_v2_point(v1, v2, v3, v4, bias=NUM_ZERO):
# Only for predictability and hashable point when same input is given
if v1 > v2:
v1, v2 = v2, v1
if v3 > v4:
v3, v4 = v4, v3
if (v1, v2) > (v3, v4):
v1, v2, v3, v4 = v3, v4, v1, v2
div = (v2[0] - v1[0]) * (v4[1] - v3[1]) - (v2[1] - v1[1]) * (v4[0] - v3[0])
if div == NUM_ZERO:
return None
vi = (((v3[0] - v4[0]) *
(v1[0] * v2[1] - v1[1] * v2[0]) - (v1[0] - v2[0]) *
(v3[0] * v4[1] - v3[1] * v4[0])) / div,
((v3[1] - v4[1]) *
(v1[0] * v2[1] - v1[1] * v2[0]) - (v1[1] - v2[1]) *
(v3[0] * v4[1] - v3[1] * v4[0])) / div,
)
fac = line_point_factor_v2(vi, v1, v2, default=-NUM_ONE)
if fac < NUM_ZERO - bias or fac > NUM_ONE + bias:
return None
fac = line_point_factor_v2(vi, v3, v4, default=-NUM_ONE)
if fac < NUM_ZERO - bias or fac > NUM_ONE + bias:
return None
# vi = round(vi[X], 8), round(vi[Y], 8)
return vi
# ----------------------------------------------------------------------------
# Simple naive line intersect, (for testing only)
def isect_segments__naive(segments):
"""
Brute force O(n2) version of ``isect_segments`` for test validation.
"""
isect = []
# order points left -> right
if Real is float:
segments = [
(s[0], s[1]) if s[0][X] <= s[1][X] else
(s[1], s[0])
for s in segments]
else:
segments = [
(
(Real(s[0][0]), Real(s[0][1])),
(Real(s[1][0]), Real(s[1][1])),
) if (s[0] <= s[1]) else
(
(Real(s[1][0]), Real(s[1][1])),
(Real(s[0][0]), Real(s[0][1])),
)
for s in segments]
n = len(segments)
for i in range(n):
a0, a1 = segments[i]
for j in range(i + 1, n):
b0, b1 = segments[j]
if a0 not in (b0, b1) and a1 not in (b0, b1):
ix = isect_seg_seg_v2_point(a0, a1, b0, b1)
if ix is not None:
# USE_IGNORE_SEGMENT_ENDINGS handled already
isect.append(ix)
return isect
def isect_polygon__naive(points):
"""
Brute force O(n2) version of ``isect_polygon`` for test validation.
"""
isect = []
n = len(points)
if Real is float:
pass
else:
points = [(Real(p[0]), Real(p[1])) for p in points]
for i in range(n):
a0, a1 = points[i], points[(i + 1) % n]
for j in range(i + 1, n):
b0, b1 = points[j], points[(j + 1) % n]
if a0 not in (b0, b1) and a1 not in (b0, b1):
ix = isect_seg_seg_v2_point(a0, a1, b0, b1)
if ix is not None:
if USE_IGNORE_SEGMENT_ENDINGS:
if ((len_squared_v2v2(ix, a0) < NUM_EPS_SQ or
len_squared_v2v2(ix, a1) < NUM_EPS_SQ) and
(len_squared_v2v2(ix, b0) < NUM_EPS_SQ or
len_squared_v2v2(ix, b1) < NUM_EPS_SQ)):
continue
isect.append(ix)
return isect
# ----------------------------------------------------------------------------
# Inline Libs
#
# bintrees: 2.0.2, extracted from:
# http://pypi.python.org/pypi/bintrees
#
# - Removed unused functions, such as slicing and range iteration.
# - Added 'cmp' and and 'cmp_data' arguments,
# so we can define our own comparison that takes an arg.
# Needed for sweep-line.
# - Added support for 'default' arguments for prev_item/succ_item,
# so we can avoid exception handling.
# -------
# ABCTree
from operator import attrgetter
_sentinel = object()
class _ABCTree(object):
def __init__(self, items=None, cmp=None, cmp_data=None):
"""T.__init__(...) initializes T; see T.__class__.__doc__ for signature"""
self._root = None
self._count = 0
if cmp is None:
def cmp(cmp_data, a, b):
if a < b:
return -1
elif a > b:
return 1
else:
return 0
self._cmp = cmp
self._cmp_data = cmp_data
if items is not None:
self.update(items)
def clear(self):
"""T.clear() -> None. Remove all items from T."""
def _clear(node):
if node is not None:
_clear(node.left)
_clear(node.right)
node.free()
_clear(self._root)
self._count = 0
self._root = None
@property
def count(self):
"""Get items count."""
return self._count
def get_value(self, key):
node = self._root
while node is not None:
cmp = self._cmp(self._cmp_data, key, node.key)
if cmp == 0:
return node.value
elif cmp < 0:
node = node.left
else:
node = node.right
raise KeyError(str(key))
def pop_item(self):
"""T.pop_item() -> (k, v), remove and return some (key, value) pair as a
2-tuple; but raise KeyError if T is empty.
"""
if self.is_empty():
raise KeyError("pop_item(): tree is empty")
node = self._root
while True:
if node.left is not None:
node = node.left
elif node.right is not None:
node = node.right
else:
break
key = node.key
value = node.value
self.remove(key)
return key, value
popitem = pop_item # for compatibility to dict()
def min_item(self):
"""Get item with min key of tree, raises ValueError if tree is empty."""
if self.is_empty():
raise ValueError("Tree is empty")
node = self._root
while node.left is not None:
node = node.left
return node.key, node.value
def max_item(self):
"""Get item with max key of tree, raises ValueError if tree is empty."""
if self.is_empty():
raise ValueError("Tree is empty")
node = self._root
while node.right is not None:
node = node.right
return node.key, node.value
def succ_item(self, key, default=_sentinel):
"""Get successor (k,v) pair of key, raises KeyError if key is max key
or key does not exist. optimized for pypy.
"""
# removed graingets version, because it was little slower on CPython and much slower on pypy
# this version runs about 4x faster with pypy than the Cython version
# Note: Code sharing of succ_item() and ceiling_item() is possible, but has always a speed penalty.
node = self._root
succ_node = None
while node is not None:
cmp = self._cmp(self._cmp_data, key, node.key)
if cmp == 0:
break
elif cmp < 0:
if (succ_node is None) or self._cmp(self._cmp_data, node.key, succ_node.key) < 0:
succ_node = node
node = node.left
else:
node = node.right
if node is None: # stay at dead end
if default is _sentinel:
raise KeyError(str(key))
return default
# found node of key
if node.right is not None:
# find smallest node of right subtree
node = node.right
while node.left is not None:
node = node.left
if succ_node is None:
succ_node = node
elif self._cmp(self._cmp_data, node.key, succ_node.key) < 0:
succ_node = node
elif succ_node is None: # given key is biggest in tree
if default is _sentinel:
raise KeyError(str(key))
return default
return succ_node.key, succ_node.value
def prev_item(self, key, default=_sentinel):
"""Get predecessor (k,v) pair of key, raises KeyError if key is min key
or key does not exist. optimized for pypy.
"""
# removed graingets version, because it was little slower on CPython and much slower on pypy
# this version runs about 4x faster with pypy than the Cython version
# Note: Code sharing of prev_item() and floor_item() is possible, but has always a speed penalty.
node = self._root
prev_node = None
while node is not None:
cmp = self._cmp(self._cmp_data, key, node.key)
if cmp == 0:
break
elif cmp < 0:
node = node.left
else:
if (prev_node is None) or self._cmp(self._cmp_data, prev_node.key, node.key) < 0:
prev_node = node
node = node.right
if node is None: # stay at dead end (None)
if default is _sentinel:
raise KeyError(str(key))
return default
# found node of key
if node.left is not None:
# find biggest node of left subtree
node = node.left
while node.right is not None:
node = node.right
if prev_node is None:
prev_node = node
elif self._cmp(self._cmp_data, prev_node.key, node.key) < 0:
prev_node = node
elif prev_node is None: # given key is smallest in tree
if default is _sentinel:
raise KeyError(str(key))
return default
return prev_node.key, prev_node.value
def __repr__(self):
"""T.__repr__(...) <==> repr(x)"""
tpl = "%s({%s})" % (self.__class__.__name__, '%s')
return tpl % ", ".join(("%r: %r" % item for item in self.items()))
def __contains__(self, key):
"""k in T -> True if T has a key k, else False"""
try:
self.get_value(key)
return True
except KeyError:
return False
def __len__(self):
"""T.__len__() <==> len(x)"""
return self.count
def is_empty(self):
"""T.is_empty() -> False if T contains any items else True"""
return self.count == 0
def set_default(self, key, default=None):
"""T.set_default(k[,d]) -> T.get(k,d), also set T[k]=d if k not in T"""
try:
return self.get_value(key)
except KeyError:
self.insert(key, default)
return default
setdefault = set_default # for compatibility to dict()
def get(self, key, default=None):
"""T.get(k[,d]) -> T[k] if k in T, else d. d defaults to None."""
try:
return self.get_value(key)
except KeyError:
return default
def pop(self, key, *args):
"""T.pop(k[,d]) -> v, remove specified key and return the corresponding value.
If key is not found, d is returned if given, otherwise KeyError is raised
"""
if len(args) > 1:
raise TypeError("pop expected at most 2 arguments, got %d" % (1 + len(args)))
try:
value = self.get_value(key)
self.remove(key)
return value
except KeyError:
if len(args) == 0:
raise
else:
return args[0]
def prev_key(self, key, default=_sentinel):
"""Get predecessor to key, raises KeyError if key is min key
or key does not exist.
"""
item = self.prev_item(key, default)
return default if item is default else item[0]
def succ_key(self, key, default=_sentinel):
"""Get successor to key, raises KeyError if key is max key
or key does not exist.
"""
item = self.succ_item(key, default)
return default if item is default else item[0]
def pop_min(self):
"""T.pop_min() -> (k, v), remove item with minimum key, raise ValueError
if T is empty.
"""
item = self.min_item()
self.remove(item[0])
return item
def pop_max(self):
"""T.pop_max() -> (k, v), remove item with maximum key, raise ValueError
if T is empty.
"""
item = self.max_item()
self.remove(item[0])
return item
def min_key(self):
"""Get min key of tree, raises ValueError if tree is empty. """
return self.min_item()[0]
def max_key(self):
"""Get max key of tree, raises ValueError if tree is empty. """
return self.max_item()[0]
def key_slice(self, start_key, end_key, reverse=False):
"""T.key_slice(start_key, end_key) -> key iterator:
start_key <= key < end_key.
Yields keys in ascending order if reverse is False else in descending order.
"""
return (k for k, v in self.iter_items(start_key, end_key, reverse=reverse))
def iter_items(self, start_key=None, end_key=None, reverse=False):
"""Iterates over the (key, value) items of the associated tree,
in ascending order if reverse is True, iterate in descending order,
reverse defaults to False"""
# optimized iterator (reduced method calls) - faster on CPython but slower on pypy
if self.is_empty():
return []
if reverse:
return self._iter_items_backward(start_key, end_key)
else:
return self._iter_items_forward(start_key, end_key)
def _iter_items_forward(self, start_key=None, end_key=None):
for item in self._iter_items(left=attrgetter("left"), right=attrgetter("right"),
start_key=start_key, end_key=end_key):
yield item
def _iter_items_backward(self, start_key=None, end_key=None):
for item in self._iter_items(left=attrgetter("right"), right=attrgetter("left"),
start_key=start_key, end_key=end_key):
yield item
def _iter_items(self, left=attrgetter("left"), right=attrgetter("right"), start_key=None, end_key=None):
node = self._root
stack = []
go_left = True
in_range = self._get_in_range_func(start_key, end_key)
while True:
if left(node) is not None and go_left:
stack.append(node)
node = left(node)
else:
if in_range(node.key):
yield node.key, node.value
if right(node) is not None:
node = right(node)
go_left = True
else:
if not len(stack):
return # all done
node = stack.pop()
go_left = False
def _get_in_range_func(self, start_key, end_key):
if start_key is None and end_key is None:
return lambda x: True
else:
if start_key is None:
start_key = self.min_key()
if end_key is None:
return (lambda x: self._cmp(self._cmp_data, start_key, x) <= 0)
else:
return (lambda x: self._cmp(self._cmp_data, start_key, x) <= 0 and
self._cmp(self._cmp_data, x, end_key) < 0)
# ------
# RBTree
class Node(object):
"""Internal object, represents a tree node."""
__slots__ = ['key', 'value', 'red', 'left', 'right']
def __init__(self, key=None, value=None):
self.key = key
self.value = value
self.red = True
self.left = None
self.right = None
def free(self):
self.left = None
self.right = None
self.key = None
self.value = None
def __getitem__(self, key):
"""N.__getitem__(key) <==> x[key], where key is 0 (left) or 1 (right)."""
return self.left if key == 0 else self.right
def __setitem__(self, key, value):
"""N.__setitem__(key, value) <==> x[key]=value, where key is 0 (left) or 1 (right)."""
if key == 0:
self.left = value
else:
self.right = value
class RBTree(_ABCTree):
"""
RBTree implements a balanced binary tree with a dict-like interface.
see: http://en.wikipedia.org/wiki/Red_black_tree
"""
@staticmethod
def is_red(node):
if (node is not None) and node.red:
return True
else:
return False
@staticmethod
def jsw_single(root, direction):
other_side = 1 - direction
save = root[other_side]
root[other_side] = save[direction]
save[direction] = root
root.red = True
save.red = False
return save
@staticmethod
def jsw_double(root, direction):
other_side = 1 - direction
root[other_side] = RBTree.jsw_single(root[other_side], other_side)
return RBTree.jsw_single(root, direction)
def _new_node(self, key, value):
"""Create a new tree node."""
self._count += 1
return Node(key, value)
def insert(self, key, value):
"""T.insert(key, value) <==> T[key] = value, insert key, value into tree."""
if self._root is None: # Empty tree case
self._root = self._new_node(key, value)
self._root.red = False # make root black
return
head = Node() # False tree root
grand_parent = None
grand_grand_parent = head
parent = None # parent
direction = 0
last = 0
# Set up helpers
grand_grand_parent.right = self._root
node = grand_grand_parent.right
# Search down the tree
while True:
if node is None: # Insert new node at the bottom
node = self._new_node(key, value)
parent[direction] = node
elif RBTree.is_red(node.left) and RBTree.is_red(node.right): # Color flip
node.red = True
node.left.red = False
node.right.red = False
# Fix red violation
if RBTree.is_red(node) and RBTree.is_red(parent):
direction2 = 1 if grand_grand_parent.right is grand_parent else 0
if node is parent[last]:
grand_grand_parent[direction2] = RBTree.jsw_single(grand_parent, 1 - last)
else:
grand_grand_parent[direction2] = RBTree.jsw_double(grand_parent, 1 - last)
# Stop if found
if self._cmp(self._cmp_data, key, node.key) == 0:
node.value = value # set new value for key
break
last = direction
direction = 0 if (self._cmp(self._cmp_data, key, node.key) < 0) else 1
# Update helpers
if grand_parent is not None:
grand_grand_parent = grand_parent
grand_parent = parent
parent = node
node = node[direction]
self._root = head.right # Update root
self._root.red = False # make root black
def remove(self, key):
"""T.remove(key) <==> del T[key], remove item from tree."""
if self._root is None:
raise KeyError(str(key))
head = Node() # False tree root
node = head
node.right = self._root
parent = None
grand_parent = None
found = None # Found item
direction = 1
# Search and push a red down
while node[direction] is not None:
last = direction
# Update helpers
grand_parent = parent
parent = node
node = node[direction]
direction = 1 if (self._cmp(self._cmp_data, node.key, key) < 0) else 0
# Save found node
if self._cmp(self._cmp_data, key, node.key) == 0:
found = node
# Push the red node down
if not RBTree.is_red(node) and not RBTree.is_red(node[direction]):
if RBTree.is_red(node[1 - direction]):
parent[last] = RBTree.jsw_single(node, direction)
parent = parent[last]
elif not RBTree.is_red(node[1 - direction]):
sibling = parent[1 - last]
if sibling is not None:
if (not RBTree.is_red(sibling[1 - last])) and (not RBTree.is_red(sibling[last])):
# Color flip
parent.red = False
sibling.red = True
node.red = True
else:
direction2 = 1 if grand_parent.right is parent else 0
if RBTree.is_red(sibling[last]):
grand_parent[direction2] = RBTree.jsw_double(parent, last)
elif RBTree.is_red(sibling[1-last]):
grand_parent[direction2] = RBTree.jsw_single(parent, last)
# Ensure correct coloring
grand_parent[direction2].red = True
node.red = True
grand_parent[direction2].left.red = False
grand_parent[direction2].right.red = False
# Replace and remove if found
if found is not None:
found.key = node.key
found.value = node.value
parent[int(parent.right is node)] = node[int(node.left is None)]
node.free()
self._count -= 1
# Update root and make it black
self._root = head.right
if self._root is not None:
self._root.red = False
if not found:
raise KeyError(str(key))
================================================
FILE: imgaug/imgaug.py
================================================
"""Collection of basic functions used throughout imgaug."""
from __future__ import print_function, division, absolute_import
import math
import numbers
import sys
import os
import types
import functools
# collections.abc exists since 3.3 and is expected to be used for 3.8+
try:
from collections.abc import Iterable
except ImportError:
from collections import Iterable
import numpy as np
import cv2
import six
import six.moves as sm
import skimage.draw
import skimage.measure
try:
import numba
except ImportError:
numba = None
ALL = "ALL"
DEFAULT_FONT_FP = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"DejaVuSans.ttf"
)
# to check if a dtype instance is among these dtypes, use e.g.
# `dtype.type in NP_FLOAT_TYPES` do not just use `dtype in NP_FLOAT_TYPES` as
# that would fail
NP_FLOAT_TYPES = set(np.sctypes["float"])
NP_INT_TYPES = set(np.sctypes["int"])
NP_UINT_TYPES = set(np.sctypes["uint"])
IMSHOW_BACKEND_DEFAULT = "matplotlib"
IMRESIZE_VALID_INTERPOLATIONS = [
"nearest", "linear", "area", "cubic",
cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_AREA, cv2.INTER_CUBIC]
# Cache dict to save kernels used for pooling.
# Added in 0.5.0.
_POOLING_KERNELS_CACHE = {}
# Added in 0.5.0.
_NUMBA_INSTALLED = numba is not None
# Added in 0.5.0.
_UINT8_DTYPE = np.dtype("uint8")
###############################################################################
# Helpers for deprecation
###############################################################################
class DeprecationWarning(Warning): # pylint: disable=redefined-builtin
"""Warning for deprecated calls.
Since python 2.7 DeprecatedWarning is silent by default. So we define
our own DeprecatedWarning here so that it is not silent by default.
"""
def warn(msg, category=UserWarning, stacklevel=2):
"""Generate a a warning with stacktrace.
Parameters
----------
msg : str
The message of the warning.
category : class
The class of the warning to produce.
stacklevel : int, optional
How many steps above this function to "jump" in the stacktrace when
displaying file and line number of the error message.
Usually ``2``.
"""
import warnings
warnings.warn(msg, category=category, stacklevel=stacklevel)
def warn_deprecated(msg, stacklevel=2):
"""Generate a non-silent deprecation warning with stacktrace.
The used warning is ``imgaug.imgaug.DeprecationWarning``.
Parameters
----------
msg : str
The message of the warning.
stacklevel : int, optional
How many steps above this function to "jump" in the stacktrace when
displaying file and line number of the error message.
Usually ``2``
"""
warn(msg, category=DeprecationWarning, stacklevel=stacklevel)
class deprecated(object): # pylint: disable=invalid-name
"""Decorator to mark deprecated functions with warning.
Adapted from
.
Parameters
----------
alt_func : None or str, optional
If given, tell user what function to use instead.
behavior : {'warn', 'raise'}, optional
Behavior during call to deprecated function: ``warn`` means that the
user is warned that the function is deprecated; ``raise`` means that
an error is raised.
removed_version : None or str, optional
The package version in which the deprecated function will be removed.
comment : None or str, optional
An optional comment that will be appended to the warning message.
"""
def __init__(self, alt_func=None, behavior="warn", removed_version=None,
comment=None):
self.alt_func = alt_func
self.behavior = behavior
self.removed_version = removed_version
self.comment = comment
def __call__(self, func):
alt_msg = None
if self.alt_func is not None:
alt_msg = "Use ``%s`` instead." % (self.alt_func,)
rmv_msg = None
if self.removed_version is not None:
rmv_msg = "It will be removed in version %s." % (
self.removed_version,)
comment_msg = None
if self.comment is not None and len(self.comment) > 0:
comment_msg = "%s." % (self.comment.rstrip(". "),)
addendum = " ".join([submsg
for submsg
in [alt_msg, rmv_msg, comment_msg]
if submsg is not None])
@functools.wraps(func)
def wrapped(*args, **kwargs):
# getargpec() is deprecated
# pylint: disable=deprecated-method
# TODO add class name if class method
import inspect
# arg_names = func.__code__.co_varnames
# getargspec() was deprecated in py3, but doesn't exist in py2
if hasattr(inspect, "getfullargspec"):
arg_names = inspect.getfullargspec(func)[0]
else:
arg_names = inspect.getargspec(func)[0]
if "self" in arg_names or "cls" in arg_names:
main_msg = "Method ``%s.%s()`` is deprecated." % (
args[0].__class__.__name__, func.__name__)
else:
main_msg = "Function ``%s()`` is deprecated." % (
func.__name__,)
msg = (main_msg + " " + addendum).rstrip(" ").replace("``", "`")
if self.behavior == "warn":
warn_deprecated(msg, stacklevel=3)
elif self.behavior == "raise":
raise DeprecationWarning(msg)
return func(*args, **kwargs)
# modify doc string to display deprecation warning
doc = "**Deprecated**. " + addendum
if wrapped.__doc__ is None:
wrapped.__doc__ = doc
else:
wrapped.__doc__ = doc + "\n\n " + wrapped.__doc__
return wrapped
###############################################################################
def is_np_array(val):
"""Check whether a variable is a numpy array.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a numpy array. Otherwise ``False``.
"""
# using np.generic here via isinstance(val, (np.ndarray, np.generic))
# seems to also fire for scalar numpy values even though those are not
# arrays
return isinstance(val, np.ndarray)
def is_np_scalar(val):
"""Check whether a variable is a numpy scalar.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a numpy scalar. Otherwise ``False``.
"""
# Note that isscalar() alone also fires for thinks like python strings
# or booleans.
# The isscalar() was added to make this function not fire for non-scalar
# numpy types. Not sure if it is necessary.
return isinstance(val, np.generic) and np.isscalar(val)
def is_single_integer(val):
"""Check whether a variable is an ``int``.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is an ``int``. Otherwise ``False``.
"""
return isinstance(val, numbers.Integral) and not isinstance(val, bool)
def is_single_float(val):
"""Check whether a variable is a ``float``.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a ``float``. Otherwise ``False``.
"""
return (
isinstance(val, numbers.Real)
and not is_single_integer(val)
and not isinstance(val, bool)
)
def is_single_number(val):
"""Check whether a variable is a ``number``, i.e. an ``int`` or ``float``.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a ``number``. Otherwise ``False``.
"""
return is_single_integer(val) or is_single_float(val)
def is_iterable(val):
"""
Checks whether a variable is iterable.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is an iterable. Otherwise ``False``.
"""
return isinstance(val, Iterable)
# TODO convert to is_single_string() or rename is_single_integer/float/number()
def is_string(val):
"""Check whether a variable is a string.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a string. Otherwise ``False``.
"""
return isinstance(val, six.string_types)
def is_single_bool(val):
"""Check whether a variable is a ``bool``.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a ``bool``. Otherwise ``False``.
"""
# pylint: disable=unidiomatic-typecheck
return type(val) == type(True)
def is_integer_array(val):
"""Check whether a variable is a numpy integer array.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a numpy integer array. Otherwise ``False``.
"""
return is_np_array(val) and issubclass(val.dtype.type, np.integer)
def is_float_array(val):
"""Check whether a variable is a numpy float array.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a numpy float array. Otherwise ``False``.
"""
return is_np_array(val) and issubclass(val.dtype.type, np.floating)
def is_callable(val):
"""Check whether a variable is a callable, e.g. a function.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` if the variable is a callable. Otherwise ``False``.
"""
# python 3.x with x <= 2 does not support callable(), apparently
if sys.version_info[0] == 3 and sys.version_info[1] <= 2:
return hasattr(val, '__call__')
return callable(val)
def is_generator(val):
"""Check whether a variable is a generator.
Parameters
----------
val
The variable to check.
Returns
-------
bool
``True`` is the variable is a generator. Otherwise ``False``.
"""
return isinstance(val, types.GeneratorType)
def flatten(nested_iterable):
"""Flatten arbitrarily nested lists/tuples.
Code partially taken from https://stackoverflow.com/a/10824420.
Parameters
----------
nested_iterable
A ``list`` or ``tuple`` of arbitrarily nested values.
Yields
------
any
All values in `nested_iterable`, flattened.
"""
# don't just check if something is iterable here, because then strings
# and arrays will be split into their characters and components
if not isinstance(nested_iterable, (list, tuple)):
yield nested_iterable
else:
for i in nested_iterable:
if isinstance(i, (list, tuple)):
for j in flatten(i):
yield j
else:
yield i
# TODO no longer used anywhere. deprecate?
def caller_name():
"""Return the name of the caller, e.g. a function.
Returns
-------
str
The name of the caller as a string
"""
# pylint: disable=protected-access
return sys._getframe(1).f_code.co_name
def seed(entropy=None, seedval=None):
"""Set the seed of imgaug's global RNG.
The global RNG controls most of the "randomness" in imgaug.
The global RNG is the default one used by all augmenters. Under special
circumstances (e.g. when an augmenter is switched to deterministic mode),
the global RNG is replaced with a local one. The state of that replacement
may be dependent on the global RNG's state at the time of creating the
child RNG.
.. note::
This function is not yet marked as deprecated, but might be in the
future. The preferred way to seed `imgaug` is via
:func:`~imgaug.random.seed`.
Parameters
----------
entropy : int
The seed value to use.
seedval : None or int, optional
Deprecated since 0.4.0.
"""
assert entropy is not None or seedval is not None, (
"Expected argument 'entropy' or 'seedval' to be not-None, but both"
"were None.")
if seedval is not None:
assert entropy is None, (
"Argument 'seedval' is the outdated name for 'entropy'. Hence, "
"if it is provided, 'entropy' must be None. Got 'entropy' value "
"of type %s." % (type(entropy),))
warn_deprecated("Parameter 'seedval' is deprecated. Use "
"'entropy' instead.")
entropy = seedval
import imgaug.random
imgaug.random.seed(entropy)
@deprecated("imgaug.random.normalize_generator")
def normalize_random_state(random_state):
"""Normalize various inputs to a numpy random generator.
Parameters
----------
random_state : None or int or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.bit_generator.SeedSequence or numpy.random.RandomState
See :func:`~imgaug.random.normalize_generator`.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator`` (even if
the input was a ``RandomState``).
"""
import imgaug.random
return imgaug.random.normalize_generator_(random_state)
@deprecated("imgaug.random.get_global_rng")
def current_random_state():
"""Get or create the current global RNG of imgaug.
Note that the first call to this function will create a global RNG.
Returns
-------
imgaug.random.RNG
The global RNG to use.
"""
import imgaug.random
return imgaug.random.get_global_rng()
@deprecated("imgaug.random.convert_seed_to_rng")
def new_random_state(seed=None, fully_random=False):
"""Create a new numpy random number generator.
Parameters
----------
seed : None or int, optional
The seed value to use. If ``None`` and `fully_random` is ``False``,
the seed will be derived from the global RNG. If `fully_random` is
``True``, the seed will be provided by the OS.
fully_random : bool, optional
Whether the seed will be provided by the OS.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
Both are initialized with the provided seed.
"""
# pylint: disable=redefined-outer-name
import imgaug.random
if seed is None:
if fully_random:
return imgaug.random.RNG.create_fully_random()
return imgaug.random.RNG.create_pseudo_random_()
return imgaug.random.RNG(seed)
# TODO seems to not be used anywhere anymore
@deprecated("imgaug.random.convert_seed_to_rng")
def dummy_random_state():
"""Create a dummy random state using a seed of ``1``.
Returns
-------
imgaug.random.RNG
The new random state.
"""
import imgaug.random
return imgaug.random.RNG(1)
@deprecated("imgaug.random.copy_generator_unless_global_rng")
def copy_random_state(random_state, force_copy=False):
"""Copy an existing numpy (random number) generator.
Parameters
----------
random_state : numpy.random.Generator or numpy.random.RandomState
The generator to copy.
force_copy : bool, optional
If ``True``, this function will always create a copy of every random
state. If ``False``, it will not copy numpy's default random state,
but all other random states.
Returns
-------
rs_copy : numpy.random.RandomState
The copied random state.
"""
import imgaug.random
if force_copy:
return imgaug.random.copy_generator(random_state)
return imgaug.random.copy_generator_unless_global_generator(random_state)
@deprecated("imgaug.random.derive_generator_")
def derive_random_state(random_state):
"""Derive a child numpy random generator from another one.
Parameters
----------
random_state : numpy.random.Generator or numpy.random.RandomState
The generator from which to derive a new child generator.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
In both cases a derived child generator.
"""
import imgaug.random
return imgaug.random.derive_generator_(random_state)
@deprecated("imgaug.random.derive_generators_")
def derive_random_states(random_state, n=1):
"""Derive child numpy random generators from another one.
Parameters
----------
random_state : numpy.random.Generator or numpy.random.RandomState
The generator from which to derive new child generators.
n : int, optional
Number of child generators to derive.
Returns
-------
list of numpy.random.Generator or list of numpy.random.RandomState
In numpy <=1.16 a ``list`` of ``RandomState`` s,
in 1.17+ a ``list`` of ``Generator`` s.
In both cases lists of derived child generators.
"""
import imgaug.random
return imgaug.random.derive_generators_(random_state, n=n)
@deprecated("imgaug.random.advance_generator_")
def forward_random_state(random_state):
"""Advance a numpy random generator's internal state.
Parameters
----------
random_state : numpy.random.Generator or numpy.random.RandomState
Generator of which to advance the internal state.
"""
import imgaug.random
imgaug.random.advance_generator_(random_state)
# TODO change this to some atan2 stuff?
def angle_between_vectors(v1, v2):
"""Calculcate the angle in radians between vectors `v1` and `v2`.
From
http://stackoverflow.com/questions/2827393/angles-between-two-n-dimensional-vectors-in-python
Parameters
----------
v1 : (N,) ndarray
First vector.
v2 : (N,) ndarray
Second vector.
Returns
-------
float
Angle in radians.
Examples
--------
>>> angle_between_vectors(np.float32([1, 0, 0]), np.float32([0, 1, 0]))
1.570796...
>>> angle_between_vectors(np.float32([1, 0, 0]), np.float32([1, 0, 0]))
0.0
>>> angle_between_vectors(np.float32([1, 0, 0]), np.float32([-1, 0, 0]))
3.141592...
"""
# pylint: disable=invalid-name
length1 = np.linalg.norm(v1)
length2 = np.linalg.norm(v2)
v1_unit = (v1 / length1) if length1 > 0 else np.float32(v1) * 0
v2_unit = (v2 / length2) if length2 > 0 else np.float32(v2) * 0
return np.arccos(np.clip(np.dot(v1_unit, v2_unit), -1.0, 1.0))
# TODO is this used anywhere?
# TODO this might also be covered by augmentables.utils or
# augmentables.polys/lines
def compute_line_intersection_point(x1, y1, x2, y2, x3, y3, x4, y4):
"""Compute the intersection point of two lines.
Taken from https://stackoverflow.com/a/20679579 .
Parameters
----------
x1 : number
x coordinate of the first point on line 1.
(The lines extends beyond this point.)
y1 : number
y coordinate of the first point on line 1.
(The lines extends beyond this point.)
x2 : number
x coordinate of the second point on line 1.
(The lines extends beyond this point.)
y2 : number
y coordinate of the second point on line 1.
(The lines extends beyond this point.)
x3 : number
x coordinate of the first point on line 2.
(The lines extends beyond this point.)
y3 : number
y coordinate of the first point on line 2.
(The lines extends beyond this point.)
x4 : number
x coordinate of the second point on line 2.
(The lines extends beyond this point.)
y4 : number
y coordinate of the second point on line 2.
(The lines extends beyond this point.)
Returns
-------
tuple of number or bool
The coordinate of the intersection point as a ``tuple`` ``(x, y)``.
If the lines are parallel (no intersection point or an infinite number
of them), the result is ``False``.
"""
# pylint: disable=invalid-name
def _make_line(point1, point2):
line_y = (point1[1] - point2[1])
line_x = (point2[0] - point1[0])
slope = (point1[0] * point2[1] - point2[0] * point1[1])
return line_y, line_x, -slope
line1 = _make_line((x1, y1), (x2, y2))
line2 = _make_line((x3, y3), (x4, y4))
D = line1[0] * line2[1] - line1[1] * line2[0]
Dx = line1[2] * line2[1] - line1[1] * line2[2]
Dy = line1[0] * line2[2] - line1[2] * line2[0]
if D != 0:
x = Dx / D
y = Dy / D
return x, y
return False
# TODO replace by cv2.putText()?
def draw_text(img, y, x, text, color=(0, 255, 0), size=25):
"""Draw text on an image.
This uses by default DejaVuSans as its font, which is included in this
library.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: yes; not tested
* ``float64``: no
* ``float128``: no
* ``bool``: no
TODO check if other dtypes could be enabled
Parameters
----------
img : (H,W,3) ndarray
The image array to draw text on.
Expected to be of dtype ``uint8`` or ``float32`` (expected value
range is ``[0.0, 255.0]``).
y : int
x-coordinate of the top left corner of the text.
x : int
y- coordinate of the top left corner of the text.
text : str
The text to draw.
color : iterable of int, optional
Color of the text to draw. For RGB-images this is expected to be an
RGB color.
size : int, optional
Font size of the text to draw.
Returns
-------
(H,W,3) ndarray
Input image with text drawn on it.
"""
from PIL import (
Image as PIL_Image,
ImageDraw as PIL_ImageDraw,
ImageFont as PIL_ImageFont
)
assert img.dtype.name in ["uint8", "float32"], (
"Can currently draw text only on images of dtype 'uint8' or "
"'float32'. Got dtype %s." % (img.dtype.name,))
input_dtype = img.dtype
if img.dtype == np.float32:
img = img.astype(np.uint8)
img = PIL_Image.fromarray(img)
font = PIL_ImageFont.truetype(DEFAULT_FONT_FP, size)
context = PIL_ImageDraw.Draw(img)
context.text((x, y), text, fill=tuple(color), font=font)
img_np = np.asarray(img)
# PIL/asarray returns read only array
if not img_np.flags["WRITEABLE"]:
try:
# this seems to no longer work with np 1.16 (or was pillow
# updated?)
img_np.setflags(write=True)
except ValueError as ex:
if "cannot set WRITEABLE flag to True of this array" in str(ex):
img_np = np.copy(img_np)
if img_np.dtype != input_dtype:
img_np = img_np.astype(input_dtype)
return img_np
# TODO rename sizes to size?
def imresize_many_images(images, sizes=None, interpolation=None):
"""Resize each image in a list or array to a specified size.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: no (1)
* ``uint64``: no (2)
* ``int8``: yes; tested (3)
* ``int16``: yes; tested
* ``int32``: limited; tested (4)
* ``int64``: no (2)
* ``float16``: yes; tested (5)
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: no (1)
* ``bool``: yes; tested (6)
- (1) rejected by ``cv2.imresize``
- (2) results too inaccurate
- (3) mapped internally to ``int16`` when interpolation!="nearest"
- (4) only supported for interpolation="nearest", other interpolations
lead to cv2 error
- (5) mapped internally to ``float32``
- (6) mapped internally to ``uint8``
Parameters
----------
images : (N,H,W,[C]) ndarray or list of (H,W,[C]) ndarray
Array of the images to resize.
Usually recommended to be of dtype ``uint8``.
sizes : float or iterable of int or iterable of float
The new size of the images, given either as a fraction (a single
float) or as a ``(height, width)`` ``tuple`` of two integers or as a
``(height fraction, width fraction)`` ``tuple`` of two floats.
interpolation : None or str or int, optional
The interpolation to use during resize.
If ``int``, then expected to be one of:
* ``cv2.INTER_NEAREST`` (nearest neighbour interpolation)
* ``cv2.INTER_LINEAR`` (linear interpolation)
* ``cv2.INTER_AREA`` (area interpolation)
* ``cv2.INTER_CUBIC`` (cubic interpolation)
If ``str``, then expected to be one of:
* ``nearest`` (identical to ``cv2.INTER_NEAREST``)
* ``linear`` (identical to ``cv2.INTER_LINEAR``)
* ``area`` (identical to ``cv2.INTER_AREA``)
* ``cubic`` (identical to ``cv2.INTER_CUBIC``)
If ``None``, the interpolation will be chosen automatically. For size
increases, ``area`` interpolation will be picked and for size
decreases, ``linear`` interpolation will be picked.
Returns
-------
(N,H',W',[C]) ndarray
Array of the resized images.
Examples
--------
>>> import imgaug as ia
>>> images = np.zeros((2, 8, 16, 3), dtype=np.uint8)
>>> images_resized = ia.imresize_many_images(images, 2.0)
>>> images_resized.shape
(2, 16, 32, 3)
Convert two RGB images of height ``8`` and width ``16`` to images of
height ``2*8=16`` and width ``2*16=32``.
>>> images_resized = ia.imresize_many_images(images, (2.0, 4.0))
>>> images_resized.shape
(2, 16, 64, 3)
Convert two RGB images of height ``8`` and width ``16`` to images of
height ``2*8=16`` and width ``4*16=64``.
>>> images_resized = ia.imresize_many_images(images, (16, 32))
>>> images_resized.shape
(2, 16, 32, 3)
Converts two RGB images of height ``8`` and width ``16`` to images of
height ``16`` and width ``32``.
"""
# pylint: disable=too-many-statements
# we just do nothing if the input contains zero images
# one could also argue that an exception would be appropriate here
if len(images) == 0:
return images
# verify that sizes contains only values >0
if is_single_number(sizes) and sizes <= 0:
raise ValueError(
"If 'sizes' is given as a single number, it is expected to "
"be >= 0, got %.8f." % (sizes,))
# change after the validation to make the above error messages match the
# original input
if is_single_number(sizes):
sizes = (sizes, sizes)
else:
assert len(sizes) == 2, (
"If 'sizes' is given as a tuple, it is expected be a tuple of two "
"entries, got %d entries." % (len(sizes),))
assert all([is_single_number(val) and val >= 0 for val in sizes]), (
"If 'sizes' is given as a tuple, it is expected be a tuple of two "
"ints or two floats, each >= 0, got types %s with values %s." % (
str([type(val) for val in sizes]), str(sizes)))
# if input is a list, call this function N times for N images
# but check beforehand if all images have the same shape, then just
# convert to a single array and de-convert afterwards
if isinstance(images, list):
nb_shapes = len({image.shape for image in images})
if nb_shapes == 1:
return list(imresize_many_images(
np.array(images), sizes=sizes, interpolation=interpolation))
return [
imresize_many_images(
image[np.newaxis, ...],
sizes=sizes,
interpolation=interpolation)[0, ...]
for image in images]
shape = images.shape
assert images.ndim in [3, 4], "Expected array of shape (N, H, W, [C]), " \
"got shape %s" % (str(shape),)
nb_images = shape[0]
height_image, width_image = shape[1], shape[2]
nb_channels = shape[3] if images.ndim > 3 else None
height_target, width_target = sizes[0], sizes[1]
height_target = (int(np.round(height_image * height_target))
if is_single_float(height_target)
else height_target)
width_target = (int(np.round(width_image * width_target))
if is_single_float(width_target)
else width_target)
if height_target == height_image and width_target == width_image:
return np.copy(images)
# return empty array if input array contains zero-sized axes
# note that None==0 is not True (for case nb_channels=None)
if 0 in [height_target, width_target, nb_channels]:
shape_out = tuple([shape[0], height_target, width_target]
+ list(shape[3:]))
return np.zeros(shape_out, dtype=images.dtype)
# place this after the (h==h' and w==w') check so that images with
# zero-sized don't result in errors if the aren't actually resized
# verify that all input images have height/width > 0
has_zero_size_axes = any([axis == 0 for axis in images.shape[1:]])
assert not has_zero_size_axes, (
"Cannot resize images, because at least one image has a height and/or "
"width and/or number of channels of zero. "
"Observed shapes were: %s." % (
str([image.shape for image in images]),))
inter = interpolation
assert inter is None or inter in IMRESIZE_VALID_INTERPOLATIONS, (
"Expected 'interpolation' to be None or one of %s. Got %s." % (
", ".join(
[str(valid_ip) for valid_ip in IMRESIZE_VALID_INTERPOLATIONS]
),
str(inter)
)
)
if inter is None:
if height_target > height_image or width_target > width_image:
inter = cv2.INTER_AREA
else:
inter = cv2.INTER_LINEAR
elif inter in ["nearest", cv2.INTER_NEAREST]:
inter = cv2.INTER_NEAREST
elif inter in ["linear", cv2.INTER_LINEAR]:
inter = cv2.INTER_LINEAR
elif inter in ["area", cv2.INTER_AREA]:
inter = cv2.INTER_AREA
else: # if ip in ["cubic", cv2.INTER_CUBIC]:
inter = cv2.INTER_CUBIC
# TODO find more beautiful way to avoid circular imports
from . import dtypes as iadt
if inter == cv2.INTER_NEAREST:
iadt.gate_dtypes_strs(
images,
allowed="bool uint8 uint16 int8 int16 int32 "
"float16 float32 float64",
disallowed="uint32 uint64 int64 float128",
augmenter=None
)
else:
iadt.gate_dtypes_strs(
images,
allowed="bool uint8 uint16 int8 int16 float16 float32 float64",
disallowed="uint32 uint64 int32 int64 float128",
augmenter=None
)
result_shape = (nb_images, height_target, width_target)
if nb_channels is not None:
result_shape = result_shape + (nb_channels,)
result = np.zeros(result_shape, dtype=images.dtype)
for i, image in enumerate(images):
input_dtype = image.dtype
input_dtype_name = input_dtype.name
if input_dtype_name == "bool":
image = image.astype(np.uint8) * 255
elif input_dtype_name == "int8" and inter != cv2.INTER_NEAREST:
image = image.astype(np.int16)
elif input_dtype_name == "float16":
image = image.astype(np.float32)
if nb_channels is not None and nb_channels > 512:
channels = [
cv2.resize(image[..., c], (width_target, height_target),
interpolation=inter) for c in sm.xrange(nb_channels)]
result_img = np.stack(channels, axis=-1)
else:
result_img = cv2.resize(
image, (width_target, height_target), interpolation=inter)
assert result_img.dtype.name == image.dtype.name, (
"Expected cv2.resize() to keep the input dtype '%s', but got "
"'%s'. This is an internal error. Please report." % (
image.dtype.name, result_img.dtype.name
)
)
# cv2 removes the channel axis if input was (H, W, 1)
# we re-add it (but only if input was not (H, W))
if (len(result_img.shape) == 2 and nb_channels is not None
and nb_channels == 1):
result_img = result_img[:, :, np.newaxis]
if input_dtype_name == "bool":
result_img = result_img > 127
elif input_dtype_name == "int8" and inter != cv2.INTER_NEAREST:
# TODO somehow better avoid circular imports here
from . import dtypes as iadt
result_img = iadt.restore_dtypes_(result_img, np.int8)
elif input_dtype_name == "float16":
# TODO see above
from . import dtypes as iadt
result_img = iadt.restore_dtypes_(result_img, np.float16)
result[i] = result_img
return result
def _assert_two_or_three_dims(shape):
if hasattr(shape, "shape"):
shape = shape.shape
assert len(shape) in [2, 3], (
"Expected image with two or three dimensions, but got %d dimensions "
"and shape %s." % (len(shape), shape))
def imresize_single_image(image, sizes, interpolation=None):
"""Resize a single image.
**Supported dtypes**:
See :func:`~imgaug.imgaug.imresize_many_images`.
Parameters
----------
image : (H,W,C) ndarray or (H,W) ndarray
Array of the image to resize.
Usually recommended to be of dtype ``uint8``.
sizes : float or iterable of int or iterable of float
See :func:`~imgaug.imgaug.imresize_many_images`.
interpolation : None or str or int, optional
See :func:`~imgaug.imgaug.imresize_many_images`.
Returns
-------
(H',W',C) ndarray or (H',W') ndarray
The resized image.
"""
_assert_two_or_three_dims(image)
grayscale = False
if image.ndim == 2:
grayscale = True
image = image[:, :, np.newaxis]
rs = imresize_many_images(
image[np.newaxis, :, :, :], sizes, interpolation=interpolation)
if grayscale:
return rs[0, :, :, 0]
return rs[0, ...]
def pool(arr, block_size, func, pad_mode="constant", pad_cval=0,
preserve_dtype=True, cval=None):
"""Resize an array by pooling values within blocks.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; tested
* ``uint32``: yes; tested (2)
* ``uint64``: no (1)
* ``int8``: yes; tested
* ``int16``: yes; tested
* ``int32``: yes; tested (2)
* ``int64``: no (1)
* ``float16``: yes; tested
* ``float32``: yes; tested
* ``float64``: yes; tested
* ``float128``: yes; tested (2)
* ``bool``: yes; tested
- (1) results too inaccurate (at least when using np.average as func)
- (2) Note that scikit-image documentation says that the wrapped
pooling function converts inputs to ``float64``. Actual tests
showed no indication of that happening (at least when using
preserve_dtype=True).
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pool. Ideally of datatype ``float64``.
block_size : int or tuple of int
Spatial size of each group of values to pool, aka kernel size.
* If a single ``int``, then a symmetric block of that size along
height and width will be used.
* If a ``tuple`` of two values, it is assumed to be the block size
along height and width of the image-like, with pooling happening
per channel.
* If a ``tuple`` of three values, it is assumed to be the block size
along height, width and channels.
func : callable
Function to apply to a given block in order to convert it to a single
number, e.g. :func:`numpy.average`, :func:`numpy.min`,
:func:`numpy.max`.
pad_mode : str, optional
Padding mode to use if the array cannot be divided by `block_size`
without remainder. See :func:`~imgaug.imgaug.pad` for details.
pad_cval : number, optional
Value to use for padding if `mode` is ``constant``.
See :func:`numpy.pad` for details.
preserve_dtype : bool, optional
Whether to convert the array back to the input datatype if it is
changed away from that in the pooling process.
cval : None or number, optional
Deprecated. Old name for `pad_cval`.
Returns
-------
(H',W') ndarray or (H',W',C') ndarray
Array after pooling.
"""
# TODO find better way to avoid circular import
from . import dtypes as iadt
from .augmenters import size as iasize
if arr.size == 0:
return np.copy(arr)
iadt.gate_dtypes_strs(
{arr.dtype},
allowed="bool uint8 uint16 uint32 int8 int16 int32 "
"float16 float32 float64 float128",
disallowed="uint64 int64"
)
if cval is not None:
warn_deprecated("`cval` is a deprecated argument in pool(). "
"Use `pad_cval` instead.")
pad_cval = cval
_assert_two_or_three_dims(arr)
is_valid_int = is_single_integer(block_size) and block_size >= 1
is_valid_tuple = is_iterable(block_size) and len(block_size) in [2, 3] \
and [is_single_integer(val) and val >= 1 for val in block_size]
assert is_valid_int or is_valid_tuple, (
"Expected argument 'block_size' to be a single integer >0 or "
"a tuple of 2 or 3 values with each one being >0. Got %s." % (
str(block_size)))
if is_single_integer(block_size):
block_size = [block_size, block_size]
if len(block_size) < arr.ndim:
block_size = list(block_size) + [1]
# We use custom padding here instead of the one from block_reduce(),
# because (1) it is expected to be faster and (2) it allows us more
# flexibility wrt to padding modes.
arr = iasize.pad_to_multiples_of(
arr,
height_multiple=block_size[0],
width_multiple=block_size[1],
mode=pad_mode,
cval=pad_cval
)
input_dtype = arr.dtype
arr_reduced = skimage.measure.block_reduce(arr, tuple(block_size), func,
cval=cval)
if preserve_dtype and arr_reduced.dtype.name != input_dtype.name:
arr_reduced = arr_reduced.astype(input_dtype)
return arr_reduced
# This automatically calls a special uint8 method if it fulfills standard
# cv2 criteria. Otherwise it falls back to pool().
# Added in 0.5.0.
def _pool_dispatcher_(arr, block_size, func_uint8, blockfunc, pad_mode="edge",
pad_cval=255, preserve_dtype=True, cval=None,
copy=False):
if not isinstance(block_size, (tuple, list)):
block_size = (block_size, block_size)
if 0 in block_size:
return arr if not copy else np.copy(arr)
shape = arr.shape
nb_channels = 0 if len(shape) <= 2 else shape[-1]
valid_for_cv2 = (
arr.dtype.name == "uint8"
and len(block_size) == 2
and nb_channels <= 512
and 0 not in shape
)
if valid_for_cv2:
return func_uint8(arr, block_size, pad_mode=pad_mode,
pad_cval=pad_cval if cval is None else cval)
return pool(arr, block_size, blockfunc, pad_mode=pad_mode,
pad_cval=pad_cval, preserve_dtype=preserve_dtype, cval=cval)
def avg_pool(arr, block_size, pad_mode="reflect", pad_cval=128,
preserve_dtype=True, cval=None):
"""Resize an array using average pooling.
Defaults to ``pad_mode="reflect"`` to ensure that padded values do not
affect the average.
.. note::
This function currently rounds ``0.5`` up for (most) ``uint8``
images, but rounds it down for other dtypes.
**Supported dtypes**:
See :func:`~imgaug.imgaug.pool`.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pool.
See :func:`~imgaug.imgaug.pool` for details.
block_size : int or tuple of int
Size of each block of values to pool.
See :func:`~imgaug.imgaug.pool` for details.
pad_mode : str, optional
Padding mode to use if the array cannot be divided by `block_size`
without remainder.
See :func:`~imgaug.imgaug.pad` for details.
pad_cval : number, optional
Padding value.
See :func:`~imgaug.imgaug.pool` for details.
preserve_dtype : bool, optional
Whether to preserve the input array dtype.
See :func:`~imgaug.imgaug.pool` for details.
cval : None or number, optional
Deprecated. Old name for `pad_cval`.
Returns
-------
(H',W') ndarray or (H',W',C') ndarray
Array after average pooling.
"""
return _pool_dispatcher_(
arr,
block_size,
_avg_pool_uint8,
np.average,
pad_mode=pad_mode,
pad_cval=pad_cval,
preserve_dtype=preserve_dtype,
cval=cval,
copy=True
)
# Added in 0.5.0.
def _avg_pool_uint8(arr, block_size, pad_mode="reflect", pad_cval=128):
from imgaug.augmenters.size import pad_to_multiples_of
ndim_in = arr.ndim
shape = arr.shape
if shape[0] % block_size[0] != 0 or shape[1] % block_size[1] != 0:
arr = pad_to_multiples_of(
arr,
height_multiple=block_size[0],
width_multiple=block_size[1],
mode=pad_mode,
cval=pad_cval
)
height = arr.shape[0] // block_size[0]
width = arr.shape[1] // block_size[1]
arr = cv2.resize(arr, (width, height), interpolation=cv2.INTER_AREA)
if arr.ndim < ndim_in:
arr = arr[:, :, np.newaxis]
return arr
def max_pool(arr, block_size, pad_mode="edge", pad_cval=0,
preserve_dtype=True, cval=None):
"""Resize an array using max-pooling.
Defaults to ``pad_mode="edge"`` to ensure that padded values do not affect
the maximum, even if the dtype was something else than ``uint8``.
**Supported dtypes**:
See :func:`~imgaug.imgaug.pool`.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pool.
See :func:`~imgaug.imgaug.pool` for details.
block_size : int or tuple of int
Size of each block of values to pool.
See :func:`~imgaug.imgaug.pool` for details.
pad_mode : str, optional
Padding mode to use if the array cannot be divided by `block_size`
without remainder.
See :func:`~imgaug.imgaug.pad` for details.
pad_cval : number, optional
Padding value.
See :func:`~imgaug.imgaug.pool` for details.
preserve_dtype : bool, optional
Whether to preserve the input array dtype.
See :func:`~imgaug.imgaug.pool` for details.
cval : None or number, optional
Deprecated. Old name for `pad_cval`.
Returns
-------
(H',W') ndarray or (H',W',C') ndarray
Array after max-pooling.
"""
return max_pool_(np.copy(arr), block_size, pad_mode=pad_mode,
pad_cval=pad_cval, preserve_dtype=preserve_dtype,
cval=cval)
def max_pool_(arr, block_size, pad_mode="edge", pad_cval=0,
preserve_dtype=True, cval=None):
"""Resize an array in-place using max-pooling.
Defaults to ``pad_mode="edge"`` to ensure that padded values do not affect
the maximum, even if the dtype was something else than ``uint8``.
Added in 0.5.0.
**Supported dtypes**:
See :func:`~imgaug.imgaug.pool`.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pool.
May be altered in-place.
See :func:`~imgaug.imgaug.pool` for details.
block_size : int or tuple of int
Size of each block of values to pool.
See :func:`~imgaug.imgaug.pool` for details.
pad_mode : str, optional
Padding mode to use if the array cannot be divided by `block_size`
without remainder.
See :func:`~imgaug.imgaug.pad` for details.
pad_cval : number, optional
Padding value.
See :func:`~imgaug.imgaug.pool` for details.
preserve_dtype : bool, optional
Whether to preserve the input array dtype.
See :func:`~imgaug.imgaug.pool` for details.
cval : None or number, optional
Deprecated. Old name for `pad_cval`.
Returns
-------
(H',W') ndarray or (H',W',C') ndarray
Array after max-pooling.
Might be a view of `arr`.
"""
return _pool_dispatcher_(
arr,
block_size,
_max_pool_uint8_,
np.max,
pad_mode=pad_mode,
pad_cval=pad_cval,
preserve_dtype=preserve_dtype,
cval=cval
)
def min_pool(arr, block_size, pad_mode="edge", pad_cval=255,
preserve_dtype=True):
"""Resize an array using min-pooling.
Defaults to ``pad_mode="edge"`` to ensure that padded values do not affect
the minimum, even if the dtype was something else than ``uint8``.
**Supported dtypes**:
See :func:`~imgaug.imgaug.pool`.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pool.
See :func:`~imgaug.imgaug.pool` for details.
block_size : int or tuple of int
Size of each block of values to pool.
See :func:`~imgaug.imgaug.pool` for details.
pad_mode : str, optional
Padding mode to use if the array cannot be divided by `block_size`
without remainder.
See :func:`~imgaug.imgaug.pad` for details.
pad_cval : number, optional
Padding value.
See :func:`~imgaug.imgaug.pool` for details.
preserve_dtype : bool, optional
Whether to preserve the input array dtype.
See :func:`~imgaug.imgaug.pool` for details.
Returns
-------
(H',W') ndarray or (H',W',C') ndarray
Array after min-pooling.
"""
return min_pool_(np.copy(arr), block_size, pad_mode=pad_mode,
pad_cval=pad_cval, preserve_dtype=preserve_dtype)
def min_pool_(arr, block_size, pad_mode="edge", pad_cval=255,
preserve_dtype=True):
"""Resize an array in-place using min-pooling.
Defaults to ``pad_mode="edge"`` to ensure that padded values do not affect
the minimum, even if the dtype was something else than ``uint8``.
Added in 0.5.0.
**Supported dtypes**:
See :func:`~imgaug.imgaug.pool`.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pool.
May be altered in-place.
See :func:`~imgaug.imgaug.pool` for details.
block_size : int or tuple of int
Size of each block of values to pool.
See :func:`~imgaug.imgaug.pool` for details.
pad_mode : str, optional
Padding mode to use if the array cannot be divided by `block_size`
without remainder.
See :func:`~imgaug.imgaug.pad` for details.
pad_cval : number, optional
Padding value.
See :func:`~imgaug.imgaug.pool` for details.
preserve_dtype : bool, optional
Whether to preserve the input array dtype.
See :func:`~imgaug.imgaug.pool` for details.
Returns
-------
(H',W') ndarray or (H',W',C') ndarray
Array after min-pooling.
Might be a view of `arr`.
"""
return _pool_dispatcher_(
arr,
block_size,
_min_pool_uint8_,
np.min,
pad_mode=pad_mode,
pad_cval=pad_cval,
preserve_dtype=preserve_dtype
)
# Added in 0.5.0.
def _min_pool_uint8_(arr, block_size, pad_mode="edge", pad_cval=255):
return _minmax_pool_uint8_(arr, block_size, cv2.erode,
pad_mode=pad_mode, pad_cval=pad_cval)
# Added in 0.5.0.
def _max_pool_uint8_(arr, block_size, pad_mode="edge", pad_cval=0):
return _minmax_pool_uint8_(arr, block_size, cv2.dilate,
pad_mode=pad_mode, pad_cval=pad_cval)
# Added in 0.5.0.
def _minmax_pool_uint8_(arr, block_size, func, pad_mode, pad_cval):
from imgaug.augmenters.size import pad_to_multiples_of
ndim_in = arr.ndim
shape = arr.shape
if shape[0] % block_size[0] != 0 or shape[1] % block_size[1] != 0:
arr = pad_to_multiples_of(
arr,
height_multiple=block_size[0],
width_multiple=block_size[1],
mode=pad_mode,
cval=pad_cval
)
kernel = globals()["_POOLING_KERNELS_CACHE"].get(block_size, None)
if kernel is None:
kernel = np.ones(block_size, dtype=np.uint8)
if block_size[0] <= 30 and block_size[1] <= 30:
globals()["_POOLING_KERNELS_CACHE"][block_size] = kernel
# TODO why was this done with image flips instead of kernel flips?
arr = cv2.flip(arr, -1)
arr = func(arr, kernel, iterations=1)
arr = cv2.flip(arr, -1)
if arr.ndim < ndim_in:
arr = arr[:, :, np.newaxis]
start_height = (block_size[0] - 1) // 2
start_width = (block_size[1] - 1) // 2
return arr[start_height::block_size[0], start_width::block_size[1]]
def median_pool(arr, block_size, pad_mode="reflect", pad_cval=128,
preserve_dtype=True):
"""Resize an array using median-pooling.
Defaults to ``pad_mode="reflect"`` to ensure that padded values do not
affect the average.
**Supported dtypes**:
See :func:`~imgaug.imgaug.pool`.
Parameters
----------
arr : (H,W) ndarray or (H,W,C) ndarray
Image-like array to pool.
See :func:`~imgaug.imgaug.pool` for details.
block_size : int or tuple of int
Size of each block of values to pool.
See :func:`~imgaug.imgaug.pool` for details.
pad_mode : str, optional
Padding mode to use if the array cannot be divided by `block_size`
without remainder.
See :func:`~imgaug.imgaug.pad` for details.
pad_cval : number, optional
Padding value.
See :func:`~imgaug.imgaug.pool` for details.
preserve_dtype : bool, optional
Whether to preserve the input array dtype.
See :func:`~imgaug.imgaug.pool` for details.
Returns
-------
(H',W') ndarray or (H',W',C') ndarray
Array after min-pooling.
"""
# This uses a custom dispatcher (compared to avg/min/max pool), because
# cv2 medianBlur only works with odd kernel sizes > 1, does not support
# height/width-wise ksizes and uses a different method for ksizes > 5
# leading to different performance characteristics for ksizes <= 5 and
# ksizes > 5.
if not isinstance(block_size, (tuple, list)):
block_size = (block_size, block_size)
if 0 in block_size:
return np.copy(arr)
shape = arr.shape
nb_channels = 0 if len(shape) <= 2 else shape[-1]
valid_for_cv2 = (
arr.dtype.name == "uint8"
and len(block_size) == 2
and block_size[0] == block_size[1]
and (
block_size[0] in [3, 5]
or (
block_size[0] in [7, 9, 11, 13]
and (shape[0] * shape[1]) <= (32 * 32)
)
)
and nb_channels <= 512
and 0 not in shape
)
if valid_for_cv2:
return _median_pool_cv2(arr, block_size[0], pad_mode=pad_mode,
pad_cval=pad_cval)
return pool(arr, block_size, np.median, pad_mode=pad_mode,
pad_cval=pad_cval, preserve_dtype=preserve_dtype)
# block_size must be a single integer here, in contrast to the other cv2
# pool methods that support (int, int).
# Added in 0.5.0.
def _median_pool_cv2(arr, block_size, pad_mode, pad_cval):
from imgaug.augmenters.size import pad_to_multiples_of
ndim_in = arr.ndim
shape = arr.shape
if shape[0] % block_size != 0 or shape[1] % block_size != 0:
arr = pad_to_multiples_of(
arr,
height_multiple=block_size,
width_multiple=block_size,
mode=pad_mode,
cval=pad_cval
)
arr = cv2.medianBlur(arr, block_size)
if arr.ndim < ndim_in:
arr = arr[:, :, np.newaxis]
start_height = (block_size - 1) // 2
start_width = (block_size - 1) // 2
return arr[start_height::block_size, start_width::block_size]
def draw_grid(images, rows=None, cols=None):
"""Combine multiple images into a single grid-like image.
Calling this function with four images of the same shape and ``rows=2``,
``cols=2`` will combine the four images to a single image array of shape
``(2*H, 2*W, C)``, where ``H`` is the height of any of the images
(analogous ``W``) and ``C`` is the number of channels of any image.
Calling this function with four images of the same shape and ``rows=4``,
``cols=1`` is analogous to calling :func:`numpy.vstack` on the images.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: yes; fully tested
* ``uint32``: yes; fully tested
* ``uint64``: yes; fully tested
* ``int8``: yes; fully tested
* ``int16``: yes; fully tested
* ``int32``: yes; fully tested
* ``int64``: yes; fully tested
* ``float16``: yes; fully tested
* ``float32``: yes; fully tested
* ``float64``: yes; fully tested
* ``float128``: yes; fully tested
* ``bool``: yes; fully tested
Parameters
----------
images : (N,H,W,3) ndarray or iterable of (H,W,3) array
The input images to convert to a grid.
rows : None or int, optional
The number of rows to show in the grid.
If ``None``, it will be automatically derived.
cols : None or int, optional
The number of cols to show in the grid.
If ``None``, it will be automatically derived.
Returns
-------
(H',W',3) ndarray
Image of the generated grid.
"""
nb_images = len(images)
assert nb_images > 0, "Expected to get at least one image, got none."
if is_np_array(images):
assert images.ndim == 4, (
"Expected to get an array of four dimensions denoting "
"(N, H, W, C), got %d dimensions and shape %s." % (
images.ndim, images.shape))
else:
assert is_iterable(images), (
"Expected to get an iterable of ndarrays, "
"got %s." % (type(images),))
assert all([is_np_array(image) for image in images]), (
"Expected to get an iterable of ndarrays, "
"got types %s." % (
", ".join([str(type(image)) for image in images],)))
assert all([image.ndim == 3 for image in images]), (
"Expected to get images with three dimensions. Got shapes %s." % (
", ".join([str(image.shape) for image in images])))
assert len({image.dtype.name for image in images}) == 1, (
"Expected to get images with the same dtypes, got dtypes %s." % (
", ".join([image.dtype.name for image in images])))
assert len({image.shape[-1] for image in images}) == 1, (
"Expected to get images with the same number of channels, "
"got shapes %s." % (
", ".join([str(image.shape) for image in images])))
cell_height = max([image.shape[0] for image in images])
cell_width = max([image.shape[1] for image in images])
nb_channels = images[0].shape[2]
if rows is None and cols is None:
rows = cols = int(math.ceil(math.sqrt(nb_images)))
elif rows is not None:
cols = int(math.ceil(nb_images / rows))
elif cols is not None:
rows = int(math.ceil(nb_images / cols))
assert rows * cols >= nb_images, (
"Expected rows*cols to lead to at least as many cells as there were "
"images provided, but got %d rows, %d cols (=%d cells) for %d "
"images. " % (rows, cols, rows*cols, nb_images))
width = cell_width * cols
height = cell_height * rows
dtype = images.dtype if is_np_array(images) else images[0].dtype
grid = np.zeros((height, width, nb_channels), dtype=dtype)
cell_idx = 0
for row_idx in sm.xrange(rows):
for col_idx in sm.xrange(cols):
if cell_idx < nb_images:
image = images[cell_idx]
cell_y1 = cell_height * row_idx
cell_y2 = cell_y1 + image.shape[0]
cell_x1 = cell_width * col_idx
cell_x2 = cell_x1 + image.shape[1]
grid[cell_y1:cell_y2, cell_x1:cell_x2, :] = image
cell_idx += 1
return grid
def show_grid(images, rows=None, cols=None):
"""Combine multiple images into a single image and plot the result.
This will show a window of the results of :func:`~imgaug.imgaug.draw_grid`.
**Supported dtypes**:
minimum of (
:func:`~imgaug.imgaug.draw_grid`,
:func:`~imgaug.imgaug.imshow`
)
Parameters
----------
images : (N,H,W,3) ndarray or iterable of (H,W,3) array
See :func:`~imgaug.imgaug.draw_grid`.
rows : None or int, optional
See :func:`~imgaug.imgaug.draw_grid`.
cols : None or int, optional
See :func:`~imgaug.imgaug.draw_grid`.
"""
grid = draw_grid(images, rows=rows, cols=cols)
imshow(grid)
def imshow(image, backend=IMSHOW_BACKEND_DEFAULT):
"""Show an image in a window.
**Supported dtypes**:
* ``uint8``: yes; not tested
* ``uint16``: ?
* ``uint32``: ?
* ``uint64``: ?
* ``int8``: ?
* ``int16``: ?
* ``int32``: ?
* ``int64``: ?
* ``float16``: ?
* ``float32``: ?
* ``float64``: ?
* ``float128``: ?
* ``bool``: ?
Parameters
----------
image : (H,W,3) ndarray
Image to show.
backend : {'matplotlib', 'cv2'}, optional
Library to use to show the image. May be either matplotlib or
OpenCV ('cv2'). OpenCV tends to be faster, but apparently causes more
technical issues.
"""
assert backend in ["matplotlib", "cv2"], (
"Expected backend 'matplotlib' or 'cv2', got %s." % (backend,))
if backend == "cv2":
image_bgr = image
if image.ndim == 3 and image.shape[2] in [3, 4]:
image_bgr = image[..., 0:3][..., ::-1]
win_name = "imgaug-default-window"
cv2.namedWindow(win_name, cv2.WINDOW_NORMAL)
cv2.imshow(win_name, image_bgr)
cv2.waitKey(0)
cv2.destroyWindow(win_name)
else:
# import only when necessary (faster startup; optional dependency;
# less fragile -- see issue #225)
import matplotlib.pyplot as plt
dpi = 96
h, w = image.shape[0] / dpi, image.shape[1] / dpi
# if the figure is too narrow, the footer may appear and make the fig
# suddenly wider (ugly)
w = max(w, 6)
fig, ax = plt.subplots(figsize=(w, h), dpi=dpi)
fig.canvas.set_window_title("imgaug.imshow(%s)" % (image.shape,))
# cmap=gray is automatically only activate for grayscale images
ax.imshow(image, cmap="gray")
plt.show()
def do_assert(condition, message="Assertion failed."):
"""Assert that a ``condition`` holds or raise an ``Exception`` otherwise.
This was added because `assert` statements are removed in optimized code.
It replaced `assert` statements throughout the library, but that was
reverted again for readability and performance reasons.
Parameters
----------
condition : bool
If ``False``, an exception is raised.
message : str, optional
Error message.
"""
if not condition:
raise AssertionError(str(message))
# Added in 0.4.0.
def _normalize_cv2_input_arr_(arr):
flags = arr.flags
if not flags["OWNDATA"]:
arr = np.copy(arr)
flags = arr.flags
if not flags["C_CONTIGUOUS"]:
arr = np.ascontiguousarray(arr)
return arr
def apply_lut(image, table):
"""Map an input image to a new one using a lookup table.
Added in 0.4.0.
**Supported dtypes**:
See :func:`~imgaug.imgaug.apply_lut_`.
Parameters
----------
image : ndarray
See :func:`~imgaug.imgaug.apply_lut_`.
table : ndarray or list of ndarray
See :func:`~imgaug.imgaug.apply_lut_`.
Returns
-------
ndarray
Image after mapping via lookup table.
"""
return apply_lut_(np.copy(image), table)
# TODO make this function compatible with short max sized images, probably
# isn't right now
def apply_lut_(image, table):
"""Map an input image in-place to a new one using a lookup table.
Added in 0.4.0.
**Supported dtypes**:
* ``uint8``: yes; fully tested
* ``uint16``: no
* ``uint32``: no
* ``uint64``: no
* ``int8``: no
* ``int16``: no
* ``int32``: no
* ``int64``: no
* ``float16``: no
* ``float32``: no
* ``float64``: no
* ``float128``: no
* ``bool``: no
Parameters
----------
image : ndarray
Image of dtype ``uint8`` and shape ``(H,W)`` or ``(H,W,C)``.
table : ndarray or list of ndarray
Table of dtype ``uint8`` containing the mapping from old to new
values. Either a ``list`` of ``C`` ``(256,)`` arrays or a single
array of shape ``(256,)`` or ``(256, C)`` or ``(1, 256, C)``.
In case of ``(256,)`` the same table is used for all channels,
otherwise a channelwise table is used and ``C`` is expected to match
the number of channels.
Returns
-------
ndarray
Image after mapping via lookup table.
This *might* be the same array instance as provided via `image`.
"""
image_shape_orig = image.shape
nb_channels = 1 if len(image_shape_orig) == 2 else image_shape_orig[-1]
if 0 in image_shape_orig:
return image
image = _normalize_cv2_input_arr_(image)
# [(256,), (256,), ...] => (256, C)
if isinstance(table, list):
assert len(table) == nb_channels, (
"Expected to get %d tables (one per channel), got %d instead." % (
nb_channels, len(table)))
table = np.stack(table, axis=-1)
# (256, C) => (1, 256, C)
if table.shape == (256, nb_channels):
table = table[np.newaxis, :, :]
assert table.shape == (256,) or table.shape == (1, 256, nb_channels), (
"Expected 'table' to be any of the following: "
"A list of C (256,) arrays, an array of shape (256,), an array of "
"shape (256, C), an array of shape (1, 256, C). Transformed 'table' "
"up to shape %s for image with shape %s (C=%d)." % (
table.shape, image_shape_orig, nb_channels))
if nb_channels > 512:
if table.shape == (256,):
table = np.tile(table[np.newaxis, :, np.newaxis],
(1, 1, nb_channels))
subluts = []
for group_idx in np.arange(int(np.ceil(nb_channels / 512))):
c_start = group_idx * 512
c_end = c_start + 512
subluts.append(apply_lut_(image[:, :, c_start:c_end],
table[:, :, c_start:c_end]))
return np.concatenate(subluts, axis=2)
assert image.dtype == _UINT8_DTYPE, (
"Expected uint8 image, got dtype %s." % (image.dtype.name,))
image = cv2.LUT(image, table, dst=image)
return image
# Added in 0.5.0.
def _identity_decorator(*_dec_args, **_dec_kwargs):
def _decorator(func):
@functools.wraps(func)
def _wrapper(*args, **kwargs):
return func(*args, **kwargs)
return _wrapper
return _decorator
# Added in 0.5.0.
if numba is not None:
_numbajit = numba.jit
else:
_numbajit = _identity_decorator
class HooksImages(object):
"""Class to intervene with image augmentation runs.
This is e.g. useful to dynamically deactivate some augmenters.
Parameters
----------
activator : None or callable, optional
A function that gives permission to execute an augmenter.
The expected interface is::
``f(images, augmenter, parents, default)``
where ``images`` are the input images to augment, ``augmenter`` is the
instance of the augmenter to execute, ``parents`` are previously
executed augmenters and ``default`` is an expected default value to be
returned if the activator function does not plan to make a decision
for the given inputs.
propagator : None or callable, optional
A function that gives permission to propagate the augmentation further
to the children of an augmenter. This happens after the activator.
In theory, an augmenter may augment images itself (if allowed by the
activator) and then execute child augmenters afterwards (if allowed by
the propagator). If the activator returned ``False``, the propagation
step will never be executed.
The expected interface is::
``f(images, augmenter, parents, default)``
with all arguments having identical meaning to the activator.
preprocessor : None or callable, optional
A function to call before an augmenter performed any augmentations.
The interface is:
``f(images, augmenter, parents)``
with all arguments having identical meaning to the activator.
It is expected to return the input images, optionally modified.
postprocessor : None or callable, optional
A function to call after an augmenter performed augmentations.
The interface is the same as for the `preprocessor`.
Examples
--------
>>> import numpy as np
>>> import imgaug as ia
>>> import imgaug.augmenters as iaa
>>> seq = iaa.Sequential([
>>> iaa.GaussianBlur(3.0, name="blur"),
>>> iaa.Dropout(0.05, name="dropout"),
>>> iaa.Affine(translate_px=-5, name="affine")
>>> ])
>>> images = [np.zeros((10, 10), dtype=np.uint8)]
>>>
>>> def activator(images, augmenter, parents, default):
>>> return False if augmenter.name in ["blur", "dropout"] else default
>>>
>>> seq_det = seq.to_deterministic()
>>> images_aug = seq_det.augment_images(images)
>>> heatmaps = [np.random.rand(*(3, 10, 10))]
>>> heatmaps_aug = seq_det.augment_images(
>>> heatmaps,
>>> hooks=ia.HooksImages(activator=activator)
>>> )
This augments images and their respective heatmaps in the same way.
The heatmaps however are only modified by ``Affine``, not by
``GaussianBlur`` or ``Dropout``.
"""
def __init__(self, activator=None, propagator=None, preprocessor=None,
postprocessor=None):
self.activator = activator
self.propagator = propagator
self.preprocessor = preprocessor
self.postprocessor = postprocessor
def is_activated(self, images, augmenter, parents, default):
"""Estimate whether an augmenter may be executed.
This also affects propagation of data to child augmenters.
Returns
-------
bool
If ``True``, the augmenter may be executed.
Otherwise ``False``.
"""
if self.activator is None:
return default
return self.activator(images, augmenter, parents, default)
def is_propagating(self, images, augmenter, parents, default):
"""Estimate whether an augmenter may call its children.
This function decides whether an augmenter with children is allowed
to call these in order to further augment the inputs.
Note that if the augmenter itself performs augmentations (before/after
calling its children), these may still be executed, even if this
method returns ``False``.
Returns
-------
bool
If ``True``, the augmenter may propagate data to its children.
Otherwise ``False``.
"""
if self.propagator is None:
return default
return self.propagator(images, augmenter, parents, default)
def preprocess(self, images, augmenter, parents):
"""Preprocess input data per augmenter before augmentation.
Returns
-------
(N,H,W,C) ndarray or (N,H,W) ndarray or list of (H,W,C) ndarray or list of (H,W) ndarray
The input images, optionally modified.
"""
if self.preprocessor is None:
return images
return self.preprocessor(images, augmenter, parents)
def postprocess(self, images, augmenter, parents):
"""Postprocess input data per augmenter after augmentation.
Returns
-------
(N,H,W,C) ndarray or (N,H,W) ndarray or list of (H,W,C) ndarray or list of (H,W) ndarray
The input images, optionally modified.
"""
if self.postprocessor is None:
return images
return self.postprocessor(images, augmenter, parents)
class HooksHeatmaps(HooksImages):
"""Class to intervene with heatmap augmentation runs.
This is e.g. useful to dynamically deactivate some augmenters.
This class is currently the same as the one for images. This may or may
not change in the future.
"""
class HooksKeypoints(HooksImages):
"""Class to intervene with keypoint augmentation runs.
This is e.g. useful to dynamically deactivate some augmenters.
This class is currently the same as the one for images. This may or may
not change in the future.
"""
#####################################################################
# Create classes/functions that were moved to other files and create
# DeprecatedWarnings when they are called.
#####################################################################
def _mark_moved_class_or_function(class_name_old, module_name_new,
class_name_new):
# pylint: disable=redefined-outer-name
class_name_new = (class_name_new
if class_name_new is not None
else class_name_old)
def _func(*args, **kwargs):
import importlib
warn_deprecated(
"Using imgaug.imgaug.%s is deprecated. Use %s.%s instead." % (
class_name_old, module_name_new, class_name_new
))
module = importlib.import_module(module_name_new)
return getattr(module, class_name_new)(*args, **kwargs)
return _func
MOVED = [
("Keypoint", "imgaug.augmentables.kps", None),
("KeypointsOnImage", "imgaug.augmentables.kps", None),
("BoundingBox", "imgaug.augmentables.bbs", None),
("BoundingBoxesOnImage", "imgaug.augmentables.bbs", None),
("Polygon", "imgaug.augmentables.polys", None),
("PolygonsOnImage", "imgaug.augmentables.polys", None),
("MultiPolygon", "imgaug.augmentables.polys", None),
("_ConcavePolygonRecoverer", "imgaug.augmentables.polys", None),
("HeatmapsOnImage", "imgaug.augmentables.heatmaps", None),
("SegmentationMapsOnImage", "imgaug.augmentables.segmaps", None),
("Batch", "imgaug.augmentables.batches", None),
("BatchLoader", "imgaug.multicore", None),
("BackgroundAugmenter", "imgaug.multicore", None),
("compute_geometric_median", "imgaug.augmentables.kps", None),
("_convert_points_to_shapely_line_string", "imgaug.augmentables.polys",
None),
("_interpolate_point_pair", "imgaug.augmentables.polys", None),
("_interpolate_points", "imgaug.augmentables.polys", None),
("_interpolate_points_by_max_distance", "imgaug.augmentables.polys", None),
("pad", "imgaug.augmenters.size", None),
("pad_to_aspect_ratio", "imgaug.augmenters.size", None),
("pad_to_multiples_of", "imgaug.augmenters.size", None),
("compute_paddings_for_aspect_ratio", "imgaug.augmenters.size",
"compute_paddings_to_reach_aspect_ratio"),
("compute_paddings_to_reach_multiples_of", "imgaug.augmenters.size", None),
("compute_paddings_to_reach_exponents_of", "imgaug.augmenters.size", None),
("quokka", "imgaug.data", None),
("quokka_square", "imgaug.data", None),
("quokka_heatmap", "imgaug.data", None),
("quokka_segmentation_map", "imgaug.data", None),
("quokka_keypoints", "imgaug.data", None),
("quokka_bounding_boxes", "imgaug.data", None),
("quokka_polygons", "imgaug.data", None),
]
for class_name_old, module_name_new, class_name_new in MOVED:
locals()[class_name_old] = _mark_moved_class_or_function(
class_name_old, module_name_new, class_name_new)
================================================
FILE: imgaug/multicore.py
================================================
"""Classes and functions dealing with augmentation on multiple CPU cores."""
from __future__ import print_function, division, absolute_import
import sys
import multiprocessing
import threading
import traceback
import time
import random
import platform
import numpy as np
import cv2
import imgaug.imgaug as ia
import imgaug.random as iarandom
from imgaug.augmentables.batches import Batch, UnnormalizedBatch
if sys.version_info[0] == 2:
# pylint: disable=redefined-builtin, import-error
import cPickle as pickle
from Queue import Empty as QueueEmpty, Full as QueueFull
import socket
BrokenPipeError = socket.error
elif sys.version_info[0] == 3:
import pickle
from queue import Empty as QueueEmpty, Full as QueueFull
_CONTEXT = None
# Added in 0.4.0.
def _get_context_method():
vinfo = sys.version_info
# get_context() is only supported in 3.5 and later (same for
# set_start_method)
get_context_unsupported = (
vinfo[0] == 2
or (vinfo[0] == 3 and vinfo[1] <= 3))
method = None
# Fix random hanging code in NixOS by switching to spawn method,
# see issue #414
# TODO This is only a workaround and doesn't really fix the underlying
# issue. The cause of the underlying issue is currently unknown.
# Its possible that #535 fixes the issue, though earlier tests
# indicated that the cause was something else.
# TODO this might break the semaphore used to prevent out of memory
# errors
if "NixOS" in platform.version():
method = "spawn"
if get_context_unsupported:
ia.warn("Detected usage of imgaug.multicore in python <=3.4 "
"and NixOS. This is known to sometimes cause endlessly "
"hanging programs when also making use of multicore "
"augmentation (aka background augmentation). Use "
"python 3.5 or later to prevent this.")
elif platform.system() == "Darwin" and vinfo[0:2] == (3, 7):
# On Mac with python 3.7 there seems to be a problem with matplotlib,
# resulting in the error "libc++abi.dylib: terminating with uncaught
# exception of type std::runtime_error: Couldn't close file".
# The error seems to be due to opened files that get closed in
# child processes and can be prevented by switching to spawn mode.
# See https://github.com/matplotlib/matplotlib/issues/15410
# and https://bugs.python.org/issue33725.
# It is possible that this problem also affects other python versions,
# but here it only appeared (consistently) in the 3.7 tests and the
# reports also seem to be focused around 3.7, suggesting explicitly
# to update to 3.8.2.
method = "spawn"
if get_context_unsupported:
return False
return method
# Added in 0.4.0.
def _set_context(method):
# method=False indicates that multiprocessing module (i.e. no context)
# should be used, e.g. because get_context() is not supported
globals()["_CONTEXT"] = (
multiprocessing if method is False
else multiprocessing.get_context(method))
# Added in 0.4.0.
def _reset_context():
globals()["_CONTEXT"] = None
# Added in 0.4.0.
def _autoset_context():
_set_context(_get_context_method())
# Added in 0.4.0.
def _get_context():
if _CONTEXT is None:
_autoset_context()
return _CONTEXT
class Pool(object):
"""
Wrapper around ``multiprocessing.Pool`` for multicore augmentation.
Parameters
----------
augseq : imgaug.augmenters.meta.Augmenter
The augmentation sequence to apply to batches.
processes : None or int, optional
The number of background workers, similar to the same parameter in
multiprocessing.Pool. If ``None``, the number of the machine's CPU
cores will be used (this counts hyperthreads as CPU cores). If this is
set to a negative value ``p``, then ``P - abs(p)`` will be used,
where ``P`` is the number of CPU cores. E.g. ``-1`` would use all
cores except one (this is useful to e.g. reserve one core to feed
batches to the GPU).
maxtasksperchild : None or int, optional
The number of tasks done per worker process before the process is
killed and restarted, similar to the same parameter in
multiprocessing.Pool. If ``None``, worker processes will not be
automatically restarted.
seed : None or int, optional
The seed to use for child processes. If ``None``, a random seed will
be used.
"""
# This attribute saves the augmentation sequence for background workers so
# that it does not have to be resend with every batch. The attribute is set
# once per worker in the worker's initializer. As each worker has its own
# process, it is a different variable per worker (though usually should be
# of equal content).
_WORKER_AUGSEQ = None
# This attribute saves the initial seed for background workers so that for
# any future batch the batch's specific seed can be derived, roughly via
# SEED_START+SEED_BATCH. As each worker has its own process, this seed can
# be unique per worker even though all seemingly use the same constant
# attribute.
_WORKER_SEED_START = None
def __init__(self, augseq, processes=None, maxtasksperchild=None,
seed=None):
# make sure that don't call pool again in a child process
assert Pool._WORKER_AUGSEQ is None, (
"_WORKER_AUGSEQ was already set when calling Pool.__init__(). "
"Did you try to instantiate a Pool within a Pool?")
assert processes is None or processes != 0, (
"Expected `processes` to be `None` (\"use as many cores as "
"available\") or a negative integer (\"use as many as available "
"MINUS this number\") or an integer>1 (\"use exactly that many "
"processes\"). Got type %s, value %s instead." % (
type(processes), str(processes))
)
self.augseq = augseq
self.processes = processes
self.maxtasksperchild = maxtasksperchild
if seed is not None:
assert iarandom.SEED_MIN_VALUE <= seed <= iarandom.SEED_MAX_VALUE, (
"Expected `seed` to be either `None` or a value between "
"%d and %d. Got type %s, value %s instead." % (
iarandom.SEED_MIN_VALUE,
iarandom.SEED_MAX_VALUE,
type(seed),
str(seed)
)
)
self.seed = seed
# multiprocessing.Pool instance
self._pool = None
# Running counter of the number of augmented batches. This will be
# used to send indexes for each batch to the workers so that they can
# augment using SEED_BASE+SEED_BATCH and ensure consistency of applied
# augmentation order between script runs.
self._batch_idx = 0
@property
def pool(self):
"""Return or create the ``multiprocessing.Pool`` instance.
This creates a new instance upon the first call and afterwards
returns that instance (until the property ``_pool`` is set to
``None`` again).
Returns
-------
multiprocessing.Pool
The ``multiprocessing.Pool`` used internally by this
``imgaug.multicore.Pool``.
"""
if self._pool is None:
processes = self.processes
if processes is not None and processes < 0:
# cpu count returns the number of logical cpu cores, i.e.
# including hyperthreads could also use
# os.sched_getaffinity(0) here, which seems to not exist on
# BSD though.
# In python 3.4+, there is also os.cpu_count(), which
# multiprocessing.cpu_count() then redirects to.
# At least one guy on stackoverflow.com/questions/1006289
# reported that only os.* existed, not the multiprocessing
# method.
# TODO make this also check if os.cpu_count exists as a
# fallback
try:
processes = _get_context().cpu_count() - abs(processes)
processes = max(processes, 1)
except (ImportError, NotImplementedError):
ia.warn(
"Could not find method multiprocessing.cpu_count(). "
"This will likely lead to more CPU cores being used "
"for the background augmentation than originally "
"intended.")
processes = None
self._pool = _get_context().Pool(
processes,
initializer=_Pool_initialize_worker,
initargs=(self.augseq, self.seed),
maxtasksperchild=self.maxtasksperchild)
return self._pool
def map_batches(self, batches, chunksize=None):
"""
Augment a list of batches.
Parameters
----------
batches : list of imgaug.augmentables.batches.Batch
The batches to augment.
chunksize : None or int, optional
Rough indicator of how many tasks should be sent to each worker.
Increasing this number can improve performance.
Returns
-------
list of imgaug.augmentables.batches.Batch
Augmented batches.
"""
self._assert_batches_is_list(batches)
return self.pool.map(
_Pool_starworker,
self._handle_batch_ids(batches),
chunksize=chunksize)
def map_batches_async(self, batches, chunksize=None, callback=None,
error_callback=None):
"""
Augment batches asynchonously.
Parameters
----------
batches : list of imgaug.augmentables.batches.Batch
The batches to augment.
chunksize : None or int, optional
Rough indicator of how many tasks should be sent to each worker.
Increasing this number can improve performance.
callback : None or callable, optional
Function to call upon finish. See ``multiprocessing.Pool``.
error_callback : None or callable, optional
Function to call upon errors. See ``multiprocessing.Pool``.
Returns
-------
multiprocessing.MapResult
Asynchonous result. See ``multiprocessing.Pool``.
"""
self._assert_batches_is_list(batches)
return self.pool.map_async(
_Pool_starworker,
self._handle_batch_ids(batches),
chunksize=chunksize,
callback=callback,
error_callback=error_callback)
@classmethod
def _assert_batches_is_list(cls, batches):
assert isinstance(batches, list), (
"Expected `batches` to be a list, got type %s. Call "
"imap_batches() if you use generators.") % (type(batches),)
def imap_batches(self, batches, chunksize=1, output_buffer_size=None):
"""
Augment batches from a generator.
Pattern for output buffer constraint is from
https://stackoverflow.com/a/47058399.
Parameters
----------
batches : generator of imgaug.augmentables.batches.Batch
The batches to augment, provided as a generator. Each call to the
generator should yield exactly one batch.
chunksize : None or int, optional
Rough indicator of how many tasks should be sent to each worker.
Increasing this number can improve performance.
output_buffer_size : None or int, optional
Max number of batches to handle *at the same time* in the *whole*
pipeline (including already augmented batches that are waiting to
be requested). If the buffer size is reached, no new batches will
be loaded from `batches` until a produced (i.e. augmented) batch is
consumed (i.e. requested from this method).
The buffer is unlimited if this is set to ``None``. For large
datasets, this should be set to an integer value to avoid filling
the whole RAM if loading+augmentation happens faster than training.
*New in version 0.3.0.*
Yields
------
imgaug.augmentables.batches.Batch
Augmented batch.
"""
self._assert_batches_is_generator(batches)
# buffer is either None or a Semaphore
output_buffer_left = _create_output_buffer_left(output_buffer_size)
# TODO change this to 'yield from' once switched to 3.3+
gen = self.pool.imap(
_Pool_starworker,
self._ibuffer_batch_loading(
self._handle_batch_ids_gen(batches),
output_buffer_left
),
chunksize=chunksize)
for batch in gen:
yield batch
if output_buffer_left is not None:
output_buffer_left.release()
def imap_batches_unordered(self, batches, chunksize=1,
output_buffer_size=None):
"""Augment batches from a generator (without preservation of order).
Pattern for output buffer constraint is from
https://stackoverflow.com/a/47058399.
Parameters
----------
batches : generator of imgaug.augmentables.batches.Batch
The batches to augment, provided as a generator. Each call to the
generator should yield exactly one batch.
chunksize : None or int, optional
Rough indicator of how many tasks should be sent to each worker.
Increasing this number can improve performance.
output_buffer_size : None or int, optional
Max number of batches to handle *at the same time* in the *whole*
pipeline (including already augmented batches that are waiting to
be requested). If the buffer size is reached, no new batches will
be loaded from `batches` until a produced (i.e. augmented) batch is
consumed (i.e. requested from this method).
The buffer is unlimited if this is set to ``None``. For large
datasets, this should be set to an integer value to avoid filling
the whole RAM if loading+augmentation happens faster than training.
*New in version 0.3.0.*
Yields
------
imgaug.augmentables.batches.Batch
Augmented batch.
"""
self._assert_batches_is_generator(batches)
# buffer is either None or a Semaphore
output_buffer_left = _create_output_buffer_left(output_buffer_size)
gen = self.pool.imap_unordered(
_Pool_starworker,
self._ibuffer_batch_loading(
self._handle_batch_ids_gen(batches),
output_buffer_left
),
chunksize=chunksize
)
for batch in gen:
yield batch
if output_buffer_left is not None:
output_buffer_left.release()
@classmethod
def _assert_batches_is_generator(cls, batches):
assert ia.is_generator(batches), (
"Expected `batches` to be generator, got type %s. Call "
"map_batches() if you use lists.") % (type(batches),)
def __enter__(self):
assert self._pool is None, (
"Tried to __enter__ a pool that has already been initialized.")
_ = self.pool # initialize internal multiprocessing pool instance
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
def close(self):
"""Close the pool gracefully."""
if self._pool is not None:
self._pool.close()
self._pool.join()
self._pool = None
def terminate(self):
"""Terminate the pool immediately."""
if self._pool is not None:
self._pool.terminate()
self._pool.join()
self._pool = None
# TODO why does this function exist if it may only be called after
# close/terminate and both of these two already call join() themselves
def join(self):
"""
Wait for the workers to exit.
This may only be called after first calling
:func:`~imgaug.multicore.Pool.close` or
:func:`~imgaug.multicore.Pool.terminate`.
"""
if self._pool is not None:
self._pool.join()
def _handle_batch_ids(self, batches):
ids = np.arange(self._batch_idx, self._batch_idx + len(batches))
inputs = list(zip(ids, batches))
self._batch_idx += len(batches)
return inputs
def _handle_batch_ids_gen(self, batches):
for batch in batches:
batch_idx = self._batch_idx
yield batch_idx, batch
self._batch_idx += 1
@classmethod
def _ibuffer_batch_loading(cls, batches, output_buffer_left):
for batch in batches:
if output_buffer_left is not None:
output_buffer_left.acquire()
yield batch
def _create_output_buffer_left(output_buffer_size):
output_buffer_left = None
if output_buffer_size:
assert output_buffer_size > 0, (
"Expected buffer size to be greater than zero, but got size %d "
"instead." % (output_buffer_size,))
output_buffer_left = _get_context().Semaphore(output_buffer_size)
return output_buffer_left
# This could be a classmethod or staticmethod of Pool in 3.x, but in 2.7 that
# leads to pickle errors.
def _Pool_initialize_worker(augseq, seed_start):
# pylint: disable=invalid-name, protected-access
# Not using this seems to have caused infinite hanging in the case
# of gaussian blur on at least MacOSX.
# It is also in most cases probably not sensible to use multiple
# threads while already running augmentation in multiple processes.
cv2.setNumThreads(0)
if seed_start is None:
# pylint falsely thinks in older versions that
# multiprocessing.current_process() was not callable, see
# https://github.com/PyCQA/pylint/issues/1699
# pylint: disable=not-callable
process_name = _get_context().current_process().name
# pylint: enable=not-callable
# time_ns() exists only in 3.7+
if sys.version_info[0] == 3 and sys.version_info[1] >= 7:
seed_offset = time.time_ns()
else:
seed_offset = int(time.time() * 10**6) % 10**6
seed = hash(process_name) + seed_offset
_reseed_global_local(seed, augseq)
Pool._WORKER_SEED_START = seed_start
Pool._WORKER_AUGSEQ = augseq
# not sure if really necessary, but shouldn't hurt either
Pool._WORKER_AUGSEQ.localize_random_state_()
# This could be a classmethod or staticmethod of Pool in 3.x, but in 2.7 that
# leads to pickle errors.
def _Pool_worker(batch_idx, batch):
# pylint: disable=invalid-name, protected-access
assert ia.is_single_integer(batch_idx), (
"Expected `batch_idx` to be an integer. Got type %s instead." % (
type(batch_idx)
))
assert isinstance(batch, (UnnormalizedBatch, Batch)), (
"Expected `batch` to be either an instance of "
"`imgaug.augmentables.batches.UnnormalizedBatch` or "
"`imgaug.augmentables.batches.Batch`. Got type %s instead." % (
type(batch)
))
assert Pool._WORKER_AUGSEQ is not None, (
"Expected `Pool._WORKER_AUGSEQ` to NOT be `None`. Did you manually "
"call _Pool_worker()?")
augseq = Pool._WORKER_AUGSEQ
# TODO why is this if here? _WORKER_SEED_START should always be set?
if Pool._WORKER_SEED_START is not None:
seed = Pool._WORKER_SEED_START + batch_idx
_reseed_global_local(seed, augseq)
result = augseq.augment_batch_(batch)
return result
# could be a classmethod or staticmethod of Pool in 3.x, but in 2.7 that leads
# to pickle errors starworker is here necessary, because starmap does not exist
# in 2.7
def _Pool_starworker(inputs):
# pylint: disable=invalid-name
return _Pool_worker(*inputs)
def _reseed_global_local(base_seed, augseq):
seed_global = _derive_seed(base_seed, -10**9)
seed_local = _derive_seed(base_seed)
iarandom.seed(seed_global)
augseq.seed_(seed_local)
def _derive_seed(base_seed, offset=0):
return (
iarandom.SEED_MIN_VALUE
+ (base_seed + offset)
% (iarandom.SEED_MAX_VALUE - iarandom.SEED_MIN_VALUE)
)
class BatchLoader(object):
"""**Deprecated**. Load batches in the background.
Deprecated. Use ``imgaug.multicore.Pool`` instead.
Loaded batches can be accesses using :attr:`imgaug.BatchLoader.queue`.
Parameters
----------
load_batch_func : callable or generator
Generator or generator function (i.e. function that yields Batch
objects) or a function that returns a list of Batch objects.
Background loading automatically stops when the last batch was yielded
or the last batch in the list was reached.
queue_size : int, optional
Maximum number of batches to store in the queue. May be set higher
for small images and/or small batches.
nb_workers : int, optional
Number of workers to run in the background.
threaded : bool, optional
Whether to run the background processes using threads (True) or full
processes (False).
"""
@ia.deprecated(alt_func="imgaug.multicore.Pool")
def __init__(self, load_batch_func, queue_size=50, nb_workers=1,
threaded=True):
assert queue_size >= 2, (
"Queue size for BatchLoader must be at least 2, "
"got %d." % (queue_size,))
assert nb_workers >= 1, (
"Number of workers for BatchLoader must be at least 1, "
"got %d" % (nb_workers,))
self._queue_internal = multiprocessing.Queue(queue_size//2)
self.queue = multiprocessing.Queue(queue_size//2)
self.join_signal = multiprocessing.Event()
self.workers = []
self.threaded = threaded
seeds = iarandom.get_global_rng().generate_seeds_(nb_workers)
for i in range(nb_workers):
if threaded:
worker = threading.Thread(
target=self._load_batches,
args=(load_batch_func, self._queue_internal,
self.join_signal, None)
)
else:
worker = multiprocessing.Process(
target=self._load_batches,
args=(load_batch_func, self._queue_internal,
self.join_signal, seeds[i])
)
worker.daemon = True
worker.start()
self.workers.append(worker)
self.main_worker_thread = threading.Thread(
target=self._main_worker,
args=()
)
self.main_worker_thread.daemon = True
self.main_worker_thread.start()
def count_workers_alive(self):
return sum([int(worker.is_alive()) for worker in self.workers])
def all_finished(self):
"""
Determine whether the workers have finished the loading process.
Returns
-------
out : bool
True if all workers have finished. Else False.
"""
return self.count_workers_alive() == 0
def _main_worker(self):
workers_running = self.count_workers_alive()
while workers_running > 0 and not self.join_signal.is_set():
# wait for a new batch in the source queue and load it
try:
batch_str = self._queue_internal.get(timeout=0.1)
if batch_str == "":
workers_running -= 1
else:
self.queue.put(batch_str)
except QueueEmpty:
time.sleep(0.01)
except (EOFError, BrokenPipeError):
break
workers_running = self.count_workers_alive()
# All workers have finished, move the remaining entries from internal
# to external queue
while True:
try:
batch_str = self._queue_internal.get(timeout=0.005)
if batch_str != "":
self.queue.put(batch_str)
except QueueEmpty:
break
except (EOFError, BrokenPipeError):
break
self.queue.put(pickle.dumps(None, protocol=-1))
time.sleep(0.01)
@classmethod
def _load_batches(cls, load_batch_func, queue_internal, join_signal,
seedval):
# pylint: disable=broad-except
if seedval is not None:
random.seed(seedval)
np.random.seed(seedval)
iarandom.seed(seedval)
try:
gen = (
load_batch_func()
if not ia.is_generator(load_batch_func)
else load_batch_func
)
for batch in gen:
assert isinstance(batch, Batch), (
"Expected batch returned by load_batch_func to "
"be of class imgaug.Batch, got %s." % (
type(batch),))
batch_pickled = pickle.dumps(batch, protocol=-1)
while not join_signal.is_set():
try:
queue_internal.put(batch_pickled, timeout=0.005)
break
except QueueFull:
pass
if join_signal.is_set():
break
except Exception:
traceback.print_exc()
finally:
queue_internal.put("")
time.sleep(0.01)
def terminate(self):
"""Stop all workers."""
# pylint: disable=protected-access
if not self.join_signal.is_set():
self.join_signal.set()
# give minimal time to put generated batches in queue and gracefully
# shut down
time.sleep(0.01)
if self.main_worker_thread.is_alive():
self.main_worker_thread.join()
if self.threaded:
for worker in self.workers:
if worker.is_alive():
worker.join()
else:
for worker in self.workers:
if worker.is_alive():
worker.terminate()
worker.join()
# wait until all workers are fully terminated
while not self.all_finished():
time.sleep(0.001)
# empty queue until at least one element can be added and place None
# as signal that BL finished
if self.queue.full():
self.queue.get()
self.queue.put(pickle.dumps(None, protocol=-1))
time.sleep(0.01)
# clean the queue, this reportedly prevents hanging threads
while True:
try:
self._queue_internal.get(timeout=0.005)
except QueueEmpty:
break
if not self._queue_internal._closed:
self._queue_internal.close()
if not self.queue._closed:
self.queue.close()
self._queue_internal.join_thread()
self.queue.join_thread()
time.sleep(0.025)
def __del__(self):
if not self.join_signal.is_set():
self.join_signal.set()
class BackgroundAugmenter(object):
"""
**Deprecated**. Augment batches in the background processes.
Deprecated. Use ``imgaug.multicore.Pool`` instead.
This is a wrapper around the multiprocessing module.
Parameters
----------
batch_loader : BatchLoader or multiprocessing.Queue
BatchLoader object that loads the data fed into the
BackgroundAugmenter, or alternatively a Queue. If a Queue, then it
must be made sure that a final ``None`` in the Queue signals that the
loading is finished and no more batches will follow. Otherwise the
BackgroundAugmenter will wait forever for the next batch.
augseq : Augmenter
An augmenter to apply to all loaded images.
This may be e.g. a Sequential to apply multiple augmenters.
queue_size : int
Size of the queue that is used to temporarily save the augmentation
results. Larger values offer the background processes more room
to save results when the main process doesn't load much, i.e. they
can lead to smoother and faster training. For large images, high
values can block a lot of RAM though.
nb_workers : 'auto' or int
Number of background workers to spawn.
If ``auto``, it will be set to ``C-1``, where ``C`` is the number of
CPU cores.
"""
@ia.deprecated(alt_func="imgaug.multicore.Pool")
def __init__(self, batch_loader, augseq, queue_size=50, nb_workers="auto"):
assert queue_size > 0, (
"Expected 'queue_size' to be at least 1, got %d." % (queue_size,))
self.augseq = augseq
self.queue_source = (
batch_loader
if isinstance(batch_loader, multiprocessing.queues.Queue)
else batch_loader.queue
)
self.queue_result = multiprocessing.Queue(queue_size)
if nb_workers == "auto":
try:
nb_workers = multiprocessing.cpu_count()
except (ImportError, NotImplementedError):
nb_workers = 1
# try to reserve at least one core for the main process
nb_workers = max(1, nb_workers - 1)
else:
assert nb_workers >= 1, (
"Expected 'nb_workers' to be \"auto\" or at least 1, "
"got %d instead." % (nb_workers,))
self.nb_workers = nb_workers
self.workers = []
self.nb_workers_finished = 0
seeds = iarandom.get_global_rng().generate_seeds_(nb_workers)
for i in range(nb_workers):
worker = multiprocessing.Process(
target=self._augment_images_worker,
args=(augseq, self.queue_source, self.queue_result, seeds[i])
)
worker.daemon = True
worker.start()
self.workers.append(worker)
def all_finished(self):
return self.nb_workers_finished == self.nb_workers
def get_batch(self):
"""
Returns a batch from the queue of augmented batches.
If workers are still running and there are no batches in the queue,
it will automatically wait for the next batch.
Returns
-------
out : None or imgaug.Batch
One batch or None if all workers have finished.
"""
if self.all_finished():
return None
batch_str = self.queue_result.get()
batch = pickle.loads(batch_str)
if batch is not None:
return batch
self.nb_workers_finished += 1
if self.nb_workers_finished >= self.nb_workers:
try:
# remove `None` from the source queue
self.queue_source.get(timeout=0.001)
except QueueEmpty:
pass
return None
return self.get_batch()
@classmethod
def _augment_images_worker(cls, augseq, queue_source, queue_result,
seedval):
"""
Augment endlessly images in the source queue.
This is a worker function for that endlessly queries the source queue
(input batches), augments batches in it and sends the result to the
output queue.
"""
np.random.seed(seedval)
random.seed(seedval)
augseq.seed_(seedval)
iarandom.seed(seedval)
loader_finished = False
while not loader_finished:
# wait for a new batch in the source queue and load it
try:
batch_str = queue_source.get(timeout=0.1)
batch = pickle.loads(batch_str)
if batch is None:
loader_finished = True
# put it back in so that other workers know that the
# loading queue is finished
queue_source.put(pickle.dumps(None, protocol=-1))
else:
batch_aug = augseq.augment_batch_(batch)
# send augmented batch to output queue
batch_str = pickle.dumps(batch_aug, protocol=-1)
queue_result.put(batch_str)
except QueueEmpty:
time.sleep(0.01)
queue_result.put(pickle.dumps(None, protocol=-1))
time.sleep(0.01)
def terminate(self):
"""
Terminates all background processes immediately.
This will also free their RAM.
"""
# pylint: disable=protected-access
for worker in self.workers:
if worker.is_alive():
worker.terminate()
self.nb_workers_finished = len(self.workers)
if not self.queue_result._closed:
self.queue_result.close()
time.sleep(0.01)
def __del__(self):
time.sleep(0.1)
self.terminate()
================================================
FILE: imgaug/parameters.py
================================================
"""Classes and methods to use for parameters of augmenters.
This module contains e.g. classes representing probability
distributions (guassian, poisson etc.), classes representing noise sources
and methods to normalize parameter-related user inputs.
"""
from __future__ import print_function, division, absolute_import
import copy as copy_module
from collections import defaultdict
from abc import ABCMeta, abstractmethod
import tempfile
from functools import reduce, wraps
from operator import mul as mul_op
import numpy as np
import six
import six.moves as sm
import scipy
import scipy.stats
import imageio
import cv2
from . import imgaug as ia
from . import dtypes as iadt
from . import random as iarandom
from .external.opensimplex import OpenSimplex
# Added in 0.5.0.
_PREFETCHING_ENABLED = True
# Added in 0.5.0.
_NB_PREFETCH = 10000
# Added in 0.5.0.
_NB_PREFETCH_STRINGS = 1000
# Added in 0.5.0.
def _prefetchable(func):
@wraps(func)
def _inner(*args, **kwargs):
param = func(*args, **kwargs)
return _wrap_leafs_of_param_in_prefetchers(param, _NB_PREFETCH)
return _inner
# Added in 0.5.0.
def _prefetchable_str(func):
@wraps(func)
def _inner(*args, **kwargs):
param = func(*args, **kwargs)
return _wrap_leafs_of_param_in_prefetchers(param, _NB_PREFETCH_STRINGS)
return _inner
# Added in 0.5.0.
def _wrap_param_in_prefetchers(param, nb_prefetch):
for key, value in param.__dict__.items():
if isinstance(value, StochasticParameter):
param.__dict__[key] = _wrap_param_in_prefetchers(value, nb_prefetch)
if param.prefetchable:
return AutoPrefetcher(param, nb_prefetch)
return param
# Added in 0.5.0.
def _wrap_leafs_of_param_in_prefetchers(param, nb_prefetch):
param_wrapped, _did_wrap_any_child = \
_wrap_leafs_of_param_in_prefetchers_recursive(
param, nb_prefetch
)
return param_wrapped
# Added in 0.5.0.
def _wrap_leafs_of_param_in_prefetchers_recursive(param, nb_prefetch):
# Do not descent into AutoPrefetcher, otherwise we risk turning an
# AutoPrefetcher(X) into AutoPrefetcher(AutoPrefetcher(X)) if X is
# prefetchable
if isinstance(param, AutoPrefetcher):
# report did_wrap_any_child=True here, so that parent parameters
# are not wrapped in prefetchers, which could lead to ugly scenarios
# like AutoPrefetcher(Normal(AutoPrefetcher(Uniform(-1.0, 1.0))),
return param, True
if isinstance(param, (list, tuple)):
result = []
did_wrap_any_child = False
for param_i in param:
param_i_wrapped, did_wrap_any_child_i = \
_wrap_leafs_of_param_in_prefetchers_recursive(
param_i, nb_prefetch
)
result.append(param_i_wrapped)
did_wrap_any_child = did_wrap_any_child or did_wrap_any_child_i
if not did_wrap_any_child:
return param, False
if isinstance(param, tuple):
return tuple(result), did_wrap_any_child
return result, did_wrap_any_child
if not isinstance(param, StochasticParameter):
return param, False
did_wrap_any_child = False
for key, value in param.__dict__.items():
param_wrapped, did_wrap_i = \
_wrap_leafs_of_param_in_prefetchers_recursive(
value, nb_prefetch
)
param.__dict__[key] = param_wrapped
did_wrap_any_child = did_wrap_any_child or did_wrap_i
if param.prefetchable and not did_wrap_any_child and _PREFETCHING_ENABLED:
return AutoPrefetcher(param, nb_prefetch), True
return param, did_wrap_any_child
def toggle_prefetching(enabled):
"""Toggle prefetching on or off.
Added in 0.5.0.
Parameters
----------
enabled : bool
Whether enabled is activated (``True``) or off (``False``).
"""
# pylint: disable=global-statement
global _PREFETCHING_ENABLED
_PREFETCHING_ENABLED = enabled
class toggled_prefetching(object): # pylint: disable=invalid-name
"""Context that toggles prefetching on or off depending on a flag.
Added in 0.5.0.
Parameters
----------
enabled : bool
Whether enabled is activated (``True``) or off (``False``).
"""
# Added in 0.5.0.
def __init__(self, enabled):
self.enabled = enabled
self._old_state = None
# Added in 0.5.0.
def __enter__(self):
# pylint: disable=global-statement
global _PREFETCHING_ENABLED
self._old_state = _PREFETCHING_ENABLED
_PREFETCHING_ENABLED = self.enabled
# Added in 0.5.0.
def __exit__(self, exception_type, exception_value, exception_traceback):
# pylint: disable=global-statement
global _PREFETCHING_ENABLED
_PREFETCHING_ENABLED = self._old_state
class no_prefetching(toggled_prefetching): # pylint: disable=invalid-name
"""Context that deactviates prefetching.
Added in 0.5.0.
"""
# Added in 0.5.0.
def __init__(self):
super(no_prefetching, self).__init__(False)
def _check_value_range(value, name, value_range):
if value_range is None:
return True
if isinstance(value_range, tuple):
assert len(value_range) == 2, (
"If 'value_range' is a tuple, it must contain exactly 2 entries, "
"got %d." % (len(value_range),))
if value_range[0] is None and value_range[1] is None:
return True
if value_range[0] is None:
assert value <= value_range[1], (
"Parameter '%s' is outside of the expected value "
"range (x <= %.4f)" % (name, value_range[1]))
return True
if value_range[1] is None:
assert value_range[0] <= value, (
"Parameter '%s' is outside of the expected value "
"range (%.4f <= x)" % (name, value_range[0]))
return True
assert value_range[0] <= value <= value_range[1], (
"Parameter '%s' is outside of the expected value "
"range (%.4f <= x <= %.4f)" % (
name, value_range[0], value_range[1]))
return True
if ia.is_callable(value_range):
value_range(value)
return True
raise Exception("Unexpected input for value_range, got %s." % (
str(value_range),))
# FIXME this uses _check_value_range, which checks for a<=x<=b, but a produced
# Uniform parameter has value range a<=x= self.nb_prefetch:
return self.other_param.draw_samples(size, random_state)
if self.samples is None:
self._prefetch(random_state)
leftover = len(self.samples) - self.index - nb_components
if leftover <= 0:
self._prefetch(random_state)
samples = self.samples[self.index:self.index+nb_components]
self.index += nb_components
return samples.reshape(size)
# Added in 0.5.0.
def _prefetch(self, random_state):
samples = self.other_param.draw_samples((self.nb_prefetch,),
random_state)
if self.samples is None:
self.samples = samples
else:
self.samples = np.concatenate([
self.samples[self.index:], samples
], axis=0)
self.index = 0
# Added in 0.5.0.
def __getattr__(self, attr):
other_param = super(
AutoPrefetcher, self
).__getattribute__("other_param")
return getattr(other_param, attr)
# Added in 0.5.0.
def __repr__(self):
return self.__str__()
# Added in 0.5.0.
def __str__(self):
has_samples = (self.samples is not None)
return (
"AutoPrefetcher("
"nb_prefetch=%d, "
"samples=%s (dtype %s), "
"index=%d, "
"last_rng_idx=%s, "
"other_param=%s"
")" % (
self.nb_prefetch,
self.samples.shape if has_samples else "None",
self.samples.dtype.name if has_samples else "None",
self.index,
self.last_rng_idx,
str(self.other_param)
)
)
class Deterministic(StochasticParameter):
"""Parameter that is a constant value.
If ``N`` values are sampled from this parameter, it will return ``N`` times
``V``, where ``V`` is the constant value.
Parameters
----------
value : number or str or imgaug.parameters.StochasticParameter
A constant value to use.
A string may be provided to generate arrays of strings.
If this is a StochasticParameter, a single value will be sampled
from it exactly once and then used as the constant value.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Deterministic(10)
>>> param.draw_sample()
10
Will always sample the value 10.
"""
def __init__(self, value):
super(Deterministic, self).__init__()
if isinstance(value, StochasticParameter):
self.value = value.draw_sample()
elif ia.is_single_number(value) or ia.is_string(value):
self.value = value
else:
raise Exception("Expected StochasticParameter object or number or "
"string, got %s." % (type(value),))
def _draw_samples(self, size, random_state):
kwargs = {}
if ia.is_single_integer(self.value):
kwargs = {"dtype": np.int32}
elif ia.is_single_float(self.value):
kwargs = {"dtype": np.float32}
return np.full(size, self.value, **kwargs)
def __repr__(self):
return self.__str__()
def __str__(self):
if ia.is_single_integer(self.value):
return "Deterministic(int %d)" % (self.value,)
if ia.is_single_float(self.value):
return "Deterministic(float %.8f)" % (self.value,)
return "Deterministic(%s)" % (str(self.value),)
# TODO replace two-value parameters used in tests with this
class DeterministicList(StochasticParameter):
"""Parameter that repeats elements from a list in the given order.
E.g. of samples of shape ``(A, B, C)`` are requested, this parameter will
return the first ``A*B*C`` elements, reshaped to ``(A, B, C)`` from the
provided list. If the list contains less than ``A*B*C`` elements, it
will (by default) be tiled until it is long enough (i.e. the sampling
will start again at the first element, if necessary multiple times).
Added in 0.4.0.
Parameters
----------
values : ndarray or iterable of number
An iterable of values to sample from in the order within the iterable.
"""
# Added in 0.4.0.
def __init__(self, values):
super(DeterministicList, self).__init__()
assert ia.is_iterable(values), (
"Expected to get an iterable as input, got type %s." % (
type(values).__name__,))
assert len(values) > 0, ("Expected to get at least one value, got "
"zero.")
if ia.is_np_array(values):
# this would not be able to handle e.g. [[1, 2], [3]] and output
# dtype object due to the non-regular shape, hence we have the
# else block
self.values = values.flatten()
else:
self.values = np.array(list(ia.flatten(values)))
kind = self.values.dtype.kind
# limit to 32bit instead of 64bit for efficiency
if kind == "i":
self.values = self.values.astype(np.int32)
elif kind == "f":
self.values = self.values.astype(np.float32)
# Added in 0.4.0.
def _draw_samples(self, size, random_state):
nb_requested = int(np.prod(size))
values = self.values
if nb_requested > self.values.size:
# we don't use itertools.cycle() here, as that would require
# running through a loop potentially many times (as `size` can
# be very large), which would be slow
multiplier = int(np.ceil(nb_requested / values.size))
values = np.tile(values, (multiplier,))
return values[:nb_requested].reshape(size)
# Added in 0.4.0.
def __repr__(self):
return self.__str__()
# Added in 0.4.0.
def __str__(self):
if self.values.dtype.kind == "f":
values = ["%.4f" % (value,) for value in self.values]
return "DeterministicList([%s])" % (", ".join(values),)
return "DeterministicList(%s)" % (str(self.values.tolist()),)
class Choice(StochasticParameter):
"""Parameter that samples value from a list of allowed values.
Parameters
----------
a : iterable
List of allowed values.
Usually expected to be ``int`` s, ``float`` s or ``str`` s.
May also contain ``StochasticParameter`` s. Each
``StochasticParameter`` that is randomly picked will automatically be
replaced by a sample of itself (or by ``N`` samples if the parameter
was picked ``N`` times).
replace : bool, optional
Whether to perform sampling with or without replacing.
p : None or iterable of number, optional
Probabilities of each element in `a`.
Must have the same length as `a` (if provided).
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Choice([5, 17, 25], p=[0.25, 0.5, 0.25])
>>> sample = param.draw_sample()
>>> assert sample in [5, 17, 25]
Create and sample from a parameter, which will produce with ``50%``
probability the sample ``17`` and in the other ``50%`` of all cases the
sample ``5`` or ``25``..
"""
def __init__(self, a, replace=True, p=None):
# pylint: disable=invalid-name
super(Choice, self).__init__()
assert ia.is_iterable(a), (
"Expected a to be an iterable (e.g. list), got %s." % (type(a),))
self.a = a
self.replace = replace
if p is not None:
assert ia.is_iterable(p), (
"Expected p to be None or an iterable, got %s." % (type(p),))
assert len(p) == len(a), (
"Expected lengths of a and p to be identical, "
"got %d and %d." % (len(a), len(p)))
self.p = p
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return self.replace
def _draw_samples(self, size, random_state):
if any([isinstance(a_i, StochasticParameter) for a_i in self.a]):
rngs = random_state.duplicate(1+len(self.a))
samples = rngs[0].choice(
self.a, np.prod(size), replace=self.replace, p=self.p)
# collect the sampled parameters and how many samples must be taken
# from each of them
params_counter = defaultdict(lambda: 0)
for sample in samples:
if isinstance(sample, StochasticParameter):
key = str(sample)
params_counter[key] += 1
# collect per parameter once the required number of samples
# iterate here over self.a to always use the same seed for
# the same parameter
# TODO this might fail if the same parameter is added multiple
# times to self.a?
# TODO this will fail if a parameter cant handle size=(N,)
param_to_samples = dict()
for i, param in enumerate(self.a):
key = str(param)
if key in params_counter:
param_to_samples[key] = param.draw_samples(
size=(params_counter[key],),
random_state=rngs[1+i]
)
# assign the values sampled from the parameters to the `samples`
# array by replacing the respective parameter
param_to_readcount = defaultdict(lambda: 0)
for i, sample in enumerate(samples):
if isinstance(sample, StochasticParameter):
key = str(sample)
readcount = param_to_readcount[key]
samples[i] = param_to_samples[key][readcount]
param_to_readcount[key] += 1
samples = samples.reshape(size)
else:
samples = random_state.choice(self.a, size, replace=self.replace,
p=self.p)
dtype = samples.dtype
if dtype.itemsize*8 > 32:
# strings have kind "U"
kind = dtype.kind
if kind == "i":
samples = samples.astype(np.int32)
elif kind == "u":
samples = samples.astype(np.uint32)
elif kind == "f":
samples = samples.astype(np.float32)
return samples
def __repr__(self):
return self.__str__()
def __str__(self):
return "Choice(a=%s, replace=%s, p=%s)" % (
str(self.a), str(self.replace), str(self.p),)
class Binomial(StochasticParameter):
"""Binomial distribution.
Parameters
----------
p : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Probability of the binomial distribution. Expected to be in the
interval ``[0.0, 1.0]``.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`Binomial.draw_sample` or
:func:`Binomial.draw_samples`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Binomial(Uniform(0.01, 0.2))
Create a binomial distribution that uses a varying probability between
``0.01`` and ``0.2``, randomly and uniformly estimated once per sampling
call.
"""
def __init__(self, p):
super(Binomial, self).__init__()
self.p = handle_continuous_param(p, "p")
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
p = self.p.draw_sample(random_state=random_state)
assert 0 <= p <= 1.0, (
"Expected probability p to be in the interval [0.0, 1.0], "
"got %.4f." % (p,))
return random_state.binomial(1, p, size).astype(np.int32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Binomial(%s)" % (self.p,)
class DiscreteUniform(StochasticParameter):
"""Uniform distribution over the discrete interval ``[a..b]``.
Parameters
----------
a : int or tuple of int or list of int or imgaug.parameters.StochasticParameter
Lower bound of the interval.
If ``a>b``, `a` and `b` will automatically be flipped.
If ``a==b``, all generated values will be identical to `a`.
* If a single ``int``, this ``int`` will be used as a
constant value.
* If a ``tuple`` of two ``int`` s ``(a, b)``, the value will be
sampled from the discrete interval ``[a..b]`` once per call.
* If a ``list`` of ``int``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`DiscreteUniform.draw_sample` or
:func:`DiscreteUniform.draw_samples`.
b : int or imgaug.parameters.StochasticParameter
Upper bound of the interval. Analogous to `a`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.DiscreteUniform(10, Choice([20, 30, 40]))
>>> sample = param.draw_sample()
>>> assert 10 <= sample <= 40
Create a discrete uniform distribution which's interval differs between
calls and can be ``[10..20]``, ``[10..30]`` or ``[10..40]``.
"""
def __init__(self, a, b):
# pylint: disable=invalid-name
super(DiscreteUniform, self).__init__()
self.a = handle_discrete_param(a, "a")
self.b = handle_discrete_param(b, "b")
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
# pylint: disable=invalid-name
a = self.a.draw_sample(random_state=random_state)
b = self.b.draw_sample(random_state=random_state)
if a > b:
a, b = b, a
elif a == b:
return np.full(size, a, dtype=np.int32)
return random_state.integers(a, b + 1, size, dtype=np.int32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "DiscreteUniform(%s, %s)" % (self.a, self.b)
class Poisson(StochasticParameter):
"""Parameter that resembles a poisson distribution.
A poisson distribution with ``lambda=0`` has its highest probability at
point ``0`` and decreases quickly from there.
Poisson distributions are discrete and never negative.
Parameters
----------
lam : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Lambda parameter of the poisson distribution.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`Poisson.draw_sample` or
:func:`Poisson.draw_samples`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Poisson(1)
>>> sample = param.draw_sample()
>>> assert sample >= 0
Create a poisson distribution with ``lambda=1`` and sample a value from
it.
"""
def __init__(self, lam):
super(Poisson, self).__init__()
self.lam = handle_continuous_param(lam, "lam")
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
lam = self.lam.draw_sample(random_state=random_state)
lam = max(lam, 0)
return random_state.poisson(lam=lam, size=size).astype(np.int32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Poisson(%s)" % (self.lam,)
class Normal(StochasticParameter):
"""Parameter that resembles a normal/gaussian distribution.
Parameters
----------
loc : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The mean of the normal distribution.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`Laplace.draw_sample` or
:func:`Laplace.draw_samples`.
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The standard deviation of the normal distribution.
If this parameter reaches ``0``, the output array will be filled with
`loc`.
Datatype behaviour is the analogous to `loc`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Normal(Choice([-1.0, 1.0]), 1.0)
Create a gaussian distribution with a mean that differs by call.
Samples values may sometimes follow ``N(-1.0, 1.0)`` and sometimes
``N(1.0, 1.0)``.
"""
def __init__(self, loc, scale):
super(Normal, self).__init__()
self.loc = handle_continuous_param(loc, "loc")
self.scale = handle_continuous_param(scale, "scale",
value_range=(0, None))
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
loc = self.loc.draw_sample(random_state=random_state)
scale = self.scale.draw_sample(random_state=random_state)
assert scale >= 0, "Expected scale to be >=0, got %.4f." % (scale,)
if scale == 0:
return np.full(size, loc, dtype=np.float32)
return random_state.normal(loc, scale, size=size).astype(np.float32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Normal(loc=%s, scale=%s)" % (self.loc, self.scale)
# TODO docstring for parameters is outdated
class TruncatedNormal(StochasticParameter):
"""Parameter that resembles a truncated normal distribution.
A truncated normal distribution is similar to a normal distribution,
except the domain is smoothly bounded to a min and max value.
This is a wrapper around :func:`scipy.stats.truncnorm`.
Parameters
----------
loc : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The mean of the normal distribution.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`TruncatedNormal.draw_sample` or
:func:`TruncatedNormal.draw_samples`.
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The standard deviation of the normal distribution.
If this parameter reaches ``0``, the output array will be filled with
`loc`.
Datatype behaviour is the same as for `loc`.
low : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The minimum value of the truncated normal distribution.
Datatype behaviour is the same as for `loc`.
high : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The maximum value of the truncated normal distribution.
Datatype behaviour is the same as for `loc`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.TruncatedNormal(0, 5.0, low=-10, high=10)
>>> samples = param.draw_samples(100, random_state=0)
>>> assert np.all(samples >= -10)
>>> assert np.all(samples <= 10)
Create a truncated normal distribution with its minimum at ``-10.0``
and its maximum at ``10.0``.
"""
def __init__(self, loc, scale, low=-np.inf, high=np.inf):
super(TruncatedNormal, self).__init__()
self.loc = handle_continuous_param(loc, "loc")
self.scale = handle_continuous_param(scale, "scale",
value_range=(0, None))
self.low = handle_continuous_param(low, "low")
self.high = handle_continuous_param(high, "high")
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
# pylint: disable=invalid-name
loc = self.loc.draw_sample(random_state=random_state)
scale = self.scale.draw_sample(random_state=random_state)
low = self.low.draw_sample(random_state=random_state)
high = self.high.draw_sample(random_state=random_state)
seed = random_state.generate_seed_()
if low > high:
low, high = high, low
assert scale >= 0, "Expected scale to be >=0, got %.4f." % (scale,)
if scale == 0:
return np.full(size, fill_value=loc, dtype=np.float32)
a = (low - loc) / scale
b = (high - loc) / scale
tnorm = scipy.stats.truncnorm(a=a, b=b, loc=loc, scale=scale)
# Using a seed here works with both np.random interfaces.
# Last time tried, scipy crashed when providing just
# random_state.generator on the new np.random interface.
return tnorm.rvs(size=size, random_state=seed).astype(np.float32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "TruncatedNormal(loc=%s, scale=%s, low=%s, high=%s)" % (
self.loc, self.scale, self.low, self.high)
class Laplace(StochasticParameter):
"""Parameter that resembles a (continuous) laplace distribution.
This is a wrapper around numpy's :func:`numpy.random.laplace`.
Parameters
----------
loc : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The position of the distribution peak, similar to the mean in normal
distributions.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`Laplace.draw_sample` or
:func:`Laplace.draw_samples`.
scale : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
The exponential decay factor, similar to the standard deviation in
gaussian distributions.
If this parameter reaches ``0``, the output array will be filled with
`loc`.
Datatype behaviour is the analogous to `loc`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Laplace(0, 1.0)
Create a laplace distribution, which's peak is at ``0`` and decay is
``1.0``.
"""
def __init__(self, loc, scale):
super(Laplace, self).__init__()
self.loc = handle_continuous_param(loc, "loc")
self.scale = handle_continuous_param(scale, "scale",
value_range=(0, None))
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
loc = self.loc.draw_sample(random_state=random_state)
scale = self.scale.draw_sample(random_state=random_state)
assert scale >= 0, "Expected scale to be >=0, got %s." % (scale,)
if scale == 0:
return np.full(size, loc, dtype=np.float32)
return random_state.laplace(loc, scale, size=size).astype(np.float32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Laplace(loc=%s, scale=%s)" % (self.loc, self.scale)
class ChiSquare(StochasticParameter):
"""Parameter that resembles a (continuous) chi-square distribution.
This is a wrapper around numpy's :func:`numpy.random.chisquare`.
Parameters
----------
df : int or tuple of two int or list of int or imgaug.parameters.StochasticParameter
Degrees of freedom. Expected value range is ``[1, inf)``.
* If a single ``int``, this ``int`` will be used as a
constant value.
* If a ``tuple`` of two ``int`` s ``(a, b)``, the value will be
sampled from the discrete interval ``[a..b]`` once per call.
* If a ``list`` of ``int``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`ChiSquare.draw_sample` or
:func:`ChiSquare.draw_samples`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.ChiSquare(df=2)
Create a chi-square distribution with two degrees of freedom.
"""
def __init__(self, df):
# pylint: disable=invalid-name
super(ChiSquare, self).__init__()
self.df = handle_discrete_param(df, "df", value_range=(1, None))
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
# pylint: disable=invalid-name
df = self.df.draw_sample(random_state=random_state)
assert df >= 1, "Expected df to be >=1, got %d." % (df,)
return random_state.chisquare(df, size=size).astype(np.float32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "ChiSquare(df=%s)" % (self.df,)
class Weibull(StochasticParameter):
"""
Parameter that resembles a (continuous) weibull distribution.
This is a wrapper around numpy's :func:`numpy.random.weibull`.
Parameters
----------
a : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Shape parameter of the distribution.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`Weibull.draw_sample` or
:func:`Weibull.draw_samples`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Weibull(a=0.5)
Create a weibull distribution with shape 0.5.
"""
def __init__(self, a):
# pylint: disable=invalid-name
super(Weibull, self).__init__()
self.a = handle_continuous_param(a, "a", value_range=(0.0001, None))
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
# pylint: disable=invalid-name
a = self.a.draw_sample(random_state=random_state)
assert a > 0, "Expected a to be >0, got %.4f." % (a,)
return random_state.weibull(a, size=size).astype(np.float32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Weibull(a=%s)" % (self.a,)
# TODO rename (a, b) to (low, high) as in numpy?
class Uniform(StochasticParameter):
"""Parameter that resembles a uniform distribution over ``[a, b)``.
Parameters
----------
a : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Lower bound of the interval.
If ``a>b``, `a` and `b` will automatically be flipped.
If ``a==b``, all generated values will be identical to `a`.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`Uniform.draw_sample` or
:func:`Uniform.draw_samples`.
b : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Upper bound of the interval. Analogous to `a`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Uniform(0, 10.0)
>>> sample = param.draw_sample()
>>> assert 0 <= sample < 10.0
Create and sample from a uniform distribution over ``[0, 10.0)``.
"""
def __init__(self, a, b):
# pylint: disable=invalid-name
super(Uniform, self).__init__()
self.a = handle_continuous_param(a, "a")
self.b = handle_continuous_param(b, "b")
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
# pylint: disable=invalid-name
a = self.a.draw_sample(random_state=random_state)
b = self.b.draw_sample(random_state=random_state)
if a > b:
a, b = b, a
elif a == b:
return np.full(size, a, dtype=np.float32)
return random_state.uniform(a, b, size).astype(np.float32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Uniform(%s, %s)" % (self.a, self.b)
class Beta(StochasticParameter):
"""Parameter that resembles a (continuous) beta distribution.
Parameters
----------
alpha : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
alpha parameter of the beta distribution.
Expected value range is ``(0, inf)``. Values below ``0`` are
automatically clipped to ``0+epsilon``.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`Beta.draw_sample` or
:func:`Beta.draw_samples`.
beta : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Beta parameter of the beta distribution. Analogous to `alpha`.
epsilon : number
Clipping parameter. If `alpha` or `beta` end up ``<=0``, they are clipped to ``0+epsilon``.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Beta(0.4, 0.6)
Create a beta distribution with ``alpha=0.4`` and ``beta=0.6``.
"""
def __init__(self, alpha, beta, epsilon=0.0001):
super(Beta, self).__init__()
self.alpha = handle_continuous_param(alpha, "alpha")
self.beta = handle_continuous_param(beta, "beta")
assert ia.is_single_number(epsilon), (
"Expected epsilon to a number, got type %s." % (type(epsilon),))
self.epsilon = epsilon
# Added in 0.5.0.
@property
def prefetchable(self):
"""See :func:`StochasticParameter.prefetchable`."""
return True
def _draw_samples(self, size, random_state):
alpha = self.alpha.draw_sample(random_state=random_state)
beta = self.beta.draw_sample(random_state=random_state)
alpha = max(alpha, self.epsilon)
beta = max(beta, self.epsilon)
return random_state.beta(alpha, beta, size=size).astype(np.float32)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Beta(%s, %s)" % (self.alpha, self.beta)
class FromLowerResolution(StochasticParameter):
"""Parameter to sample from other parameters at lower image resolutions.
This parameter is intended to be used with parameters that would usually
sample one value per pixel (or one value per pixel and channel). Instead
of sampling from the other parameter at full resolution, it samples at
lower resolution, e.g. ``0.5*H x 0.5*W`` with ``H`` being the height and
``W`` being the width. After the low-resolution sampling this parameter
then upscales the result to ``HxW``.
This parameter is intended to produce coarse samples. E.g. combining
this with :class:`Binomial` can lead to large rectangular areas of
``1`` s and ``0`` s.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
The other parameter which is to be sampled on a coarser image.
size_percent : None or number or iterable of number or imgaug.parameters.StochasticParameter, optional
Size of the 2d sampling plane in percent of the requested size.
I.e. this is relative to the size provided in the call to
``draw_samples(size)``. Lower values will result in smaller sampling
planes, which are then upsampled to `size`. This means that lower
values will result in larger rectangles. The size may be provided as
a constant value or a tuple ``(a, b)``, which will automatically be
converted to the continuous uniform range ``[a, b)`` or a
:class:`StochasticParameter`, which will be queried per call to
:func:`FromLowerResolution.draw_sample` and
:func:`FromLowerResolution.draw_samples`.
size_px : None or number or iterable of numbers or imgaug.parameters.StochasticParameter, optional
Size of the 2d sampling plane in pixels.
Lower values will result in smaller sampling planes, which are then
upsampled to the input `size` of ``draw_samples(size)``.
This means that lower values will result in larger rectangles.
The size may be provided as a constant value or a tuple ``(a, b)``,
which will automatically be converted to the discrete uniform
range ``[a..b]`` or a :class:`StochasticParameter`, which will be
queried once per call to :func:`FromLowerResolution.draw_sample` and
:func:`FromLowerResolution.draw_samples`.
method : str or int or imgaug.parameters.StochasticParameter, optional
Upsampling/interpolation method to use. This is used after the sampling
is finished and the low resolution plane has to be upsampled to the
requested `size` in ``draw_samples(size, ...)``. The method may be
the same as in :func:`~imgaug.imgaug.imresize_many_images`. Usually
``nearest`` or ``linear`` are good choices. ``nearest`` will result
in rectangles with sharp edges and ``linear`` in rectangles with
blurry and round edges. The method may be provided as a
:class:`StochasticParameter`, which will be queried once per call to
:func:`FromLowerResolution.draw_sample` and
:func:`FromLowerResolution.draw_samples`.
min_size : int, optional
Minimum size in pixels of the low resolution sampling plane.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.FromLowerResolution(
>>> Binomial(0.05),
>>> size_px=(2, 16),
>>> method=Choice(["nearest", "linear"]))
Samples from a binomial distribution with ``p=0.05``. The sampling plane
will always have a size HxWxC with H and W being independently sampled
from ``[2..16]`` (i.e. it may range from ``2x2xC`` up to ``16x16xC`` max,
but may also be e.g. ``4x8xC``). The upsampling method will be ``nearest``
in ``50%`` of all cases and ``linear`` in the other 50 percent. The result
will sometimes be rectangular patches of sharp ``1`` s surrounded by
``0`` s and sometimes blurry blobs of ``1``s, surrounded by values
``<1.0``.
"""
def __init__(self, other_param, size_percent=None, size_px=None,
method="nearest", min_size=1):
super(FromLowerResolution, self).__init__()
assert size_percent is not None or size_px is not None, (
"Expected either 'size_percent' or 'size_px' to be provided, "
"got neither of them.")
if size_percent is not None:
self.size_method = "percent"
self.size_px = None
if ia.is_single_number(size_percent):
self.size_percent = Deterministic(size_percent)
elif ia.is_iterable(size_percent):
assert len(size_percent) == 2, (
"Expected iterable 'size_percent' to contain exactly 2 "
"values, got %d." % (len(size_percent),))
self.size_percent = Uniform(size_percent[0], size_percent[1])
elif isinstance(size_percent, StochasticParameter):
self.size_percent = size_percent
else:
raise Exception(
"Expected int, float, tuple of two ints/floats or "
"StochasticParameter for size_percent, "
"got %s." % (type(size_percent),))
else: # = elif size_px is not None:
self.size_method = "px"
self.size_percent = None
if ia.is_single_integer(size_px):
self.size_px = Deterministic(size_px)
elif ia.is_iterable(size_px):
assert len(size_px) == 2, (
"Expected iterable 'size_px' to contain exactly 2 "
"values, got %d." % (len(size_px),))
self.size_px = DiscreteUniform(size_px[0], size_px[1])
elif isinstance(size_px, StochasticParameter):
self.size_px = size_px
else:
raise Exception(
"Expected int, float, tuple of two ints/floats or "
"StochasticParameter for size_px, "
"got %s." % (type(size_px),))
self.other_param = other_param
if ia.is_string(method) or ia.is_single_integer(method):
self.method = Deterministic(method)
elif isinstance(method, StochasticParameter):
self.method = method
else:
raise Exception("Expected string or StochasticParameter, "
"got %s." % (type(method),))
self.min_size = min_size
def _draw_samples(self, size, random_state):
if len(size) == 3:
n = 1
h, w, c = size
elif len(size) == 4:
n, h, w, c = size
else:
raise Exception("FromLowerResolution can only generate samples "
"of shape (H, W, C) or (N, H, W, C), "
"requested was %s." % (str(size),))
if self.size_method == "percent":
hw_percents = self.size_percent.draw_samples(
(n, 2), random_state=random_state)
hw_pxs = (hw_percents * np.array([h, w])).astype(np.int32)
else:
hw_pxs = self.size_px.draw_samples(
(n, 2), random_state=random_state)
methods = self.method.draw_samples((n,), random_state=random_state)
result = None
for i, (hw_px, method) in enumerate(zip(hw_pxs, methods)):
h_small = max(hw_px[0], self.min_size)
w_small = max(hw_px[1], self.min_size)
samples = self.other_param.draw_samples(
(1, h_small, w_small, c), random_state=random_state)
# This (1) makes sure that samples are of dtypes supported by
# imresize_many_images, and (2) forces samples to be float-kind
# if the requested interpolation is something else than nearest
# neighbour interpolation. (2) is a bit hacky and makes sure that
# continuous values are produced for e.g. cubic interpolation.
# This is particularly important for e.g. binomial distributios
# used in FromLowerResolution and thereby in e.g. CoarseDropout,
# where integer-kinds would lead to sharp edges despite using
# cubic interpolation.
if samples.dtype.kind == "f":
samples = iadt.restore_dtypes_(samples, np.float32)
elif samples.dtype.kind == "i":
if method == "nearest":
samples = iadt.restore_dtypes_(samples, np.int32)
else:
samples = iadt.restore_dtypes_(samples, np.float32)
else:
assert samples.dtype.kind == "u", (
"FromLowerResolution can only process outputs of kind "
"f (float), i (int) or u (uint), got %s." % (
samples.dtype.kind))
if method == "nearest":
samples = iadt.restore_dtypes_(samples, np.uint16)
else:
samples = iadt.restore_dtypes_(samples, np.float32)
samples_upscaled = ia.imresize_many_images(
samples, (h, w), interpolation=method)
if result is None:
result = np.zeros((n, h, w, c), dtype=samples_upscaled.dtype)
result[i] = samples_upscaled
if len(size) == 3:
return result[0]
return result
def __repr__(self):
return self.__str__()
def __str__(self):
if self.size_method == "percent":
pattern = (
"FromLowerResolution("
"size_percent=%s, method=%s, other_param=%s"
")")
return pattern % (self.size_percent, self.method, self.other_param)
pattern = (
"FromLowerResolution("
"size_px=%s, method=%s, other_param=%s"
")")
return pattern % (self.size_px, self.method, self.other_param)
class Clip(StochasticParameter):
"""Clip another parameter to a defined value range.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
The other parameter, which's values are to be clipped.
minval : None or number, optional
The minimum value to use.
If ``None``, no minimum will be used.
maxval : None or number, optional
The maximum value to use.
If ``None``, no maximum will be used.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Clip(Normal(0, 1.0), minval=-2.0, maxval=2.0)
Create a standard gaussian distribution, which's values never go below
``-2.0`` or above ``2.0``. Note that this will lead to small "bumps" of
higher probability at ``-2.0`` and ``2.0``, as values below/above these
will be clipped to them. For smoother limitations on gaussian
distributions, see :class:`TruncatedNormal`.
"""
def __init__(self, other_param, minval=None, maxval=None):
super(Clip, self).__init__()
_assert_arg_is_stoch_param("other_param", other_param)
assert minval is None or ia.is_single_number(minval), (
"Expected 'minval' to be None or a number, got type %s." % (
type(minval),))
assert maxval is None or ia.is_single_number(maxval), (
"Expected 'maxval' to be None or a number, got type %s." % (
type(maxval),))
self.other_param = other_param
self.minval = minval
self.maxval = maxval
def _draw_samples(self, size, random_state):
samples = self.other_param.draw_samples(size, random_state=random_state)
if self.minval is not None or self.maxval is not None:
# Note that this would produce a warning if 'samples' is int64
# or uint64
samples = np.clip(samples, self.minval, self.maxval, out=samples)
return samples
def __repr__(self):
return self.__str__()
def __str__(self):
opstr = str(self.other_param)
if self.minval is not None and self.maxval is not None:
return "Clip(%s, %.6f, %.6f)" % (
opstr, float(self.minval), float(self.maxval))
if self.minval is not None:
return "Clip(%s, %.6f, None)" % (opstr, float(self.minval))
if self.maxval is not None:
return "Clip(%s, None, %.6f)" % (opstr, float(self.maxval))
return "Clip(%s, None, None)" % (opstr,)
class Discretize(StochasticParameter):
"""Convert a continuous distribution to a discrete one.
This will round the values and then cast them to integers.
Values sampled from already discrete distributions are not changed.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
The other parameter, which's values are to be discretized.
round : bool, optional
Whether to round before converting to integer dtype.
Added in 0.4.0.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Discretize(iap.Normal(0, 1.0))
Create a discrete standard gaussian distribution.
"""
def __init__(self, other_param, round=True):
# pylint: disable=redefined-builtin
super(Discretize, self).__init__()
_assert_arg_is_stoch_param("other_param", other_param)
self.other_param = other_param
self.round = round
def _draw_samples(self, size, random_state):
samples = self.other_param.draw_samples(size, random_state=random_state)
assert samples.dtype.kind in ["u", "i", "b", "f"], (
"Expected to get uint, int, bool or float dtype as samples in "
"Discretize(), but got dtype '%s' (kind '%s') instead." % (
samples.dtype.name, samples.dtype.kind))
if samples.dtype.kind in ["u", "i", "b"]:
return samples
# floats seem to reliably cover ints that have half the number of
# bits -- probably not the case for float128 though as that is
# really float96
bitsize = 8 * samples.dtype.itemsize // 2
# in case some weird system knows something like float8 we set a
# lower bound here -- shouldn't happen though
bitsize = max(bitsize, 8)
dtype = np.dtype("int%d" % (bitsize,))
if self.round:
samples = np.round(samples)
return samples.astype(dtype)
def __repr__(self):
return self.__str__()
def __str__(self):
opstr = str(self.other_param)
return "Discretize(%s, round=%s)" % (opstr, str(self.round))
class Multiply(StochasticParameter):
"""Multiply the samples of another stochastic parameter.
Parameters
----------
other_param : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Other parameter which's sampled values are to be multiplied with `val`.
Let ``S`` be the requested shape of samples, then the datatype
behaviour is as follows:
* If a single ``number``, this ``number`` will be used as a
constant value to fill an array of shape ``S``.
* If a ``tuple`` of two ``number`` s ``(a, b)``, an array of
shape ``S`` will be filled with uniformly sampled values from
the continuous interval ``[a, b)``.
* If a ``list`` of ``number``, an array of shape ``S`` will be
filled with randomly picked values from the ``list``.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call to generate an array of shape ``S``.
"per call" denotes a call of :func:`Multiply.draw_sample` or
:func:`Multiply.draw_samples`.
val : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Multiplier to use.
Datatype behaviour is analogous to `other_param`, though if
``elementwise=False`` (the default), only a single sample will be
generated per call instead of ``S``.
elementwise : bool, optional
Controls the sampling behaviour of `val`.
If set to ``False``, a single samples will be requested from `val` and
used as the constant multiplier.
If set to ``True``, samples of shape ``S`` will be requested from
`val` and multiplied elementwise with the samples of `other_param`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Multiply(iap.Uniform(0.0, 1.0), -1)
Convert a uniform distribution from ``[0.0, 1.0)`` to ``(-1.0, 0.0]``.
"""
def __init__(self, other_param, val, elementwise=False):
super(Multiply, self).__init__()
self.other_param = handle_continuous_param(other_param, "other_param",
prefetch=False)
self.val = handle_continuous_param(val, "val", prefetch=False)
self.elementwise = elementwise
def _draw_samples(self, size, random_state):
rngs = random_state.duplicate(2)
samples = self.other_param.draw_samples(size, random_state=rngs[0])
elementwise = (
self.elementwise
and not isinstance(self.val, Deterministic))
if elementwise:
val_samples = self.val.draw_samples(size, random_state=rngs[1])
else:
val_samples = self.val.draw_sample(random_state=rngs[1])
if elementwise:
return np.multiply(samples, val_samples)
return samples * val_samples
def __repr__(self):
return self.__str__()
def __str__(self):
return "Multiply(%s, %s, %s)" % (
str(self.other_param), str(self.val), self.elementwise)
class Divide(StochasticParameter):
"""Divide the samples of another stochastic parameter.
This parameter will automatically prevent division by zero (uses 1.0)
as the denominator in these cases.
Parameters
----------
other_param : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Other parameter which's sampled values are to be divided by `val`.
Let ``S`` be the requested shape of samples, then the datatype
behaviour is as follows:
* If a single ``number``, this ``number`` will be used as a
constant value to fill an array of shape ``S``.
* If a ``tuple`` of two ``number`` s ``(a, b)``, an array of
shape ``S`` will be filled with uniformly sampled values from
the continuous interval ``[a, b)``.
* If a ``list`` of ``number``, an array of shape ``S`` will be
filled with randomly picked values from the ``list``.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call to generate an array of shape ``S``.
"per call" denotes a call of :func:`Divide.draw_sample` or
:func:`Divide.draw_samples`.
val : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Denominator to use.
Datatype behaviour is analogous to `other_param`, though if
``elementwise=False`` (the default), only a single sample will be
generated per call instead of ``S``.
elementwise : bool, optional
Controls the sampling behaviour of `val`.
If set to ``False``, a single samples will be requested from `val` and
used as the constant denominator.
If set to ``True``, samples of shape ``S`` will be requested from
`val` and used to divide the samples of `other_param` elementwise.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Divide(iap.Uniform(0.0, 1.0), 2)
Convert a uniform distribution ``[0.0, 1.0)`` to ``[0, 0.5)``.
"""
def __init__(self, other_param, val, elementwise=False):
super(Divide, self).__init__()
self.other_param = handle_continuous_param(other_param, "other_param",
prefetch=False)
self.val = handle_continuous_param(val, "val", prefetch=False)
self.elementwise = elementwise
def _draw_samples(self, size, random_state):
# pylint: disable=no-else-return
rngs = random_state.duplicate(2)
samples = self.other_param.draw_samples(size, random_state=rngs[0])
elementwise = (
self.elementwise
and not isinstance(self.val, Deterministic))
if elementwise:
val_samples = self.val.draw_samples(size, random_state=rngs[1])
# prevent division by zero
val_samples[val_samples == 0] = 1
return np.divide(
force_np_float_dtype(samples),
force_np_float_dtype(val_samples)
)
else:
val_sample = self.val.draw_sample(random_state=rngs[1])
# prevent division by zero
if val_sample == 0:
val_sample = 1
return force_np_float_dtype(samples) / float(val_sample)
def __repr__(self):
return self.__str__()
def __str__(self):
return "Divide(%s, %s, %s)" % (
str(self.other_param), str(self.val), self.elementwise)
# TODO sampling (N,) from something like 10+Uniform(0, 1) will return
# N times the same value as (N,) values will be sampled from 10, but only
# one from Uniform() unless elementwise=True is explicitly set. That
# seems unintuitive. How can this be prevented?
class Add(StochasticParameter):
"""Add to the samples of another stochastic parameter.
Parameters
----------
other_param : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Samples of `val` will be added to samples of this parameter.
Let ``S`` be the requested shape of samples, then the datatype
behaviour is as follows:
* If a single ``number``, this ``number`` will be used as a
constant value to fill an array of shape ``S``.
* If a ``tuple`` of two ``number`` s ``(a, b)``, an array of
shape ``S`` will be filled with uniformly sampled values from
the continuous interval ``[a, b)``.
* If a ``list`` of ``number``, an array of shape ``S`` will be
filled with randomly picked values from the ``list``.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call to generate an array of shape ``S``.
"per call" denotes a call of :func:`Add.draw_sample` or
:func:`Add.draw_samples`.
val : number or tuple of two number or list of number or imgaug.parameters.StochasticParameter
Value to add to the samples of `other_param`.
Datatype behaviour is analogous to `other_param`, though if
``elementwise=False`` (the default), only a single sample will be
generated per call instead of ``S``.
elementwise : bool, optional
Controls the sampling behaviour of `val`.
If set to ``False``, a single samples will be requested from `val` and
used as the constant multiplier.
If set to ``True``, samples of shape ``S`` will be requested from
`val` and added elementwise with the samples of `other_param`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Add(Uniform(0.0, 1.0), 1.0)
Convert a uniform distribution from ``[0.0, 1.0)`` to ``[1.0, 2.0)``.
"""
def __init__(self, other_param, val, elementwise=False):
super(Add, self).__init__()
self.other_param = handle_continuous_param(other_param, "other_param",
prefetch=False)
self.val = handle_continuous_param(val, "val", prefetch=False)
self.elementwise = elementwise
def _draw_samples(self, size, random_state):
rngs = random_state.duplicate(2)
samples = self.other_param.draw_samples(size, random_state=rngs[0])
elementwise = (
self.elementwise and not isinstance(self.val, Deterministic))
if elementwise:
val_samples = self.val.draw_samples(size, random_state=rngs[1])
else:
val_samples = self.val.draw_sample(random_state=rngs[1])
if elementwise:
return np.add(samples, val_samples)
return samples + val_samples
def __repr__(self):
return self.__str__()
def __str__(self):
return "Add(%s, %s, %s)" % (
str(self.other_param), str(self.val), self.elementwise)
class Subtract(StochasticParameter):
"""Subtract from the samples of another stochastic parameter.
Parameters
----------
other_param : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Samples of `val` will be subtracted from samples of this parameter.
Let ``S`` be the requested shape of samples, then the datatype
behaviour is as follows:
* If a single ``number``, this ``number`` will be used as a
constant value to fill an array of shape ``S``.
* If a ``tuple`` of two ``number`` s ``(a, b)``, an array of
shape ``S`` will be filled with uniformly sampled values from
the continuous interval ``[a, b)``.
* If a ``list`` of ``number``, an array of shape ``S`` will be
filled with randomly picked values from the ``list``.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call to generate an array of shape ``S``.
"per call" denotes a call of :func:`Subtract.draw_sample` or
:func:`Subtract.draw_samples`.
val : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Value to subtract from the other parameter.
Datatype behaviour is analogous to `other_param`, though if
``elementwise=False`` (the default), only a single sample will be
generated per call instead of ``S``.
elementwise : bool, optional
Controls the sampling behaviour of `val`.
If set to ``False``, a single samples will be requested from `val` and
used as the constant multiplier.
If set to ``True``, samples of shape ``S`` will be requested from
`val` and subtracted elementwise from the samples of `other_param`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Subtract(iap.Uniform(0.0, 1.0), 1.0)
Convert a uniform distribution from ``[0.0, 1.0)`` to ``[-1.0, 0.0)``.
"""
def __init__(self, other_param, val, elementwise=False):
super(Subtract, self).__init__()
self.other_param = handle_continuous_param(other_param, "other_param",
prefetch=False)
self.val = handle_continuous_param(val, "val", prefetch=False)
self.elementwise = elementwise
def _draw_samples(self, size, random_state):
rngs = random_state.duplicate(2)
samples = self.other_param.draw_samples(size, random_state=rngs[0])
elementwise = (self.elementwise
and not isinstance(self.val, Deterministic))
if elementwise:
val_samples = self.val.draw_samples(size, random_state=rngs[1])
else:
val_samples = self.val.draw_sample(random_state=rngs[1])
if elementwise:
return np.subtract(samples, val_samples)
return samples - val_samples
def __repr__(self):
return self.__str__()
def __str__(self):
return "Subtract(%s, %s, %s)" % (
str(self.other_param), str(self.val), self.elementwise)
class Power(StochasticParameter):
"""Exponentiate the samples of another stochastic parameter.
Parameters
----------
other_param : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Other parameter which's sampled values are to be exponentiated by `val`.
Let ``S`` be the requested shape of samples, then the datatype
behaviour is as follows:
* If a single ``number``, this ``number`` will be used as a
constant value to fill an array of shape ``S``.
* If a ``tuple`` of two ``number`` s ``(a, b)``, an array of
shape ``S`` will be filled with uniformly sampled values from
the continuous interval ``[a, b)``.
* If a ``list`` of ``number``, an array of shape ``S`` will be
filled with randomly picked values from the ``list``.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call to generate an array of shape ``S``.
"per call" denotes a call of :func:`Power.draw_sample` or
:func:`Power.draw_samples`.
val : number or tuple of number or list of number or imgaug.parameters.StochasticParameter
Value to use exponentiate the samples of `other_param`.
Datatype behaviour is analogous to `other_param`, though if
``elementwise=False`` (the default), only a single sample will be
generated per call instead of ``S``.
elementwise : bool, optional
Controls the sampling behaviour of `val`.
If set to ``False``, a single samples will be requested from `val` and
used as the constant multiplier.
If set to ``True``, samples of shape ``S`` will be requested from
`val` and used to exponentiate elementwise the samples of `other_param`.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Power(iap.Uniform(0.0, 1.0), 2)
Converts a uniform range ``[0.0, 1.0)`` to a distribution that is peaked
towards 1.0.
"""
def __init__(self, other_param, val, elementwise=False):
super(Power, self).__init__()
self.other_param = handle_continuous_param(other_param, "other_param",
prefetch=False)
self.val = handle_continuous_param(val, "val", prefetch=False)
self.elementwise = elementwise
def _draw_samples(self, size, random_state):
rngs = random_state.duplicate(2)
samples = self.other_param.draw_samples(size, random_state=rngs[0])
elementwise = (
self.elementwise
and not isinstance(self.val, Deterministic))
if elementwise:
exponents = self.val.draw_samples(size, random_state=rngs[1])
else:
exponents = self.val.draw_sample(random_state=rngs[1])
# without this we get int results in the case of
# Power(, )
samples, exponents = both_np_float_if_one_is_float(samples, exponents)
samples_dtype = samples.dtype
# TODO switch to this as numpy>=1.15 is now a requirement
# float_power requires numpy>=1.12
# result = np.float_power(samples, exponents)
# TODO why was float32 type here replaced with complex number
# formulation?
result = np.power(samples.astype(np.complex), exponents).real
if result.dtype != samples_dtype:
result = result.astype(samples_dtype)
return result
def __repr__(self):
return self.__str__()
def __str__(self):
return "Power(%s, %s, %s)" % (
str(self.other_param), str(self.val), self.elementwise)
class Absolute(StochasticParameter):
"""Convert the samples of another parameter to their absolute values.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
Other parameter which's sampled values are to be modified.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Absolute(iap.Uniform(-1.0, 1.0))
Convert a uniform distribution from ``[-1.0, 1.0)`` to ``[0.0, 1.0]``.
"""
def __init__(self, other_param):
super(Absolute, self).__init__()
_assert_arg_is_stoch_param("other_param", other_param)
self.other_param = other_param
def _draw_samples(self, size, random_state):
samples = self.other_param.draw_samples(size, random_state=random_state)
return np.absolute(samples)
def __repr__(self):
return self.__str__()
def __str__(self):
opstr = str(self.other_param)
return "Absolute(%s)" % (opstr,)
class RandomSign(StochasticParameter):
"""Convert a parameter's samples randomly to positive or negative values.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
Other parameter which's sampled values are to be modified.
p_positive : number
Fraction of values that are supposed to be turned to positive values.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.RandomSign(iap.Poisson(1))
Create a poisson distribution with ``alpha=1`` that is mirrored/copied (not
flipped) at the y-axis.
"""
def __init__(self, other_param, p_positive=0.5):
super(RandomSign, self).__init__()
_assert_arg_is_stoch_param("other_param", other_param)
assert ia.is_single_number(p_positive), (
"Expected 'p_positive' to be a number, got %s." % (
type(p_positive)))
assert 0.0 <= p_positive <= 1.0, (
"Expected 'p_positive' to be in the interval [0.0, 1.0], "
"got %.4f." % (p_positive,))
self.other_param = other_param
self.p_positive = p_positive
def _draw_samples(self, size, random_state):
rss = random_state.duplicate(2)
samples = self.other_param.draw_samples(size, random_state=rss[0])
# TODO add method to change from uint to int here instead of assert
assert samples.dtype.kind in ["f", "i"], (
"Expected to get samples of kind float or int, but got dtype %s "
"of kind %s." % (samples.dtype.name, samples.dtype.kind))
# TODO convert to same kind as samples
coinflips = rss[1].binomial(
1, self.p_positive, size=size).astype(np.int8)
signs = coinflips * 2 - 1
# Add absolute here to guarantee that we get p_positive percent of
# positive values. Otherwise we would merely flip p_positive percent
# of all signs.
# TODO test if
# result[coinflips_mask] *= (-1)
# is faster (with protection against mask being empty?)
result = np.absolute(samples) * signs
return result
def __repr__(self):
return self.__str__()
def __str__(self):
opstr = str(self.other_param)
return "RandomSign(%s, %.2f)" % (opstr, self.p_positive)
class ForceSign(StochasticParameter):
"""Convert a parameter's samples to either positive or negative values.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
Other parameter which's sampled values are to be modified.
positive : bool
Whether to force all signs to be positive (``True``) or
negative (``False``).
mode : {'invert', 'reroll'}, optional
Method to change the signs. Valid values are ``invert`` and ``reroll``.
``invert`` means that wrong signs are simply flipped.
``reroll`` means that all samples with wrong signs are sampled again,
optionally many times, until they randomly end up having the correct
sign.
reroll_count_max : int, optional
If `mode` is set to ``reroll``, this determines how often values may
be rerolled before giving up and simply flipping the sign (as in
``mode="invert"``). This shouldn't be set too high, as rerolling is
expensive.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.ForceSign(iap.Poisson(1), positive=False)
Create a poisson distribution with ``alpha=1`` that is flipped towards
negative values.
"""
def __init__(self, other_param, positive, mode="invert",
reroll_count_max=2):
super(ForceSign, self).__init__()
_assert_arg_is_stoch_param("other_param", other_param)
self.other_param = other_param
assert positive in [True, False], (
"Expected 'positive' to be True or False, got type %s." % (
type(positive),))
self.positive = positive
assert mode in ["invert", "reroll"], (
"Expected 'mode' to be \"invert\" or \"reroll\", got %s." % (mode,))
self.mode = mode
assert ia.is_single_integer(reroll_count_max), (
"Expected 'reroll_count_max' to be an integer, got type %s." % (
type(reroll_count_max)))
self.reroll_count_max = reroll_count_max
def _draw_samples(self, size, random_state):
rngs = random_state.duplicate(1+self.reroll_count_max)
samples = self.other_param.draw_samples(size, random_state=rngs[0])
if self.mode == "invert":
if self.positive:
samples[samples < 0] *= (-1)
else:
samples[samples > 0] *= (-1)
else:
if self.positive:
bad_samples = np.where(samples < 0)[0]
else:
bad_samples = np.where(samples > 0)[0]
reroll_count = 0
while len(bad_samples) > 0 and reroll_count < self.reroll_count_max:
# This rerolls the full input size, even when only a tiny
# fraction of the values were wrong. That is done, because not
# all parameters necessarily support any number of dimensions
# for `size`, so we cant just resample size=N for N values
# with wrong signs.
# There is still quite some room for improvement here.
samples_reroll = self.other_param.draw_samples(
size,
random_state=rngs[1+reroll_count]
)
samples[bad_samples] = samples_reroll[bad_samples]
reroll_count += 1
if self.positive:
bad_samples = np.where(samples < 0)[0]
else:
bad_samples = np.where(samples > 0)[0]
if len(bad_samples) > 0:
samples[bad_samples] *= (-1)
return samples
def __repr__(self):
return self.__str__()
def __str__(self):
opstr = str(self.other_param)
return "ForceSign(%s, %s, %s, %d)" % (
opstr, str(self.positive), self.mode, self.reroll_count_max)
def Positive(other_param, mode="invert", reroll_count_max=2):
"""Convert another parameter's results to positive values.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
Other parameter which's sampled values are to be
modified.
mode : {'invert', 'reroll'}, optional
How to change the signs. Valid values are ``invert`` and ``reroll``.
``invert`` means that wrong signs are simply flipped.
``reroll`` means that all samples with wrong signs are sampled again,
optionally many times, until they randomly end up having the correct
sign.
reroll_count_max : int, optional
If `mode` is set to ``reroll``, this determines how often values may
be rerolled before giving up and simply flipping the sign (as in
``mode="invert"``). This shouldn't be set too high, as rerolling is
expensive.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Positive(iap.Normal(0, 1), mode="reroll")
Create a gaussian distribution that has only positive values.
If any negative value is sampled in the process, that sample is resampled
up to two times to get a positive one. If it isn't positive after the
second resampling step, the sign is simply flipped.
"""
# pylint: disable=invalid-name
return ForceSign(
other_param=other_param,
positive=True,
mode=mode,
reroll_count_max=reroll_count_max
)
def Negative(other_param, mode="invert", reroll_count_max=2):
"""Convert another parameter's results to negative values.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
Other parameter which's sampled values are to be
modified.
mode : {'invert', 'reroll'}, optional
How to change the signs. Valid values are ``invert`` and ``reroll``.
``invert`` means that wrong signs are simply flipped.
``reroll`` means that all samples with wrong signs are sampled again,
optionally many times, until they randomly end up having the correct
sign.
reroll_count_max : int, optional
If `mode` is set to ``reroll``, this determines how often values may
be rerolled before giving up and simply flipping the sign (as in
``mode="invert"``). This shouldn't be set too high, as rerolling is
expensive.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Negative(iap.Normal(0, 1), mode="reroll")
Create a gaussian distribution that has only negative values.
If any positive value is sampled in the process, that sample is resampled
up to two times to get a negative one. If it isn't negative after the
second resampling step, the sign is simply flipped.
"""
# pylint: disable=invalid-name
return ForceSign(
other_param=other_param,
positive=False,
mode=mode,
reroll_count_max=reroll_count_max
)
# TODO this always aggregates the result in high resolution space, instead of
# aggregating them in low resolution and then only upscaling the final
# image (for N iterations that would save up to N-1 upscales)
class IterativeNoiseAggregator(StochasticParameter):
"""Aggregate multiple iterations of samples from another parameter.
This is supposed to be used in conjunction with :class:`SimplexNoise` or
:class:`FrequencyNoise`. If a shape ``S`` is requested, it will request
``I`` times ``S`` samples from the underlying parameter, where ``I`` is
the number of iterations. The ``I`` arrays will be combined to a single
array of shape ``S`` using an aggregation method, e.g. simple averaging.
Parameters
----------
other_param : StochasticParameter
The other parameter from which to sample one or more times.
iterations : int or iterable of int or list of int or imgaug.parameters.StochasticParameter, optional
The number of iterations.
* If a single ``int``, this ``int`` will be used as a
constant value.
* If a ``tuple`` of two ``int`` s ``(a, b)``, the value will be
sampled from the discrete interval ``[a..b]`` once per call.
* If a ``list`` of ``int``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of
:func:`IterativeNoiseAggregator.draw_sample` or
:func:`IterativeNoiseAggregator.draw_samples`.
aggregation_method : imgaug.ALL or {'min', 'avg', 'max'} or list of str or imgaug.parameters.StochasticParameter, optional
The method to use to aggregate the samples of multiple iterations
to a single output array. All methods combine several arrays of
shape ``S`` each to a single array of shape ``S`` and hence work
elementwise. Known methods are ``min`` (take the minimum over all
iterations), ``max`` (take the maximum) and ``avg`` (take the average).
* If an ``str``, it must be one of the described methods and
will be used for all calls..
* If a ``list`` of ``str``, it must contain one or more of the
described methods and a random one will be samples once per call.
* If ``imgaug.ALL``, then equivalent to the ``list``
``["min", "max", "avg"]``.
* If :class:`StochasticParameter`, a value will be sampled from
that parameter once per call and must be one of the described
methods..
"per call" denotes a call of
:func:`IterativeNoiseAggregator.draw_sample` or
:func:`IterativeNoiseAggregator.draw_samples`.
Examples
--------
>>> import imgaug.parameters as iap
>>> noise = iap.IterativeNoiseAggregator(
>>> iap.SimplexNoise(),
>>> iterations=(2, 5),
>>> aggregation_method="max")
Create a parameter that -- upon each call -- generates ``2`` to ``5``
arrays of simplex noise with the same shape. Then it combines these
noise maps to a single map using elementwise maximum.
"""
def __init__(self, other_param, iterations=(1, 3),
aggregation_method=["max", "avg"]):
# pylint: disable=dangerous-default-value
super(IterativeNoiseAggregator, self).__init__()
_assert_arg_is_stoch_param("other_param", other_param)
self.other_param = other_param
def _assert_within_bounds(_iterations):
assert all([1 <= val <= 10000 for val in _iterations]), (
"Expected 'iterations' to only contain values within "
"the interval [1, 1000], got values %s." % (
", ".join([str(val) for val in _iterations]),))
if ia.is_single_integer(iterations):
_assert_within_bounds([iterations])
self.iterations = Deterministic(iterations)
elif isinstance(iterations, list):
assert len(iterations) > 0, (
"Expected 'iterations' of type list to contain at least one "
"entry, got %d." % (len(iterations),))
_assert_within_bounds(iterations)
self.iterations = Choice(iterations)
elif ia.is_iterable(iterations):
assert len(iterations) == 2, (
"Expected iterable non-list 'iteratons' to contain exactly "
"two entries, got %d." % (len(iterations),))
assert all([ia.is_single_integer(val) for val in iterations]), (
"Expected iterable non-list 'iterations' to only contain "
"integers, got types %s." % (
", ".join([str(type(val)) for val in iterations]),))
_assert_within_bounds(iterations)
self.iterations = DiscreteUniform(iterations[0], iterations[1])
elif isinstance(iterations, StochasticParameter):
self.iterations = iterations
else:
raise Exception(
"Expected iterations to be int or tuple of two ints or "
"StochasticParameter, got %s." % (type(iterations),))
if aggregation_method == ia.ALL:
self.aggregation_method = Choice(["min", "max", "avg"])
elif ia.is_string(aggregation_method):
self.aggregation_method = Deterministic(aggregation_method)
elif isinstance(aggregation_method, list):
assert len(aggregation_method) >= 1, (
"Expected at least one aggregation method got %d." % (
len(aggregation_method),))
assert all([ia.is_string(val) for val in aggregation_method]), (
"Expected aggregation methods provided as strings, "
"got types %s." % (
", ".join([str(type(v)) for v in aggregation_method])))
self.aggregation_method = Choice(aggregation_method)
elif isinstance(aggregation_method, StochasticParameter):
self.aggregation_method = aggregation_method
else:
raise Exception(
"Expected aggregation_method to be string or list of strings "
"or StochasticParameter, got %s." % (
type(aggregation_method),))
def _draw_samples(self, size, random_state):
rngs = random_state.duplicate(2)
aggregation_method = self.aggregation_method.draw_sample(
random_state=rngs[0])
iterations = self.iterations.draw_sample(random_state=rngs[1])
assert iterations > 0, (
"Expected to sample at least one iteration of aggregation. "
"Got %d." % (iterations,))
rngs_iterations = rngs[1].duplicate(iterations)
result = np.zeros(size, dtype=np.float32)
for i in sm.xrange(iterations):
noise_iter = self.other_param.draw_samples(
size, random_state=rngs_iterations[i])
if aggregation_method == "avg":
result += noise_iter
elif aggregation_method == "min":
if i == 0:
result = noise_iter
else:
result = np.minimum(result, noise_iter)
else: # self.aggregation_method == "max"
if i == 0:
result = noise_iter
else:
result = np.maximum(result, noise_iter)
if aggregation_method == "avg":
result = result / iterations
return result
def __repr__(self):
return self.__str__()
def __str__(self):
opstr = str(self.other_param)
return "IterativeNoiseAggregator(%s, %s, %s)" % (
opstr, str(self.iterations), str(self.aggregation_method))
class Sigmoid(StochasticParameter):
"""Apply a sigmoid function to the outputs of another parameter.
This is intended to be used in combination with :class:`SimplexNoise` or
:class:`FrequencyNoise`. It pushes the noise values away from ``~0.5`` and
towards ``0.0`` or ``1.0``, making the noise maps more binary.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
The other parameter to which the sigmoid will be applied.
threshold : number or tuple of number or iterable of number or imgaug.parameters.StochasticParameter, optional
Sets the value of the sigmoid's saddle point, i.e. where values
start to quickly shift from ``0.0`` to ``1.0``.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`Sigmoid.draw_sample` or
:func:`Sigmoid.draw_samples`.
activated : bool or number, optional
Defines whether the sigmoid is activated. If this is ``False``, the
results of `other_param` will not be altered. This may be set to a
``float`` ``p`` in value range``[0.0, 1.0]``, which will result in
`activated` being ``True`` in ``p`` percent of all calls.
mul : number, optional
The results of `other_param` will be multiplied with this value before
applying the sigmoid. For noise values (range ``[0.0, 1.0]``) this
should be set to about ``20``.
add : number, optional
This value will be added to the results of `other_param` before
applying the sigmoid. For noise values (range ``[0.0, 1.0]``) this
should be set to about ``-10.0``, provided `mul` was set to ``20``.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.Sigmoid(
>>> iap.SimplexNoise(),
>>> activated=0.5,
>>> mul=20,
>>> add=-10)
Applies a sigmoid to simplex noise in ``50%`` of all calls. The noise
results are modified to match the sigmoid's expected value range. The
sigmoid's outputs are in the range ``[0.0, 1.0]``.
"""
def __init__(self, other_param, threshold=(-10, 10), activated=True,
mul=1, add=0):
super(Sigmoid, self).__init__()
_assert_arg_is_stoch_param("other_param", other_param)
self.other_param = other_param
self.threshold = handle_continuous_param(threshold, "threshold",
prefetch=False)
self.activated = handle_probability_param(activated, "activated",
prefetch=False)
assert ia.is_single_number(mul), (
"Expected 'mul' to be a number, got type %s." % (type(mul),))
assert mul > 0, (
"Expected 'mul' to be greater than zero, got %.4f." % (mul,))
self.mul = mul
assert ia.is_single_number(add), (
"Expected 'add' to be a number, got type %s." % (type(add),))
self.add = add
@staticmethod
def create_for_noise(other_param, threshold=(-10, 10), activated=True):
"""Create a Sigmoid adjusted for noise parameters.
"noise" here denotes :class:`SimplexNoise` and :class:`FrequencyNoise`.
Parameters
----------
other_param : imgaug.parameters.StochasticParameter
See :func:`~imgaug.parameters.Sigmoid.__init__`.
threshold : number or tuple of number or iterable of number or imgaug.parameters.StochasticParameter, optional
See :func:`~imgaug.parameters.Sigmoid.__init__`.
activated : bool or number, optional
See :func:`~imgaug.parameters.Sigmoid.__init__`.
Returns
-------
Sigmoid
A sigmoid adjusted to be used with noise.
"""
return Sigmoid(other_param, threshold, activated, mul=20, add=-10)
def _draw_samples(self, size, random_state):
rngs = random_state.duplicate(3)
result = self.other_param.draw_samples(size, random_state=rngs[0])
if result.dtype.kind != "f":
result = result.astype(np.float32)
activated = self.activated.draw_sample(random_state=rngs[1])
threshold = self.threshold.draw_sample(random_state=rngs[2])
if activated > 0.5:
# threshold must be subtracted here, not added
# higher threshold = move threshold of sigmoid towards the right
# = make it harder to pass the threshold
# = more 0.0s / less 1.0s
# by subtracting a high value, it moves each x towards the left,
# leading to more values being left of the threshold, leading
# to more 0.0s
return 1 / (1 + np.exp(-(result * self.mul + self.add - threshold)))
return result
def __repr__(self):
return self.__str__()
def __str__(self):
opstr = str(self.other_param)
return "Sigmoid(%s, %s, %s, %s, %s)" % (
opstr, str(self.threshold), str(self.activated), str(self.mul),
str(self.add))
class SimplexNoise(StochasticParameter):
"""Parameter that generates simplex noise of varying resolutions.
This parameter expects to sample noise for 2d planes, i.e. for
sizes ``(H, W, [C])`` and will return a value in the range ``[0.0, 1.0]``
per spatial location in that plane.
The noise is sampled from low resolution planes and
upscaled to the requested height and width. The size of the low
resolution plane may be defined (large values can be slow) and the
interpolation method for upscaling can be set.
Parameters
----------
size_px_max : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Maximum height and width in pixels of the low resolution plane.
Upon any sampling call, the requested shape will be downscaled until
the height or width (whichever is larger) does not exceed this maximum
value anymore. Then the noise will be sampled at that shape and later
upscaled back to the requested shape.
* If a single ``int``, this ``int`` will be used as a
constant value.
* If a ``tuple`` of two ``int`` s ``(a, b)``, the value will be
sampled from the discrete interval ``[a..b]`` once per call.
* If a ``list`` of ``int``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`SimplexNoise.draw_sample` or
:func:`SimplexNoise.draw_samples`.
upscale_method : str or int or list of str or list of int or imgaug.parameters.StochasticParameter, optional
After generating the noise maps in low resolution environments, they
have to be upscaled to the originally requested shape (i.e. usually
the image size). This parameter controls the interpolation method to
use. See also :func:`~imgaug.imgaug.imresize_many_images` for a
description of possible values.
* If ``imgaug.ALL``, then either ``nearest`` or ``linear`` or
``area`` or ``cubic`` is picked per iteration (all same
probability).
* If ``str``, then that value will always be used as the method
(must be ``nearest`` or ``linear`` or ``area`` or ``cubic``).
* If ``list`` of ``str``, then a random value will be picked from
that list per call.
* If :class:`StochasticParameter`, then a random value will be
sampled from that parameter per call.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.SimplexNoise(upscale_method="linear")
Create a parameter that produces smooth simplex noise of varying sizes.
>>> param = iap.SimplexNoise(
>>> size_px_max=(8, 16),
>>> upscale_method="nearest")
Create a parameter that produces rectangular simplex noise of rather
high detail.
"""
def __init__(self, size_px_max=(2, 16),
upscale_method=["linear", "nearest"]):
# pylint: disable=dangerous-default-value
super(SimplexNoise, self).__init__()
self.size_px_max = handle_discrete_param(
size_px_max, "size_px_max", value_range=(1, 10000))
if upscale_method == ia.ALL:
self.upscale_method = Choice(["nearest", "linear", "area",
"cubic"])
elif ia.is_string(upscale_method):
self.upscale_method = Deterministic(upscale_method)
elif isinstance(upscale_method, list):
assert len(upscale_method) >= 1, (
"Expected at least one upscale method, "
"got %d." % (len(upscale_method),))
assert all([ia.is_string(val) for val in upscale_method]), (
"Expected all upscale methods to be strings, got types %s." % (
", ".join([str(type(v)) for v in upscale_method])))
self.upscale_method = Choice(upscale_method)
elif isinstance(upscale_method, StochasticParameter):
self.upscale_method = upscale_method
else:
raise Exception(
"Expected upscale_method to be string or list of strings or "
"StochasticParameter, got %s." % (type(upscale_method),))
def _draw_samples(self, size, random_state):
assert len(size) in [2, 3], (
"Expected requested noise to have shape (H, W) or (H, W, C), "
"got shape %s." % (size,))
height, width = size[0:2]
nb_channels = 1 if len(size) == 2 else size[2]
channels = [self._draw_samples_hw(height, width, random_state)
for _ in np.arange(nb_channels)]
if len(size) == 2:
return channels[0]
return np.stack(channels, axis=-1)
def _draw_samples_hw(self, height, width, random_state):
iterations = 1
rngs = random_state.duplicate(1+iterations)
aggregation_method = "max"
upscale_methods = self.upscale_method.draw_samples(
(iterations,), random_state=rngs[0])
result = np.zeros((height, width), dtype=np.float32)
for i in sm.xrange(iterations):
noise_iter = self._draw_samples_iteration(
height, width, rngs[1+i], upscale_methods[i])
if aggregation_method == "avg":
result += noise_iter
elif aggregation_method == "min":
if i == 0:
result = noise_iter
else:
result = np.minimum(result, noise_iter)
else: # self.aggregation_method == "max"
if i == 0:
result = noise_iter
else:
result = np.maximum(result, noise_iter)
if aggregation_method == "avg":
result = result / iterations
return result
def _draw_samples_iteration(self, height, width, rng, upscale_method):
opensimplex_seed = rng.generate_seed_()
# we have to use int(.) here, otherwise we can get warnings about
# value overflows in OpenSimplex L103
generator = OpenSimplex(seed=int(opensimplex_seed))
maxlen = max(height, width)
size_px_max = self.size_px_max.draw_sample(random_state=rng)
if maxlen > size_px_max:
downscale_factor = size_px_max / maxlen
h_small = int(height * downscale_factor)
w_small = int(width * downscale_factor)
else:
h_small = height
w_small = width
# don't go below Hx1 or 1xW
h_small = max(h_small, 1)
w_small = max(w_small, 1)
noise = np.zeros((h_small, w_small), dtype=np.float32)
for y in sm.xrange(h_small):
for x in sm.xrange(w_small):
noise[y, x] = generator.noise2d(y=y, x=x)
# TODO this was previously (noise+0.5)/2, which was wrong as the noise
# here is in range [-1.0, 1.0], but this new normalization might
# lead to bad masks due to too many values being significantly
# above 0.0 instead of being clipped to 0?
noise_0to1 = (noise + 1.0) / 2
noise_0to1 = np.clip(noise_0to1, 0.0, 1.0)
if noise_0to1.shape != (height, width):
noise_0to1_uint8 = (noise_0to1 * 255).astype(np.uint8)
noise_0to1_3d = np.tile(
noise_0to1_uint8[..., np.newaxis], (1, 1, 3))
noise_0to1 = ia.imresize_single_image(
noise_0to1_3d, (height, width), interpolation=upscale_method)
noise_0to1 = (noise_0to1[..., 0] / 255.0).astype(np.float32)
return noise_0to1
def __repr__(self):
return self.__str__()
def __str__(self):
return "SimplexNoise(%s, %s)" % (
str(self.size_px_max),
str(self.upscale_method)
)
class FrequencyNoise(StochasticParameter):
"""Parameter to generate noise of varying frequencies.
This parameter expects to sample noise for 2d planes, i.e. for
sizes ``(H, W, [C])`` and will return a value in the range ``[0.0, 1.0]``
per spatial location in that plane.
The exponent controls the frequencies and therefore noise patterns.
Small values (around ``-4.0``) will result in large blobs. Large values
(around ``4.0``) will result in small, repetitive patterns.
The noise is sampled from low resolution planes and
upscaled to the requested height and width. The size of the low
resolution plane may be defined (high values can be slow) and the
interpolation method for upscaling can be set.
Parameters
----------
exponent : number or tuple of number or list of number or imgaug.parameters.StochasticParameter, optional
Exponent to use when scaling in the frequency domain.
Sane values are in the range ``-4`` (large blobs) to ``4`` (small
patterns). To generate cloud-like structures, use roughly ``-2``.
* If a single ``number``, this ``number`` will be used as a
constant value.
* If a ``tuple`` of two ``number`` s ``(a, b)``, the value will be
sampled from the continuous interval ``[a, b)`` once per call.
* If a ``list`` of ``number``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
size_px_max : int or tuple of int or list of int or imgaug.parameters.StochasticParameter, optional
Maximum height and width in pixels of the low resolution plane.
Upon any sampling call, the requested shape will be downscaled until
the height or width (whichever is larger) does not exceed this maximum
value anymore. Then the noise will be sampled at that shape and later
upscaled back to the requested shape.
* If a single ``int``, this ``int`` will be used as a
constant value.
* If a ``tuple`` of two ``int`` s ``(a, b)``, the value will be
sampled from the discrete interval ``[a..b]`` once per call.
* If a ``list`` of ``int``, a random value will be picked from
the ``list`` once per call.
* If a :class:`StochasticParameter`, that parameter will be
queried once per call.
"per call" denotes a call of :func:`FrequencyNoise.draw_sample` or
:func:`FrequencyNoise.draw_samples`.
upscale_method : imgaug.ALL or str or list of str or imgaug.parameters.StochasticParameter, optional
After generating the noise maps in low resolution environments, they
have to be upscaled to the originally requested shape (i.e. usually
the image size). This parameter controls the interpolation method to
use. See also :func:`~imgaug.imgaug.imresize_many_images` for a
description of possible values.
* If ``imgaug.ALL``, then either ``nearest`` or ``linear`` or
``area`` or ``cubic`` is picked per iteration (all same
probability).
* If ``str``, then that value will always be used as the method
(must be ``nearest`` or ``linear`` or ``area`` or ``cubic``).
* If ``list`` of ``str``, then a random value will be picked from
that list per call.
* If :class:`StochasticParameter`, then a random value will be
sampled from that parameter per call.
Examples
--------
>>> import imgaug.parameters as iap
>>> param = iap.FrequencyNoise(
>>> exponent=-2,
>>> size_px_max=(16, 32),
>>> upscale_method="linear")
Create a parameter that produces noise with cloud-like patterns.
"""
def __init__(self, exponent=(-4, 4), size_px_max=(4, 32),
upscale_method=["linear", "nearest"]):
# pylint: disable=dangerous-default-value
super(FrequencyNoise, self).__init__()
self.exponent = handle_continuous_param(exponent, "exponent")
self.size_px_max = handle_discrete_param(
size_px_max, "size_px_max", value_range=(1, 10000))
if upscale_method == ia.ALL:
self.upscale_method = Choice(["nearest", "linear", "area",
"cubic"])
elif ia.is_string(upscale_method):
self.upscale_method = Deterministic(upscale_method)
elif isinstance(upscale_method, list):
assert len(upscale_method) >= 1, (
"Expected at least one upscale method, "
"got %d." % (len(upscale_method),))
assert all([ia.is_string(val) for val in upscale_method]), (
"Expected all upscale methods to be strings, got types %s." % (
", ".join([str(type(v)) for v in upscale_method])))
self.upscale_method = Choice(upscale_method)
elif isinstance(upscale_method, StochasticParameter):
self.upscale_method = upscale_method
else:
raise Exception(
"Expected upscale_method to be string or list of strings or "
"StochasticParameter, got %s." % (type(upscale_method),))
self._distance_matrix_cache = np.zeros((0, 0), dtype=np.float32)
# TODO this is the same as in SimplexNoise, make DRY
def _draw_samples(self, size, random_state):
# code here is similar to:
# http://www.redblobgames.com/articles/noise/2d/
# http://www.redblobgames.com/articles/noise/2d/2d-noise.js
assert len(size) in [2, 3], (
"Expected requested noise to have shape (H, W) or (H, W, C), "
"got shape %s." % (size,))
height, width = size[0:2]
nb_channels = 1 if len(size) == 2 else size[2]
channels = [self._draw_samples_hw(height, width, random_state)
for _ in np.arange(nb_channels)]
if len(size) == 2:
return channels[0]
return np.stack(channels, axis=-1)
def _draw_samples_hw(self, height, width, random_state):
maxlen = max(height, width)
size_px_max = self.size_px_max.draw_sample(random_state=random_state)
h_small, w_small = height, width
if maxlen > size_px_max:
downscale_factor = size_px_max / maxlen
h_small = int(height * downscale_factor)
w_small = int(width * downscale_factor)
# don't go below Hx4 or 4xW
h_small = max(h_small, 4)
w_small = max(w_small, 4)
# exponents to pronounce some frequencies
exponent = self.exponent.draw_sample(random_state=random_state)
# base function to invert, derived from a distance matrix (euclidean
# distance to image center)
f = self._get_distance_matrix_cached((h_small, w_small))
# prevent divide by zero warnings at the image corners in
# f**exponent
f[0, 0] = 1
f[-1, 0] = 1
f[0, -1] = 1
f[-1, -1] = 1
scale = f ** exponent
# invert setting corners to 1
scale[0, 0] = 0
scale[-1, 0] = 0
scale[0, -1] = 0
scale[-1, -1] = 0
# generate random base matrix
# first channel: wn_r, second channel: wn_a
wn = random_state.random(size=(2, h_small, w_small))
wn[0, ...] *= (max(h_small, w_small) ** 2)
wn[1, ...] *= 2 * np.pi
wn[0, ...] *= np.cos(wn[1, ...])
wn[1, ...] *= np.sin(wn[1, ...])
wn *= scale[np.newaxis, :, :]
wn = wn.transpose((1, 2, 0))
if wn.dtype != np.float32:
wn = wn.astype(np.float32)
# equivalent but slightly faster then:
# wn_freqs_mul = np.zeros(treal.shape, dtype=np.complex)
# wn_freqs_mul.real = wn[0]
# wn_freqs_mul.imag = wn[1]
# wn_inv = np.fft.ifft2(wn_freqs_mul).real
wn_inv = cv2.idft(wn)[:, :, 0]
# normalize to 0 to 1
# equivalent to but slightly faster than:
# wn_inv_min = np.min(wn_inv)
# wn_inv_max = np.max(wn_inv)
# noise_0to1 = (wn_inv - wn_inv_min) / (wn_inv_max - wn_inv_min)
# does not accept wn_inv as dst directly
noise_0to1 = cv2.normalize(
wn_inv,
dst=np.zeros_like(wn_inv),
alpha=0.01,
beta=1.0,
norm_type=cv2.NORM_MINMAX
)
# upscale from low resolution to image size
if noise_0to1.shape != (height, width):
upscale_method = self.upscale_method.draw_sample(
random_state=random_state
)
noise_0to1 = ia.imresize_single_image(
noise_0to1.astype(np.float32),
(height, width),
interpolation=upscale_method)
if upscale_method == "cubic":
noise_0to1 = np.clip(noise_0to1, 0.0, 1.0)
return noise_0to1
def _get_distance_matrix_cached(self, size):
cache = self._distance_matrix_cache
height, width = cache.shape
if height < size[0] or width < size[1]:
self._distance_matrix_cache = self._create_distance_matrix(
(max(height, size[0]), max(width, size[1]))
)
return self._extract_distance_matrix(self._distance_matrix_cache, size)
@classmethod
def _extract_distance_matrix(cls, matrix, size):
height, width = matrix.shape[0:2]
leftover_y = (height - size[0]) / 2
leftover_x = (width - size[1]) / 2
y1 = int(np.floor(leftover_y))
y2 = height - int(np.ceil(leftover_y))
x1 = int(np.floor(leftover_x))
x2 = width - int(np.ceil(leftover_x))
return matrix[y1:y2, x1:x2]
@classmethod
def _create_distance_matrix(cls, size):
def _create_line(line_size):
start = np.arange(line_size // 2)
middle = [line_size//2] if line_size % 2 == 1 else []
end = start[::-1]
return np.concatenate([start, middle, end])
height, width = size
ydist = _create_line(height) ** 2
xdist = _create_line(width) ** 2
ydist_2d = np.broadcast_to(ydist[:, np.newaxis], size)
xdist_2d = np.broadcast_to(xdist[np.newaxis, :], size)
dist = np.sqrt(ydist_2d + xdist_2d)
return dist
def __repr__(self):
return self.__str__()
def __str__(self):
return "FrequencyNoise(%s, %s, %s)" % (
str(self.exponent),
str(self.size_px_max),
str(self.upscale_method))
def _assert_arg_is_stoch_param(arg_name, arg_value):
assert isinstance(arg_value, StochasticParameter), (
"Expected '%s' to be a StochasticParameter, "
"got type %s." % (arg_name, arg_value,))
================================================
FILE: imgaug/quokka_annotations.json
================================================
{
"bounding_boxes": [
{
"x1": 148.0,
"y1": 50.0,
"x2": 550.0,
"y2": 642.0,
"label": "animal"
}
],
"keypoints": [
{
"x": 163.0,
"y": 78.0,
"label": "ear_tip_left"
},
{
"x": 422.0,
"y": 56.0,
"label": "ear_tip_right"
},
{
"x": 248.0,
"y": 205.0,
"label": "eye_left"
},
{
"x": 366.0,
"y": 194.0,
"label": "eye_right"
},
{
"x": 313.0,
"y": 270.0,
"label": "nose"
},
{
"x": 244.0,
"y": 535.0,
"label": "paw_left"
},
{
"x": 290,
"y": 536,
"label": "paw_right"
}
],
"polygons": [
{
"keypoints": [
{"x": 422.0, "y": 56.0},
{"x": 435.0, "y": 66.0},
{"x": 435.0, "y": 111.0},
{"x": 435.0, "y": 111.0},
{"x": 422.0, "y": 158.0},
{"x": 451.0, "y": 233.0},
{"x": 468.0, "y": 298.0},
{"x": 474.0, "y": 355.0},
{"x": 474.0, "y": 374.0},
{"x": 515.0, "y": 395.0},
{"x": 541.0, "y": 467.0},
{"x": 548.0, "y": 499.0},
{"x": 547.0, "y": 559.0},
{"x": 532.0, "y": 641.0},
{"x": 235.0, "y": 641.0},
{"x": 208.0, "y": 608.0},
{"x": 188.0, "y": 574.0},
{"x": 177.0, "y": 535.0},
{"x": 159.0, "y": 493.0},
{"x": 151.0, "y": 466.0},
{"x": 154.0, "y": 432.0},
{"x": 155.0, "y": 389.0},
{"x": 147.0, "y": 363.0},
{"x": 143.0, "y": 329.0},
{"x": 145.0, "y": 289.0},
{"x": 170.0, "y": 237.0},
{"x": 181.0, "y": 186.0},
{"x": 182.0, "y": 168.0},
{"x": 163.0, "y": 144.0},
{"x": 154.0, "y": 115.0},
{"x": 155.0, "y": 84.0},
{"x": 172.0, "y": 69.0},
{"x": 205.0, "y": 60.0},
{"x": 244.0, "y": 62.0},
{"x": 279.0, "y": 66.0},
{"x": 307.0, "y": 71.0},
{"x": 345.0, "y": 61.0},
{"x": 375.0, "y": 60.0},
{"x": 403.0, "y": 50.0}
]
}
]
}
================================================
FILE: imgaug/random.py
================================================
"""Classes and functions related to pseudo-random number generation.
This module deals with the generation of pseudo-random numbers.
It provides the :class:`~imgaug.random.RNG` class, which is the primary
random number generator in ``imgaug``. It also provides various utility
functions related random number generation, such as copying random number
generators or setting their state.
The main benefit of this module is to hide the actually used random number
generation classes and methods behin imgaug-specific classes and methods.
This allows to deal with numpy using two different interfaces (one old
interface in numpy <=1.16 and a new one in numpy 1.17+). It also allows
to potentially switch to a different framework/library in the future.
Definitions
-----------
- *numpy generator* or *numpy random number generator*: Usually an instance
of :class:`numpy.random.Generator`. Can often also denote an instance
of :class:`numpy.random.RandomState` as both have almost the same interface.
- *RandomState*: An instance of `numpy.random.RandomState`.
Note that outside of this module, the term "random state" often roughly
translates to "any random number generator with numpy-like interface
in a given state", i.e. it can then include instances of
:class:`numpy.random.Generator` or :class:`~imgaug.random.RNG`.
- *RNG*: An instance of :class:`~imgaug.random.RNG`.
Examples
--------
>>> import imgaug.random as iarandom
>>> rng = iarandom.RNG(1234)
>>> rng.integers(0, 1000)
Initialize a random number generator with seed ``1234``, then sample
a single integer from the discrete interval ``[0, 1000)``.
This will use a :class:`numpy.random.Generator` in numpy 1.17+ and
automatically fall back to :class:`numpy.random.RandomState` in numpy <=1.16.
"""
from __future__ import print_function, division, absolute_import
import copy as copylib
import numpy as np
import six.moves as sm
# Check if numpy is version 1.17 or later. In that version, the new random
# number interface was added.
# Note that a valid version number can also be "1.18.0.dev0+285ab1d",
# in which the last component cannot easily be converted to an int. Hence we
# only pick the first two components.
SUPPORTS_NEW_NP_RNG_STYLE = False
BIT_GENERATOR = None
_NP_VERSION = list(map(int, np.__version__.split(".")[0:2]))
if _NP_VERSION[0] > 1 or _NP_VERSION[1] >= 17:
SUPPORTS_NEW_NP_RNG_STYLE = True
BIT_GENERATOR = np.random.SFC64 # pylint: disable=invalid-name
# interface of BitGenerator
# in 1.17 this was at numpy.random.bit_generator.BitGenerator
# in 1.18 this was moved to numpy.random.BitGenerator
# pylint: disable=invalid-name, no-member
if _NP_VERSION[1] == 17:
# Added in 0.4.0.
_BIT_GENERATOR_INTERFACE = np.random.bit_generator.BitGenerator
else:
# Added in 0.4.0.
_BIT_GENERATOR_INTERFACE = np.random.BitGenerator
# pylint: enable=invalid-name, no-member
# We instantiate a current/global random state here once.
GLOBAL_RNG = None
# use 2**31 instead of 2**32 as the maximum here, because 2**31 errored on
# some systems
SEED_MIN_VALUE = 0
SEED_MAX_VALUE = 2**31-1
# Added in 0.5.0.
_RNG_IDX = 1
# TODO decrease pool_size in SeedSequence to 2 or 1?
# TODO add 'with resetted_rng(...)'
# TODO change random_state to rng or seed
class RNG(object):
"""
Random number generator for imgaug.
This class is a wrapper around ``numpy.random.Generator`` and
automatically falls back to ``numpy.random.RandomState`` in case of
numpy version 1.16 or lower. It allows to use numpy 1.17's sampling
functions in 1.16 too and supports a variety of useful functions on
the wrapped sampler, e.g. gettings its state or copying it.
Not supported sampling functions of numpy <=1.16:
* :func:`numpy.random.RandomState.rand`
* :func:`numpy.random.RandomState.randn`
* :func:`numpy.random.RandomState.randint`
* :func:`numpy.random.RandomState.random_integers`
* :func:`numpy.random.RandomState.random_sample`
* :func:`numpy.random.RandomState.ranf`
* :func:`numpy.random.RandomState.sample`
* :func:`numpy.random.RandomState.seed`
* :func:`numpy.random.RandomState.get_state`
* :func:`numpy.random.RandomState.set_state`
In :func:`~imgaug.random.RNG.choice`, the `axis` argument is not yet
supported.
Parameters
----------
generator : None or int or RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
The numpy random number generator to use. In case of numpy
version 1.17 or later, this shouldn't be a ``RandomState`` as that
class is outdated.
Behaviour for different datatypes:
* If ``None``: The global RNG is wrapped by this RNG (they are then
effectively identical, any sampling on this RNG will affect the
global RNG).
* If ``int``: In numpy 1.17+, the value is used as a seed for a
``Generator`` wrapped by this RNG. I.e. it will be provided as the
entropy to a ``SeedSequence``, which will then be used for an
``SFC64`` bit generator and wrapped by a ``Generator``.
In numpy <=1.16, the value is used as a seed for a ``RandomState``,
which is then wrapped by this RNG.
* If :class:`RNG`: That RNG's ``generator`` attribute will be used
as the generator for this RNG, i.e. the same as
``RNG(other_rng.generator)``.
* If :class:`numpy.random.Generator`: That generator will be wrapped.
* If :class:`numpy.random.BitGenerator`: A numpy
generator will be created (and wrapped by this RNG) that contains
the bit generator.
* If :class:`numpy.random.SeedSequence`: A numpy
generator will be created (and wrapped by this RNG) that contains
an ``SFC64`` bit generator initialized with the given
``SeedSequence``.
* If :class:`numpy.random.RandomState`: In numpy <=1.16, this
``RandomState`` will be wrapped and used to sample random values.
In numpy 1.17+, a seed will be derived from this ``RandomState``
and a new ``numpy.generator.Generator`` based on an ``SFC64``
bit generator will be created and wrapped by this RNG.
"""
# TODO add maybe a __new__ here that feeds-through an RNG input without
# wrapping it in RNG(rng_input)?
def __init__(self, generator):
# pylint: disable=protected-access, global-statement
global _RNG_IDX
if isinstance(generator, RNG):
self.generator = generator.generator
else:
self.generator = normalize_generator_(generator)
self._is_new_rng_style = (
not isinstance(self.generator, np.random.RandomState))
# _idx is used to have a unique id for each RNG.
# This is currently necessary for AutoPrefetcher. It could be done
# similarly via the generator state, though at a much higher
# computational cost. id(rng) cannot be used for this as multiple
# RNG instances with different states may have the same id() value.
self._idx = _RNG_IDX
_RNG_IDX += 1
@property
def state(self):
"""Get the state of this RNG.
Returns
-------
tuple or dict
The state of the RNG.
In numpy 1.17+, the bit generator's state will be returned.
In numpy <=1.16, the ``RandomState`` 's state is returned.
In both cases the state is a copy. In-place changes will not affect
the RNG.
"""
return get_generator_state(self.generator)
@state.setter
def state(self, value):
"""Set the state if the RNG in-place.
Parameters
----------
value : tuple or dict
The new state of the RNG.
Should correspond to the output of the ``state`` property.
"""
self.set_state_(value)
def set_state_(self, value):
"""Set the state if the RNG in-place.
Parameters
----------
value : tuple or dict
The new state of the RNG.
Should correspond to the output of the ``state`` property.
Returns
-------
RNG
The RNG itself.
"""
set_generator_state_(self.generator, value)
return self
def use_state_of_(self, other):
"""Copy and use (in-place) the state of another RNG.
.. note::
It is often sensible to first verify that neither this RNG nor
`other` are identical to the global RNG.
Parameters
----------
other : RNG
The other RNG, which's state will be copied.
Returns
-------
RNG
The RNG itself.
"""
return self.set_state_(other.state)
def is_global_rng(self):
"""Estimate whether this RNG is identical to the global RNG.
Returns
-------
bool
``True`` is this RNG's underlying generator is identical to the
global RNG's underlying generator. The RNGs themselves may
be different, only the wrapped generator matters.
``False`` otherwise.
"""
# We use .generator here, because otherwise RNG(global_rng) would be
# viewed as not-identical to the global RNG, even though its generator
# and bit generator are identical.
return get_global_rng().generator is self.generator
def equals_global_rng(self):
"""Estimate whether this RNG has the same state as the global RNG.
Returns
-------
bool
``True`` is this RNG has the same state as the global RNG, i.e.
it will lead to the same sampled values given the same sampling
method calls. The RNGs *don't* have to be identical object
instances, which protects against e.g. copy effects.
``False`` otherwise.
"""
return get_global_rng().equals(self)
def generate_seed_(self):
"""Sample a random seed.
This advances the underlying generator's state.
See ``SEED_MIN_VALUE`` and ``SEED_MAX_VALUE`` for the seed's value
range.
Returns
-------
int
The sampled seed.
"""
return generate_seed_(self.generator)
def generate_seeds_(self, n):
"""Generate `n` random seed values.
This advances the underlying generator's state.
See ``SEED_MIN_VALUE`` and ``SEED_MAX_VALUE`` for the seed's value
range.
Parameters
----------
n : int
Number of seeds to sample.
Returns
-------
ndarray
1D-array of ``int32`` seeds.
"""
return generate_seeds_(self.generator, n)
def reset_cache_(self):
"""Reset all cache of this RNG.
Returns
-------
RNG
The RNG itself.
"""
reset_generator_cache_(self.generator)
return self
def derive_rng_(self):
"""Create a child RNG.
This advances the underlying generator's state.
Returns
-------
RNG
A child RNG.
"""
return self.derive_rngs_(1)[0]
def derive_rngs_(self, n):
"""Create `n` child RNGs.
This advances the underlying generator's state.
Parameters
----------
n : int
Number of child RNGs to derive.
Returns
-------
list of RNG
Child RNGs.
"""
return [RNG(gen) for gen in derive_generators_(self.generator, n)]
def equals(self, other):
"""Estimate whether this RNG and `other` have the same state.
Returns
-------
bool
``True`` if this RNG's generator and the generator of `other`
have equal internal states. ``False`` otherwise.
"""
assert isinstance(other, RNG), (
"Expected 'other' to be an RNG, got type %s. "
"Use imgaug.random.is_generator_equal_to() to compare "
"numpy generators or RandomStates." % (type(other),))
return is_generator_equal_to(self.generator, other.generator)
def advance_(self):
"""Advance the RNG's internal state in-place by one step.
This advances the underlying generator's state.
.. note::
This simply samples one or more random values. This means that
a call of this method will not completely change the outputs of
the next called sampling method. To achieve more drastic output
changes, call :func:`~imgaug.random.RNG.derive_rng_`.
Returns
-------
RNG
The RNG itself.
"""
advance_generator_(self.generator)
return self
def copy(self):
"""Create a copy of this RNG.
Returns
-------
RNG
Copy of this RNG. The copy will produce the same random samples.
"""
return RNG(copy_generator(self.generator))
def copy_unless_global_rng(self):
"""Create a copy of this RNG unless it is the global RNG.
Returns
-------
RNG
Copy of this RNG unless it is the global RNG. In the latter case
the RNG instance itself will be returned without any changes.
"""
if self.is_global_rng():
return self
return self.copy()
def duplicate(self, n):
"""Create a list containing `n` times this RNG.
This method was mainly introduced as a replacement for previous
calls of :func:`~imgaug.random.RNG.derive_rngs_`. These calls
turned out to be very slow in numpy 1.17+ and were hence replaced
by simple duplication (except for the cases where child RNGs
absolutely *had* to be created).
This RNG duplication method doesn't help very much against code
repetition, but it does *mark* the points where it would be desirable
to create child RNGs for various reasons. Once deriving child RNGs
is somehow sped up in the future, these calls can again be
easily found and replaced.
Parameters
----------
n : int
Length of the output list.
Returns
-------
list of RNG
List containing `n` times this RNG (same instances, no copies).
"""
return [self for _ in sm.xrange(n)]
@classmethod
def create_fully_random(cls):
"""Create a new RNG, based on entropy provided from the OS.
Returns
-------
RNG
A new RNG. It is not derived from any other previously created
RNG, nor does it depend on the seeding of imgaug or numpy.
"""
return RNG(create_fully_random_generator())
@classmethod
def create_pseudo_random_(cls):
"""Create a new RNG in pseudo-random fashion.
A seed will be sampled from the current global RNG and used to
initialize the new RNG.
This advandes the global RNG's state.
Returns
-------
RNG
A new RNG, derived from the current global RNG.
"""
return get_global_rng().derive_rng_()
@classmethod
def create_if_not_rng_(cls, generator):
"""Create a new RNG from any input, but feed-through RNGs unchanged.
Added in 0.5.0.
Parameters
----------
generator : None or int or RNG or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
The numpy random number generator to use.
If this is an :class:`RNG`, it will be returned without change.
See ``__init__`` for details.
Returns
-------
RNG
If an :class:`RNG` was provided, then the input :class:`RNG`
instance without any change. Otherwise equivalent to
``RNG(inputs)``.
"""
if isinstance(generator, RNG):
return generator
return RNG(generator)
###########################################################################
# Below:
# Aliases for methods of numpy.random.Generator functions
#
# The methods below could also be handled with less code using some magic
# methods. Explicitly writing things down here has the advantage that
# the methods actually appear in the autogenerated API.
###########################################################################
def integers(self, low, high=None, size=None, dtype="int32",
endpoint=False):
"""Call numpy's ``integers()`` or ``randint()``.
.. note::
Changed `dtype` argument default value from numpy's ``int64`` to
``int32``.
"""
return polyfill_integers(
self.generator, low=low, high=high, size=size, dtype=dtype,
endpoint=endpoint)
def random(self, size, dtype="float32", out=None):
"""Call numpy's ``random()`` or ``random_sample()``.
.. note::
Changed `dtype` argument default value from numpy's ``d`` to
``float32``.
"""
return polyfill_random(
self.generator, size=size, dtype=dtype, out=out)
# TODO add support for Generator's 'axis' argument
def choice(self, a, size=None, replace=True, p=None):
"""Call :func:`numpy.random.Generator.choice`."""
# pylint: disable=invalid-name
return self.generator.choice(a=a, size=size, replace=replace, p=p)
def bytes(self, length):
"""Call :func:`numpy.random.Generator.bytes`."""
return self.generator.bytes(length=length)
# TODO mark in-place
def shuffle(self, x):
"""Call :func:`numpy.random.Generator.shuffle`."""
# note that shuffle() does not allow keyword arguments
# note that shuffle() works in-place
self.generator.shuffle(x)
def permutation(self, x):
"""Call :func:`numpy.random.Generator.permutation`."""
# note that permutation() does not allow keyword arguments
return self.generator.permutation(x)
def beta(self, a, b, size=None):
"""Call :func:`numpy.random.Generator.beta`."""
# pylint: disable=invalid-name
return self.generator.beta(a=a, b=b, size=size)
def binomial(self, n, p, size=None):
"""Call :func:`numpy.random.Generator.binomial`."""
return self.generator.binomial(n=n, p=p, size=size)
def chisquare(self, df, size=None):
"""Call :func:`numpy.random.Generator.chisquare`."""
# pylint: disable=invalid-name
return self.generator.chisquare(df=df, size=size)
def dirichlet(self, alpha, size=None):
"""Call :func:`numpy.random.Generator.dirichlet`."""
return self.generator.dirichlet(alpha=alpha, size=size)
def exponential(self, scale=1.0, size=None):
"""Call :func:`numpy.random.Generator.exponential`."""
return self.generator.exponential(scale=scale, size=size)
def f(self, dfnum, dfden, size=None):
"""Call :func:`numpy.random.Generator.f`."""
return self.generator.f(dfnum=dfnum, dfden=dfden, size=size)
def gamma(self, shape, scale=1.0, size=None):
"""Call :func:`numpy.random.Generator.gamma`."""
return self.generator.gamma(shape=shape, scale=scale, size=size)
def geometric(self, p, size=None):
"""Call :func:`numpy.random.Generator.geometric`."""
return self.generator.geometric(p=p, size=size)
def gumbel(self, loc=0.0, scale=1.0, size=None):
"""Call :func:`numpy.random.Generator.gumbel`."""
return self.generator.gumbel(loc=loc, scale=scale, size=size)
def hypergeometric(self, ngood, nbad, nsample, size=None):
"""Call :func:`numpy.random.Generator.hypergeometric`."""
return self.generator.hypergeometric(
ngood=ngood, nbad=nbad, nsample=nsample, size=size)
def laplace(self, loc=0.0, scale=1.0, size=None):
"""Call :func:`numpy.random.Generator.laplace`."""
return self.generator.laplace(loc=loc, scale=scale, size=size)
def logistic(self, loc=0.0, scale=1.0, size=None):
"""Call :func:`numpy.random.Generator.logistic`."""
return self.generator.logistic(loc=loc, scale=scale, size=size)
def lognormal(self, mean=0.0, sigma=1.0, size=None):
"""Call :func:`numpy.random.Generator.lognormal`."""
return self.generator.lognormal(mean=mean, sigma=sigma, size=size)
def logseries(self, p, size=None):
"""Call :func:`numpy.random.Generator.logseries`."""
return self.generator.logseries(p=p, size=size)
def multinomial(self, n, pvals, size=None):
"""Call :func:`numpy.random.Generator.multinomial`."""
return self.generator.multinomial(n=n, pvals=pvals, size=size)
def multivariate_normal(self, mean, cov, size=None, check_valid="warn",
tol=1e-8):
"""Call :func:`numpy.random.Generator.multivariate_normal`."""
return self.generator.multivariate_normal(
mean=mean, cov=cov, size=size, check_valid=check_valid, tol=tol)
def negative_binomial(self, n, p, size=None):
"""Call :func:`numpy.random.Generator.negative_binomial`."""
return self.generator.negative_binomial(n=n, p=p, size=size)
def noncentral_chisquare(self, df, nonc, size=None):
"""Call :func:`numpy.random.Generator.noncentral_chisquare`."""
# pylint: disable=invalid-name
return self.generator.noncentral_chisquare(df=df, nonc=nonc, size=size)
def noncentral_f(self, dfnum, dfden, nonc, size=None):
"""Call :func:`numpy.random.Generator.noncentral_f`."""
return self.generator.noncentral_f(
dfnum=dfnum, dfden=dfden, nonc=nonc, size=size)
def normal(self, loc=0.0, scale=1.0, size=None):
"""Call :func:`numpy.random.Generator.normal`."""
return self.generator.normal(loc=loc, scale=scale, size=size)
def pareto(self, a, size=None):
"""Call :func:`numpy.random.Generator.pareto`."""
# pylint: disable=invalid-name
return self.generator.pareto(a=a, size=size)
def poisson(self, lam=1.0, size=None):
"""Call :func:`numpy.random.Generator.poisson`."""
return self.generator.poisson(lam=lam, size=size)
def power(self, a, size=None):
"""Call :func:`numpy.random.Generator.power`."""
# pylint: disable=invalid-name
return self.generator.power(a=a, size=size)
def rayleigh(self, scale=1.0, size=None):
"""Call :func:`numpy.random.Generator.rayleigh`."""
return self.generator.rayleigh(scale=scale, size=size)
def standard_cauchy(self, size=None):
"""Call :func:`numpy.random.Generator.standard_cauchy`."""
return self.generator.standard_cauchy(size=size)
def standard_exponential(self, size=None, dtype="float32", method="zig",
out=None):
"""Call :func:`numpy.random.Generator.standard_exponential`.
.. note::
Changed `dtype` argument default value from numpy's ``d`` to
``float32``.
"""
if self._is_new_rng_style:
return self.generator.standard_exponential(
size=size, dtype=dtype, method=method, out=out)
result = self.generator.standard_exponential(size=size).astype(dtype)
if out is not None:
assert out.dtype.name == result.dtype.name, (
"Expected out array to have the same dtype as "
"standard_exponential()'s result array. Got %s (out) and "
"%s (result) instead." % (out.dtype.name, result.dtype.name))
out[...] = result
return result
def standard_gamma(self, shape, size=None, dtype="float32", out=None):
"""Call :func:`numpy.random.Generator.standard_gamma`.
.. note::
Changed `dtype` argument default value from numpy's ``d`` to
``float32``.
"""
if self._is_new_rng_style:
return self.generator.standard_gamma(
shape=shape, size=size, dtype=dtype, out=out)
result = self.generator.standard_gamma(
shape=shape, size=size).astype(dtype)
if out is not None:
assert out.dtype.name == result.dtype.name, (
"Expected out array to have the same dtype as "
"standard_gamma()'s result array. Got %s (out) and "
"%s (result) instead." % (out.dtype.name, result.dtype.name))
out[...] = result
return result
def standard_normal(self, size=None, dtype="float32", out=None):
"""Call :func:`numpy.random.Generator.standard_normal`.
.. note::
Changed `dtype` argument default value from numpy's ``d`` to
``float32``.
"""
if self._is_new_rng_style:
return self.generator.standard_normal(
size=size, dtype=dtype, out=out)
result = self.generator.standard_normal(size=size).astype(dtype)
if out is not None:
assert out.dtype.name == result.dtype.name, (
"Expected out array to have the same dtype as "
"standard_normal()'s result array. Got %s (out) and "
"%s (result) instead." % (out.dtype.name, result.dtype.name))
out[...] = result
return result
def standard_t(self, df, size=None):
"""Call :func:`numpy.random.Generator.standard_t`."""
# pylint: disable=invalid-name
return self.generator.standard_t(df=df, size=size)
def triangular(self, left, mode, right, size=None):
"""Call :func:`numpy.random.Generator.triangular`."""
return self.generator.triangular(
left=left, mode=mode, right=right, size=size)
def uniform(self, low=0.0, high=1.0, size=None):
"""Call :func:`numpy.random.Generator.uniform`."""
return self.generator.uniform(low=low, high=high, size=size)
def vonmises(self, mu, kappa, size=None):
"""Call :func:`numpy.random.Generator.vonmises`."""
# pylint: disable=invalid-name
return self.generator.vonmises(mu=mu, kappa=kappa, size=size)
def wald(self, mean, scale, size=None):
"""Call :func:`numpy.random.Generator.wald`."""
return self.generator.wald(mean=mean, scale=scale, size=size)
def weibull(self, a, size=None):
"""Call :func:`numpy.random.Generator.weibull`."""
# pylint: disable=invalid-name
return self.generator.weibull(a=a, size=size)
def zipf(self, a, size=None):
"""Call :func:`numpy.random.Generator.zipf`."""
# pylint: disable=invalid-name
return self.generator.zipf(a=a, size=size)
##################################################################
# Outdated methods from RandomState
# These are added here for backwards compatibility in case of old
# custom augmenters and Lambda calls that rely on the RandomState
# API.
##################################################################
def rand(self, *args):
"""Call :func:`numpy.random.RandomState.rand`.
.. warning::
This method is outdated in numpy. Use :func:`RNG.random` instead.
Added in 0.4.0.
"""
return self.random(size=args)
def randint(self, low, high=None, size=None, dtype="int32"):
"""Call :func:`numpy.random.RandomState.randint`.
.. note::
Changed `dtype` argument default value from numpy's ``I`` to
``int32``.
.. warning::
This method is outdated in numpy. Use :func:`RNG.integers`
instead.
Added in 0.4.0.
"""
return self.integers(low=low, high=high, size=size, dtype=dtype,
endpoint=False)
def randn(self, *args):
"""Call :func:`numpy.random.RandomState.randn`.
.. warning::
This method is outdated in numpy. Use :func:`RNG.standard_normal`
instead.
Added in 0.4.0.
"""
return self.standard_normal(size=args)
def random_integers(self, low, high=None, size=None):
"""Call :func:`numpy.random.RandomState.random_integers`.
.. warning::
This method is outdated in numpy. Use :func:`RNG.integers`
instead.
Added in 0.4.0.
"""
if high is None:
return self.integers(low=1, high=low, size=size, endpoint=True)
return self.integers(low=low, high=high, size=size, endpoint=True)
def random_sample(self, size):
"""Call :func:`numpy.random.RandomState.random_sample`.
.. warning::
This method is outdated in numpy. Use :func:`RNG.uniform`
instead.
Added in 0.4.0.
"""
return self.uniform(0.0, 1.0, size=size)
def tomaxint(self, size=None):
"""Call :func:`numpy.random.RandomState.tomaxint`.
.. warning::
This method is outdated in numpy. Use :func:`RNG.integers`
instead.
Added in 0.4.0.
"""
import sys
maxint = sys.maxsize
int32max = np.iinfo(np.int32).max
return self.integers(0, min(maxint, int32max), size=size,
endpoint=True)
def supports_new_numpy_rng_style():
"""
Determine whether numpy supports the new ``random`` interface (v1.17+).
Returns
-------
bool
``True`` if the new ``random`` interface is supported by numpy, i.e.
if numpy has version 1.17 or later. Otherwise ``False``, i.e.
numpy has version 1.16 or older and ``numpy.random.RandomState``
should be used instead.
"""
return SUPPORTS_NEW_NP_RNG_STYLE
def get_global_rng():
"""
Get or create the current global RNG of imgaug.
Note that the first call to this function will create a global RNG.
Returns
-------
RNG
The global RNG to use.
"""
# TODO change global_rng to singleton
# pylint: disable=global-statement, redefined-outer-name
global GLOBAL_RNG
if GLOBAL_RNG is None:
# This uses numpy's random state to sample a seed.
# Alternatively, `secrets.randbits(n_bits)` (3.6+) and
# `os.urandom(n_bytes)` could be used.
# See https://stackoverflow.com/a/27286733/3760780
# for an explanation how random.seed() picks a random seed value.
seed = generate_seed_(np.random)
GLOBAL_RNG = RNG(convert_seed_to_generator(seed))
return GLOBAL_RNG
# This is an in-place operation, but does not use a trailing slash to indicate
# that in order to match the interface of `random` and `numpy.random`.
def seed(entropy):
"""Set the seed of imgaug's global RNG (in-place).
The global RNG controls most of the "randomness" in imgaug.
The global RNG is the default one used by all augmenters. Under special
circumstances (e.g. when an augmenter is switched to deterministic mode),
the global RNG is replaced with a local one. The state of that replacement
may be dependent on the global RNG's state at the time of creating the
child RNG.
Parameters
----------
entropy : int
The seed value to use.
"""
if SUPPORTS_NEW_NP_RNG_STYLE:
_seed_np117_(entropy)
else:
_seed_np116_(entropy)
def _seed_np117_(entropy):
# We can't easily seed a BitGenerator in-place, nor can we easily modify
# a Generator's bit_generator in-place. So instead we create a new
# bit generator and set the current global RNG's internal bit generator
# state to a copy of the new bit generator's state.
get_global_rng().state = BIT_GENERATOR(entropy).state
def _seed_np116_(entropy):
get_global_rng().generator.seed(entropy)
def normalize_generator(generator):
"""Normalize various inputs to a numpy (random number) generator.
This function will first copy the provided argument, i.e. it never returns
a provided instance itself.
Parameters
----------
generator : None or int or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
The numpy random number generator to normalize. In case of numpy
version 1.17 or later, this shouldn't be a ``RandomState`` as that
class is outdated.
Behaviour for different datatypes:
* If ``None``: The global RNG's generator is returned.
* If ``int``: In numpy 1.17+, the value is used as a seed for a
``Generator``, i.e. it will be provided as the entropy to a
``SeedSequence``, which will then be used for an ``SFC64`` bit
generator and wrapped by a ``Generator``, which is then returned.
In numpy <=1.16, the value is used as a seed for a ``RandomState``,
which will then be returned.
* If :class:`numpy.random.Generator`: That generator will be
returned.
* If :class:`numpy.random.BitGenerator`: A numpy
generator will be created and returned that contains the bit
generator.
* If :class:`numpy.random.SeedSequence`: A numpy
generator will be created and returned that contains an ``SFC64``
bit generator initialized with the given ``SeedSequence``.
* If :class:`numpy.random.RandomState`: In numpy <=1.16, this
``RandomState`` will be returned. In numpy 1.17+, a seed will be
derived from this ``RandomState`` and a new
``numpy.generator.Generator`` based on an ``SFC64`` bit generator
will be created and returned.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator`` (even if
the input was a ``RandomState``).
"""
return normalize_generator_(copylib.deepcopy(generator))
def normalize_generator_(generator):
"""Normalize in-place various inputs to a numpy (random number) generator.
This function will try to return the provided instance itself.
Parameters
----------
generator : None or int or numpy.random.Generator or numpy.random.BitGenerator or numpy.random.SeedSequence or numpy.random.RandomState
See :func:`~imgaug.random.normalize_generator`.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator`` (even if
the input was a ``RandomState``).
"""
if not SUPPORTS_NEW_NP_RNG_STYLE:
return _normalize_generator_np116_(generator)
return _normalize_generator_np117_(generator)
def _normalize_generator_np117_(generator):
if generator is None:
return get_global_rng().generator
if isinstance(generator, np.random.SeedSequence):
return np.random.Generator(
BIT_GENERATOR(generator)
)
if isinstance(generator, _BIT_GENERATOR_INTERFACE):
generator = np.random.Generator(generator)
# TODO is it necessary/sensible here to reset the cache?
reset_generator_cache_(generator)
return generator
if isinstance(generator, np.random.Generator):
# TODO is it necessary/sensible here to reset the cache?
reset_generator_cache_(generator)
return generator
if isinstance(generator, np.random.RandomState):
# TODO warn
# TODO reset the cache here too?
return convert_seed_to_generator(generate_seed_(generator))
# seed given
seed_ = generator
return convert_seed_to_generator(seed_)
def _normalize_generator_np116_(random_state):
if random_state is None:
return get_global_rng().generator
if isinstance(random_state, np.random.RandomState):
# TODO reset the cache here, like in np117?
return random_state
# seed given
seed_ = random_state
return convert_seed_to_generator(seed_)
def convert_seed_to_generator(entropy):
"""Convert a seed value to a numpy (random number) generator.
Parameters
----------
entropy : int
The seed value to use.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
Both are initialized with the provided seed.
"""
if not SUPPORTS_NEW_NP_RNG_STYLE:
return _convert_seed_to_generator_np116(entropy)
return _convert_seed_to_generator_np117(entropy)
def _convert_seed_to_generator_np117(entropy):
seed_sequence = np.random.SeedSequence(entropy)
return convert_seed_sequence_to_generator(seed_sequence)
def _convert_seed_to_generator_np116(entropy):
return np.random.RandomState(entropy)
def convert_seed_sequence_to_generator(seed_sequence):
"""Convert a seed sequence to a numpy (random number) generator.
Parameters
----------
seed_sequence : numpy.random.SeedSequence
The seed value to use.
Returns
-------
numpy.random.Generator
Generator initialized with the provided seed sequence.
"""
return np.random.Generator(BIT_GENERATOR(seed_sequence))
def create_pseudo_random_generator_():
"""Create a new numpy (random) generator, derived from the global RNG.
This function advances the global RNG's state.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
Both are initialized with a seed sampled from the global RNG.
"""
# could also use derive_rng(get_global_rng()) here
random_seed = generate_seed_(get_global_rng().generator)
return convert_seed_to_generator(random_seed)
def create_fully_random_generator():
"""Create a new numpy (random) generator, derived from OS's entropy.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
Both are initialized with entropy requested from the OS. They are
hence independent of entered seeds or the library's global RNG.
"""
if not SUPPORTS_NEW_NP_RNG_STYLE:
return _create_fully_random_generator_np116()
return _create_fully_random_generator_np117()
def _create_fully_random_generator_np117():
# TODO need entropy here?
return np.random.Generator(np.random.SFC64())
def _create_fully_random_generator_np116():
return np.random.RandomState()
def generate_seed_(generator):
"""Sample a seed from the provided generator.
This function advances the generator's state.
See ``SEED_MIN_VALUE`` and ``SEED_MAX_VALUE`` for the seed's value
range.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator from which to sample the seed.
Returns
-------
int
The sampled seed.
"""
return generate_seeds_(generator, 1)[0]
def generate_seeds_(generator, n):
"""Sample `n` seeds from the provided generator.
This function advances the generator's state.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator from which to sample the seed.
n : int
Number of seeds to sample.
Returns
-------
ndarray
1D-array of ``int32`` seeds.
"""
return polyfill_integers(generator, SEED_MIN_VALUE, SEED_MAX_VALUE,
size=(n,))
def copy_generator(generator):
"""Copy an existing numpy (random number) generator.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator to copy.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
Both are copies of the input argument.
"""
if isinstance(generator, np.random.RandomState):
return _copy_generator_np116(generator)
return _copy_generator_np117(generator)
def _copy_generator_np117(generator):
# TODO not sure if it is enough to only copy the state
# TODO initializing a bit gen and then copying the state might be slower
# then just deepcopying the whole thing
old_bit_gen = generator.bit_generator
new_bit_gen = old_bit_gen.__class__(1)
new_bit_gen.state = copylib.deepcopy(old_bit_gen.state)
return np.random.Generator(new_bit_gen)
def _copy_generator_np116(random_state):
rs_copy = np.random.RandomState(1)
state = random_state.get_state()
rs_copy.set_state(state)
return rs_copy
def copy_generator_unless_global_generator(generator):
"""Copy a numpy generator unless it is the current global generator.
"global generator" here denotes the generator contained in the
global RNG's ``.generator`` attribute.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator to copy.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
Both are copies of the input argument, unless that input is
identical to the global generator. If it is identical, the
instance itself will be returned without copying it.
"""
if generator is get_global_rng().generator:
return generator
return copy_generator(generator)
def reset_generator_cache_(generator):
"""Reset a numpy (random number) generator's internal cache.
This function modifies the generator's state in-place.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator of which to reset the cache.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
In both cases the input argument itself.
"""
if isinstance(generator, np.random.RandomState):
return _reset_generator_cache_np116_(generator)
return _reset_generator_cache_np117_(generator)
def _reset_generator_cache_np117_(generator):
# This deactivates usage of the cache. We could also remove the cached
# value itself in "uinteger", but setting the RNG to ignore the cached
# value should be enough.
state = _get_generator_state_np117(generator)
state["has_uint32"] = 0
_set_generator_state_np117_(generator, state)
return generator
def _reset_generator_cache_np116_(random_state):
# State tuple content:
# 'MT19937', array of ints, unknown int, cache flag, cached value
# The cache flag only affects the standard_normal() method.
state = list(random_state.get_state())
state[-2] = 0
random_state.set_state(tuple(state))
return random_state
def derive_generator_(generator):
"""Create a child numpy (random number) generator from an existing one.
This advances the generator's state.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator from which to derive a new child generator.
Returns
-------
numpy.random.Generator or numpy.random.RandomState
In numpy <=1.16 a ``RandomState``, in 1.17+ a ``Generator``.
In both cases a derived child generator.
"""
return derive_generators_(generator, n=1)[0]
# TODO does this advance the RNG in 1.17? It should advance it for security
# reasons
def derive_generators_(generator, n):
"""Create child numpy (random number) generators from an existing one.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator from which to derive new child generators.
n : int
Number of child generators to derive.
Returns
-------
list of numpy.random.Generator or list of numpy.random.RandomState
In numpy <=1.16 a list of ``RandomState`` s,
in 1.17+ a list of ``Generator`` s.
In both cases lists of derived child generators.
"""
if isinstance(generator, np.random.RandomState):
return _derive_generators_np116_(generator, n=n)
return _derive_generators_np117_(generator, n=n)
def _derive_generators_np117_(generator, n):
# TODO possible to get the SeedSequence from 'rng'?
"""
advance_rng_(rng)
rng = copylib.deepcopy(rng)
reset_rng_cache_(rng)
state = rng.bit_generator.state
rngs = []
for i in sm.xrange(n):
state["state"]["state"] += (i * 100003 + 17)
rng.bit_generator.state = state
rngs.append(rng)
rng = copylib.deepcopy(rng)
return rngs
"""
# We generate here two integers instead of one, because the internal state
# of the RNG might have one 32bit integer still cached up, which would
# then be returned first when calling integers(). This should usually be
# fine, but there is some risk involved that this will lead to sampling
# many times the same seed in loop constructions (if the internal state
# is not properly advanced and the cache is then also not reset). Adding
# 'size=(2,)' decreases that risk. (It is then enough to e.g. call once
# random() to advance the internal state. No resetting of caches is
# needed.)
seed_ = generator.integers(SEED_MIN_VALUE, SEED_MAX_VALUE, dtype="int32",
size=(2,))[-1]
seed_seq = np.random.SeedSequence(seed_)
seed_seqs = seed_seq.spawn(n)
return [convert_seed_sequence_to_generator(seed_seq)
for seed_seq in seed_seqs]
def _derive_generators_np116_(random_state, n):
seed_ = random_state.randint(SEED_MIN_VALUE, SEED_MAX_VALUE)
return [_convert_seed_to_generator_np116(seed_ + i) for i in sm.xrange(n)]
def get_generator_state(generator):
"""Get the state of this provided generator.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator, which's state is supposed to be extracted.
Returns
-------
tuple or dict
The state of the generator.
In numpy 1.17+, the bit generator's state will be returned.
In numpy <=1.16, the ``RandomState`` 's state is returned.
In both cases the state is a copy. In-place changes will not affect
the RNG.
"""
if isinstance(generator, np.random.RandomState):
return _get_generator_state_np116(generator)
return _get_generator_state_np117(generator)
def _get_generator_state_np117(generator):
return generator.bit_generator.state
def _get_generator_state_np116(random_state):
return random_state.get_state()
def set_generator_state_(generator, state):
"""Set the state of a numpy (random number) generator in-place.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator, which's state is supposed to be modified.
state : tuple or dict
The new state of the generator.
Should correspond to the output of
:func:`~imgaug.random.get_generator_state`.
"""
if isinstance(generator, np.random.RandomState):
_set_generator_state_np116_(generator, state)
else:
_set_generator_state_np117_(generator, state)
def _set_generator_state_np117_(generator, state):
generator.bit_generator.state = state
def _set_generator_state_np116_(random_state, state):
random_state.set_state(state)
def is_generator_equal_to(generator, other_generator):
"""Estimate whether two generator have the same class and state.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
First generator used in the comparison.
other_generator : numpy.random.Generator or numpy.random.RandomState
Second generator used in the comparison.
Returns
-------
bool
``True`` if `generator` 's class and state are the same as the
class and state of `other_generator`. ``False`` otherwise.
"""
if isinstance(generator, np.random.RandomState):
return _is_generator_equal_to_np116(generator, other_generator)
return _is_generator_equal_to_np117(generator, other_generator)
def _is_generator_equal_to_np117(generator, other_generator):
assert generator.__class__ is other_generator.__class__, (
"Expected both rngs to have the same class, "
"got types '%s' and '%s'." % (type(generator), type(other_generator)))
state1 = get_generator_state(generator)
state2 = get_generator_state(other_generator)
assert state1["bit_generator"] == "SFC64", (
"Can currently only compare the states of numpy.random.SFC64 bit "
"generators, got %s." % (state1["bit_generator"],))
assert state2["bit_generator"] == "SFC64", (
"Can currently only compare the states of numpy.random.SFC64 bit "
"generators, got %s." % (state2["bit_generator"],))
if state1["has_uint32"] != state2["has_uint32"]:
return False
if state1["has_uint32"] == state2["has_uint32"] == 1:
if state1["uinteger"] != state2["uinteger"]:
return False
return np.array_equal(state1["state"]["state"], state2["state"]["state"])
def _is_generator_equal_to_np116(random_state, other_random_state):
state1 = _get_generator_state_np116(random_state)
state2 = _get_generator_state_np116(other_random_state)
# Note that state1 and state2 are tuples with the value at index 1 being
# a numpy array and the values at 2-4 being ints/floats, so we can't just
# apply array_equal to state1[1:4+1] and state2[1:4+1]. We need a loop
# here.
for i in sm.xrange(1, 4+1):
if not np.array_equal(state1[i], state2[i]):
return False
return True
def advance_generator_(generator):
"""Advance a numpy random generator's internal state in-place by one step.
This advances the generator's state.
.. note::
This simply samples one or more random values. This means that
a call of this method will not completely change the outputs of
the next called sampling method. To achieve more drastic output
changes, call :func:`~imgaug.random.derive_generator_`.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
Generator of which to advance the internal state.
"""
if isinstance(generator, np.random.RandomState):
_advance_generator_np116_(generator)
else:
_advance_generator_np117_(generator)
def _advance_generator_np117_(generator):
_reset_generator_cache_np117_(generator)
generator.random()
def _advance_generator_np116_(generator):
_reset_generator_cache_np116_(generator)
generator.uniform()
def polyfill_integers(generator, low, high=None, size=None, dtype="int32",
endpoint=False):
"""Sample integers from a generator in different numpy versions.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator to sample from. If it is a ``RandomState``,
:func:`numpy.random.RandomState.randint` will be called,
otherwise :func:`numpy.random.Generator.integers`.
low : int or array-like of ints
See :func:`numpy.random.Generator.integers`.
high : int or array-like of ints, optional
See :func:`numpy.random.Generator.integers`.
size : int or tuple of ints, optional
See :func:`numpy.random.Generator.integers`.
dtype : {str, dtype}, optional
See :func:`numpy.random.Generator.integers`.
endpoint : bool, optional
See :func:`numpy.random.Generator.integers`.
Returns
-------
int or ndarray of ints
See :func:`numpy.random.Generator.integers`.
"""
if hasattr(generator, "randint"):
if endpoint:
if high is None:
high = low + 1
low = 0
else:
high = high + 1
return generator.randint(low=low, high=high, size=size, dtype=dtype)
return generator.integers(low=low, high=high, size=size, dtype=dtype,
endpoint=endpoint)
def polyfill_random(generator, size, dtype="float32", out=None):
"""Sample random floats from a generator in different numpy versions.
Parameters
----------
generator : numpy.random.Generator or numpy.random.RandomState
The generator to sample from. Both ``RandomState`` and ``Generator``
support ``random()``, but with different interfaces.
size : int or tuple of ints, optional
See :func:`numpy.random.Generator.random`.
dtype : {str, dtype}, optional
See :func:`numpy.random.Generator.random`.
out : ndarray, optional
See :func:`numpy.random.Generator.random`.
Returns
-------
float or ndarray of floats
See :func:`numpy.random.Generator.random`.
"""
if hasattr(generator, "random_sample"):
# note that numpy.random in <=1.16 supports random(), but
# numpy.random.RandomState does not
result = generator.random_sample(size=size).astype(dtype)
if out is not None:
assert out.dtype.name == result.dtype.name, (
"Expected out array to have the same dtype as "
"random_sample()'s result array. Got %s (out) and %s (result) "
"instead." % (out.dtype.name, result.dtype.name))
out[...] = result
return result
return generator.random(size=size, dtype=dtype, out=out)
# TODO add tests
class temporary_numpy_seed(object):
"""Context to temporarily alter the random state of ``numpy.random``.
The random state's internal state will be set back to the original one
once the context finishes.
Added in 0.4.0.
Parameters
----------
entropy : None or int
The seed value to use.
If `None` then the seed will not be altered and the internal state
of ``numpy.random`` will not be reset back upon context exit (i.e.
this context will do nothing).
"""
# pylint complains about class name
# pylint: disable=invalid-name
def __init__(self, entropy=None):
self.old_state = None
self.entropy = entropy
def __enter__(self):
if self.entropy is not None:
self.old_state = np.random.get_state()
np.random.seed(self.entropy)
def __exit__(self, exc_type, exc_val, exc_tb):
if self.entropy is not None:
np.random.set_state(self.old_state)
================================================
FILE: imgaug/testutils.py
================================================
"""
Some utility functions that are only used for unittests.
Placing them in test/ directory seems to be against convention, so they are part of the library.
"""
from __future__ import print_function, division, absolute_import
import random
import copy
import warnings
import tempfile
import shutil
import re
import sys
import importlib
import functools
import numpy as np
import six.moves as sm
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
try:
import cPickle as pickle
except ImportError:
import pickle
import imgaug as ia
import imgaug.random as iarandom
import imgaug.parameters as iap
from imgaug.augmentables.kps import KeypointsOnImage
class ArgCopyingMagicMock(mock.MagicMock):
"""A MagicMock that copies its call args/kwargs before storing the call.
This is useful for imgaug as many augmentation methods change data
in-place.
Taken from https://stackoverflow.com/a/23264042/3760780
"""
def _mock_call(self, *args, **kwargs):
args_copy = copy.deepcopy(args)
kwargs_copy = copy.deepcopy(kwargs)
return super(ArgCopyingMagicMock, self)._mock_call(
*args_copy, **kwargs_copy)
# Added in 0.4.0.
def assert_cbaois_equal(observed, expected, max_distance=1e-4):
# pylint: disable=unidiomatic-typecheck
if isinstance(observed, list) or isinstance(expected, list):
assert isinstance(observed, list)
assert isinstance(expected, list)
assert len(observed) == len(expected)
for observed_i, expected_i in zip(observed, expected):
assert_cbaois_equal(observed_i, expected_i,
max_distance=max_distance)
else:
assert type(observed) == type(expected)
assert len(observed.items) == len(expected.items)
assert observed.shape == expected.shape
for item_a, item_b in zip(observed.items, expected.items):
assert item_a.coords_almost_equals(item_b,
max_distance=max_distance)
if isinstance(expected, ia.PolygonsOnImage):
for item_obs, item_exp in zip(observed.items, expected.items):
if item_exp.is_valid:
assert item_obs.is_valid
def create_random_images(size):
return np.random.uniform(0, 255, size).astype(np.uint8)
def create_random_keypoints(size_images, nb_keypoints_per_img):
result = []
for _ in sm.xrange(size_images[0]):
kps = []
height, width = size_images[1], size_images[2]
for _ in sm.xrange(nb_keypoints_per_img):
x = np.random.randint(0, width-1)
y = np.random.randint(0, height-1)
kps.append(ia.Keypoint(x=x, y=y))
result.append(ia.KeypointsOnImage(kps, shape=size_images[1:]))
return result
def array_equal_lists(list1, list2):
assert isinstance(list1, list), (
"Expected list1 to be a list, got type %s." % (type(list1),))
assert isinstance(list2, list), (
"Expected list2 to be a list, got type %s." % (type(list2),))
if len(list1) != len(list2):
return False
for arr1, arr2 in zip(list1, list2):
if not np.array_equal(arr1, arr2):
return False
return True
def keypoints_equal(kpsois1, kpsois2, eps=0.001):
if isinstance(kpsois1, KeypointsOnImage):
assert isinstance(kpsois2, KeypointsOnImage)
kpsois1 = [kpsois1]
kpsois2 = [kpsois2]
if len(kpsois1) != len(kpsois2):
return False
for kpsoi1, kpsoi2 in zip(kpsois1, kpsois2):
kps1 = kpsoi1.keypoints
kps2 = kpsoi2.keypoints
if len(kps1) != len(kps2):
return False
for kp1, kp2 in zip(kps1, kps2):
x_equal = (float(kp2.x) - eps
<= float(kp1.x)
<= float(kp2.x) + eps)
y_equal = (float(kp2.y) - eps
<= float(kp1.y)
<= float(kp2.y) + eps)
if not x_equal or not y_equal:
return False
return True
def reseed(seed=0):
iarandom.seed(seed)
np.random.seed(seed)
random.seed(seed)
# Added in 0.4.0.
def runtest_pickleable_uint8_img(augmenter, shape=(15, 15, 3), iterations=3):
image = np.mod(np.arange(int(np.prod(shape))), 256).astype(np.uint8)
image = image.reshape(shape)
augmenter_pkl = pickle.loads(pickle.dumps(augmenter, protocol=-1))
for _ in np.arange(iterations):
image_aug = augmenter(image=image)
image_aug_pkl = augmenter_pkl(image=image)
assert np.array_equal(image_aug, image_aug_pkl)
def wrap_shift_deprecation(func, *args, **kwargs):
"""Helper for tests of CBA shift() functions.
Added in 0.4.0.
"""
# No deprecated arguments? Just call the functions directly.
deprecated_kwargs = ["top", "right", "bottom", "left"]
if not any([kwname in kwargs for kwname in deprecated_kwargs]):
return func()
# Deprecated arguments? Log warnings and assume that there was a
# deprecation warning with expected message.
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
result = func()
assert (
"These are deprecated. Use `x` and `y` instead."
in str(caught_warnings[-1].message)
)
return result
class TemporaryDirectory(object):
"""Create a context for a temporary directory.
The directory is automatically removed at the end of the context.
This context is available in ``tmpfile.TemporaryDirectory``, but only
from 3.2+.
Added in 0.4.0.
"""
def __init__(self, suffix="", prefix="tmp", dir=None):
# pylint: disable=redefined-builtin
self.name = tempfile.mkdtemp(suffix, prefix, dir)
def __enter__(self):
return self.name
def __exit__(self, exc_type, exc_val, exc_tb):
shutil.rmtree(self.name)
# Copied from
# https://github.com/python/cpython/blob/master/Lib/unittest/case.py
# at commit 293dd23 (Nov 19, 2019).
# Required at least to enable assertWarns() in python <3.2.
# Added in 0.4.0.
def _is_subtype(expected, basetype):
if isinstance(expected, tuple):
return all(_is_subtype(e, basetype) for e in expected)
return isinstance(expected, type) and issubclass(expected, basetype)
# Copied from
# https://github.com/python/cpython/blob/master/Lib/unittest/case.py
# at commit 293dd23 (Nov 19, 2019).
# Required at least to enable assertWarns() in python <3.2.
# Added in 0.4.0.
class _BaseTestCaseContext:
# Added in 0.4.0.
def __init__(self, test_case):
self.test_case = test_case
# Added in 0.4.0.
def _raiseFailure(self, standardMsg):
# pylint: disable=invalid-name, protected-access, no-member
msg = self.test_case._formatMessage(self.msg, standardMsg)
raise self.test_case.failureException(msg)
# Copied from
# https://github.com/python/cpython/blob/master/Lib/unittest/case.py
# at commit 293dd23 (Nov 19, 2019).
# Required at least to enable assertWarns() in python <3.2.
# Added in 0.4.0.
class _AssertRaisesBaseContext(_BaseTestCaseContext):
# Added in 0.4.0.
def __init__(self, expected, test_case, expected_regex=None):
_BaseTestCaseContext.__init__(self, test_case)
self.expected = expected
self.test_case = test_case
if expected_regex is not None:
expected_regex = re.compile(expected_regex)
self.expected_regex = expected_regex
self.obj_name = None
self.msg = None
# Added in 0.4.0.
# pylint: disable=inconsistent-return-statements
def handle(self, name, args, kwargs):
"""
If args is empty, assertRaises/Warns is being used as a
context manager, so check for a 'msg' kwarg and return self.
If args is not empty, call a callable passing positional and keyword
arguments.
"""
# pylint: disable=no-member, self-cls-assignment, not-context-manager
try:
if not _is_subtype(self.expected, self._base_type):
raise TypeError('%s() arg 1 must be %s' %
(name, self._base_type_str))
if not args:
self.msg = kwargs.pop('msg', None)
if kwargs:
raise TypeError('%r is an invalid keyword argument for '
'this function' % (next(iter(kwargs)),))
return self
callable_obj = args[0]
args = args[1:]
try:
self.obj_name = callable_obj.__name__
except AttributeError:
self.obj_name = str(callable_obj)
with self:
callable_obj(*args, **kwargs)
finally:
# bpo-23890: manually break a reference cycle
self = None
# pylint: enable=inconsistent-return-statements
# Copied from
# https://github.com/python/cpython/blob/master/Lib/unittest/case.py
# at commit 293dd23 (Nov 19, 2019).
# Required at least to enable assertWarns() in python <3.2.
# Added in 0.4.0.
class _AssertWarnsContext(_AssertRaisesBaseContext):
"""A context manager used to implement TestCase.assertWarns* methods."""
_base_type = Warning
_base_type_str = 'a warning type or tuple of warning types'
# Added in 0.4.0.
def __enter__(self):
# The __warningregistry__'s need to be in a pristine state for tests
# to work properly.
# pylint: disable=invalid-name, attribute-defined-outside-init
for v in sys.modules.values():
if getattr(v, '__warningregistry__', None):
v.__warningregistry__ = {}
self.warnings_manager = warnings.catch_warnings(record=True)
self.warnings = self.warnings_manager.__enter__()
warnings.simplefilter("always", self.expected)
return self
# Added in 0.4.0.
def __exit__(self, exc_type, exc_value, tb):
# pylint: disable=invalid-name, attribute-defined-outside-init
self.warnings_manager.__exit__(exc_type, exc_value, tb)
if exc_type is not None:
# let unexpected exceptions pass through
return
try:
exc_name = self.expected.__name__
except AttributeError:
exc_name = str(self.expected)
first_matching = None
for m in self.warnings:
w = m.message
if not isinstance(w, self.expected):
continue
if first_matching is None:
first_matching = w
if (self.expected_regex is not None and
not self.expected_regex.search(str(w))):
continue
# store warning for later retrieval
self.warning = w
self.filename = m.filename
self.lineno = m.lineno
return
# Now we simply try to choose a helpful failure message
if first_matching is not None:
self._raiseFailure('"{}" does not match "{}"'.format(
self.expected_regex.pattern, str(first_matching)))
if self.obj_name:
self._raiseFailure("{} not triggered by {}".format(exc_name,
self.obj_name))
else:
self._raiseFailure("{} not triggered".format(exc_name))
# Partially copied from
# https://github.com/python/cpython/blob/master/Lib/unittest/case.py
# at commit 293dd23 (Nov 19, 2019).
# Required at least to enable assertWarns() in python <3.2.
def assertWarns(testcase, expected_warning, *args, **kwargs):
"""Context with same functionality as ``assertWarns`` in ``unittest``.
Note that unittest's ``assertWarns`` is only available in python 3.2+.
Added in 0.4.0.
Example
-------
>>> def test_foo(self):
>>> with assertWarns(self, UserWarning):
>>> pass
"""
# pylint: disable=invalid-name
context = _AssertWarnsContext(expected_warning, testcase)
return context.handle("assertWarns", args, kwargs)
class temporary_constants(object):
"""Context to temporarily change the value of one or more constants.
Added in 0.5.0.
"""
# pylint: disable=invalid-name
UNCHANGED = object()
def __init__(self, paths, values):
if ia.is_string(paths):
paths = [paths]
values = [values]
self.paths = [".".join(path_i.split(".")[:-1]) for path_i in paths]
self.cnames = [path_i.split(".")[-1] for path_i in paths]
self.values = values
self.old_values = None
def __enter__(self):
old_values = []
for path, cname, value in zip(self.paths, self.cnames, self.values):
module = importlib.import_module(path)
old_values.append(getattr(module, cname))
if value is not temporary_constants.UNCHANGED:
setattr(module, cname, value)
self.old_values = old_values
def __exit__(self, exc_type, exc_val, exc_tb):
gen = zip(self.paths, self.cnames, self.old_values)
for path, cname, old_value in gen:
module = importlib.import_module(path)
setattr(module, cname, old_value)
def is_parameter_instance(param, param_type):
"""Perform an isinstance check on a parameter while ignoring prefetching.
This is identical to ``isinstance(param, param_type)``, unless `param`
is an instance of :class:`imgaug.parameters.AutoPrefetcher`, then it
is equivalent to ``isinstance(param.other_param, param_type)`` (potentially
recursively evaluated until `param` is no longer prefetched).
Added in 0.5.0.
Parameters
----------
param : imgaug.parameters.StochasticParameter
The parameter to check.
param_type : type
The desired type. Similar as in ``isinstance``.
E.g. ``imgaug.parameters.Deterministic``.
Returns
-------
bool
Whether the parameter is of the given type.
"""
return isinstance(remove_prefetching(param), param_type)
def remove_prefetching(param):
"""Convert a possibly-prefetched parameter into a not-prefetched one.
Added in 0.5.0.
Parameters
----------
param : imgaug.parameters.StochasticParameter
Parameter to remove prefetching from.
Returns
-------
imgaug.parameters.StochasticParameter
The input parameter without prefetching. (Not copied.)
If the input parameter was not prefetched, it will be returned without
change.
"""
if isinstance(param, iap.AutoPrefetcher):
return remove_prefetching(param.other_param)
return param
def ensure_deprecation_warning(expected_text):
"""Ensure that a decorated function raises a deprecation warning.
Added in 0.5.0.
Parameters
----------
expected_text : str
Expected text fragment to be found in warning's text.
Returns
-------
callable
Decorated function.
"""
def _wrapper_with_args(func):
@functools.wraps(func)
def _wrapper(*args, **kwargs):
with warnings.catch_warnings(record=True) as caught_warnings:
func(*args, **kwargs)
assert len(caught_warnings) == 1, (
"Expected 1 warning, got %d." % (len(caught_warnings),)
)
assert (
expected_text
in str(caught_warnings[-1].message)
)
return _wrapper
return _wrapper_with_args
================================================
FILE: imgaug/validation.py
================================================
"""Helper functions to validate input data and produce error messages."""
from __future__ import print_function, division, absolute_import
import imgaug as ia
def convert_iterable_to_string_of_types(iterable_var):
"""Convert an iterable of values to a string of their types.
Parameters
----------
iterable_var : iterable
An iterable of variables, e.g. a list of integers.
Returns
-------
str
String representation of the types in `iterable_var`. One per item
in `iterable_var`. Separated by commas.
"""
types = [str(type(var_i)) for var_i in iterable_var]
return ", ".join(types)
def is_iterable_of(iterable_var, classes):
"""Check whether `iterable_var` contains only instances of given classes.
Parameters
----------
iterable_var : iterable
An iterable of items that will be matched against `classes`.
classes : type or iterable of type
One or more classes that each item in `var` must be an instanceof.
If this is an iterable, a single match per item is enough.
Returns
-------
bool
Whether `var` only contains instances of `classes`.
If `var` was empty, ``True`` will be returned.
"""
if not ia.is_iterable(iterable_var):
return False
for var_i in iterable_var:
if not isinstance(var_i, classes):
return False
return True
def assert_is_iterable_of(iterable_var, classes):
"""Assert that `iterable_var` only contains instances of given classes.
Parameters
----------
iterable_var : iterable
See :func:`~imgaug.validation.is_iterable_of`.
classes : type or iterable of type
See :func:`~imgaug.validation.is_iterable_of`.
"""
valid = is_iterable_of(iterable_var, classes)
if not valid:
expected_types_str = (
", ".join([class_.__name__ for class_ in classes])
if not isinstance(classes, type)
else classes.__name__)
if not ia.is_iterable(iterable_var):
raise AssertionError(
"Expected an iterable of the following types: %s. "
"Got instead a single instance of: %s." % (
expected_types_str,
type(iterable_var).__name__)
)
raise AssertionError(
"Expected an iterable of the following types: %s. "
"Got an iterable of types: %s." % (
expected_types_str,
convert_iterable_to_string_of_types(iterable_var))
)
================================================
FILE: pytest.ini
================================================
[pytest]
addopts = -p pytester -p no:doctest --xdoctest --xdoctest-global-exec="import imgaug as ia\nfrom imgaug import augmenters as iaa"
norecursedirs = .git ignore build __pycache__ docs doc *.egg-info _*
================================================
FILE: readthedocs.yml
================================================
version: 2
submodules:
include: all
recursive: true
================================================
FILE: requirements.txt
================================================
six
# The test for imgaug.augmenters.blend.blend_alpha() fails for numpy<1.15.
# All other tests seemed to work as of 2019/01/04.
numpy>=1.15
scipy
Pillow
matplotlib
scikit-image>=0.14.2
opencv-python-headless
# imageio versions past 2.6.1 do not support <3.5 anymore
imageio<=2.6.1; python_version<'3.5'
imageio; python_version>='3.5'
Shapely
# numba is not available in <=3.5
numba; python_version>='3.6'
================================================
FILE: setup.cfg
================================================
[metadata]
description-file = README.md
================================================
FILE: setup.py
================================================
# pylint: disable=missing-module-docstring
import re
from pkg_resources import get_distribution, DistributionNotFound
from setuptools import setup, find_packages
long_description = """A library for image augmentation in machine learning experiments, particularly convolutional
neural networks. Supports the augmentation of images, keypoints/landmarks, bounding boxes, heatmaps and segmentation
maps in a variety of different ways."""
INSTALL_REQUIRES = [
"six",
"numpy>=1.15",
"scipy",
"Pillow",
"matplotlib",
"scikit-image>=0.14.2",
"opencv-python-headless",
"imageio<=2.6.1; python_version<'3.5'",
"imageio; python_version>='3.5'",
"Shapely"
]
ALT_INSTALL_REQUIRES = {
"opencv-python-headless": ["opencv-python", "opencv-contrib-python", "opencv-contrib-python-headless"],
}
def check_alternative_installation(install_require, alternative_install_requires):
"""If some version version of alternative requirement installed, return alternative,
else return main.
"""
for alternative_install_require in alternative_install_requires:
try:
alternative_pkg_name = re.split(r"[!<>=]", alternative_install_require)[0]
get_distribution(alternative_pkg_name)
return str(alternative_install_require)
except DistributionNotFound:
continue
return str(install_require)
def get_install_requirements(main_requires, alternative_requires):
"""Iterates over all install requires
If an install require has an alternative option, check if this option is installed
If that is the case, replace the install require by the alternative to not install dual package"""
install_requires = []
for main_require in main_requires:
if main_require in alternative_requires:
main_require = check_alternative_installation(main_require, alternative_requires.get(main_require))
install_requires.append(main_require)
return install_requires
INSTALL_REQUIRES = get_install_requirements(INSTALL_REQUIRES, ALT_INSTALL_REQUIRES)
setup(
name="imgaug",
version="0.4.0",
author="Alexander Jung",
author_email="kontakt@ajung.name",
url="https://github.com/aleju/imgaug",
download_url="https://github.com/aleju/imgaug/archive/0.4.0.tar.gz",
install_requires=INSTALL_REQUIRES,
packages=find_packages(),
include_package_data=True,
package_data={
"": ["LICENSE", "README.md", "requirements.txt"],
"imgaug": ["DejaVuSans.ttf", "quokka.jpg", "quokka_annotations.json", "quokka_depth_map_halfres.png"],
"imgaug.checks": ["README.md"]
},
license="MIT",
description="Image augmentation library for deep neural networks",
long_description=long_description,
keywords=["augmentation", "image", "deep learning", "neural network", "CNN", "machine learning",
"computer vision", "overfitting"],
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Science/Research",
"Intended Audience :: Developers",
"Intended Audience :: Information Technology",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Topic :: Scientific/Engineering :: Artificial Intelligence",
"Topic :: Scientific/Engineering :: Image Recognition",
"Topic :: Software Development :: Libraries :: Python Modules"
]
)
================================================
FILE: test/augmentables/test_batches.py
================================================
from __future__ import print_function, division, absolute_import
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
from imgaug.testutils import reseed
from imgaug.augmentables.batches import _BatchInAugmentation
ATTR_NAMES = ["images", "heatmaps", "segmentation_maps", "keypoints",
"bounding_boxes", "polygons", "line_strings"]
# TODO test __init__()
class TestUnnormalizedBatch(unittest.TestCase):
def setUp(self):
reseed()
def test_get_column_names__only_images(self):
batch = ia.UnnormalizedBatch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8)
)
names = batch.get_column_names()
assert names == ["images"]
def test_get_column_names__all_columns(self):
batch = ia.UnnormalizedBatch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8),
heatmaps=[np.zeros((2, 2, 1), dtype=np.float32)],
segmentation_maps=[np.zeros((2, 2, 1), dtype=np.int32)],
keypoints=[[(0, 0)]],
bounding_boxes=[[ia.BoundingBox(0, 0, 1, 1)]],
polygons=[[ia.Polygon([(0, 0), (1, 0), (1, 1)])]],
line_strings=[[ia.LineString([(0, 0), (1, 0)])]]
)
names = batch.get_column_names()
assert names == ["images", "heatmaps", "segmentation_maps",
"keypoints", "bounding_boxes", "polygons",
"line_strings"]
def test_to_normalized_batch__only_images(self):
batch = ia.UnnormalizedBatch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8)
)
batch_norm = batch.to_normalized_batch()
assert isinstance(batch_norm, ia.Batch)
assert ia.is_np_array(batch_norm.images_unaug)
assert batch_norm.images_unaug.shape == (1, 2, 2, 3)
assert batch_norm.get_column_names() == ["images"]
def test_to_normalized_batch__all_columns(self):
batch = ia.UnnormalizedBatch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8),
heatmaps=[np.zeros((2, 2, 1), dtype=np.float32)],
segmentation_maps=[np.zeros((2, 2, 1), dtype=np.int32)],
keypoints=[[(0, 0)]],
bounding_boxes=[[ia.BoundingBox(0, 0, 1, 1)]],
polygons=[[ia.Polygon([(0, 0), (1, 0), (1, 1)])]],
line_strings=[[ia.LineString([(0, 0), (1, 0)])]]
)
batch_norm = batch.to_normalized_batch()
assert isinstance(batch_norm, ia.Batch)
assert ia.is_np_array(batch_norm.images_unaug)
assert batch_norm.images_unaug.shape == (1, 2, 2, 3)
assert isinstance(batch_norm.heatmaps_unaug[0], ia.HeatmapsOnImage)
assert isinstance(batch_norm.segmentation_maps_unaug[0],
ia.SegmentationMapsOnImage)
assert isinstance(batch_norm.keypoints_unaug[0], ia.KeypointsOnImage)
assert isinstance(batch_norm.bounding_boxes_unaug[0],
ia.BoundingBoxesOnImage)
assert isinstance(batch_norm.polygons_unaug[0], ia.PolygonsOnImage)
assert isinstance(batch_norm.line_strings_unaug[0],
ia.LineStringsOnImage)
assert batch_norm.get_column_names() == [
"images", "heatmaps", "segmentation_maps", "keypoints",
"bounding_boxes", "polygons", "line_strings"]
def test_fill_from_augmented_normalized_batch(self):
batch = ia.UnnormalizedBatch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8),
heatmaps=[np.zeros((2, 2, 1), dtype=np.float32)],
segmentation_maps=[np.zeros((2, 2, 1), dtype=np.int32)],
keypoints=[[(0, 0)]],
bounding_boxes=[[ia.BoundingBox(0, 0, 1, 1)]],
polygons=[[ia.Polygon([(0, 0), (1, 0), (1, 1)])]],
line_strings=[[ia.LineString([(0, 0), (1, 0)])]]
)
batch_norm = ia.Batch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8),
heatmaps=[
ia.HeatmapsOnImage(
np.zeros((2, 2, 1), dtype=np.float32),
shape=(2, 2, 3)
)
],
segmentation_maps=[
ia.SegmentationMapsOnImage(
np.zeros((2, 2, 1), dtype=np.int32),
shape=(2, 2, 3)
)
],
keypoints=[
ia.KeypointsOnImage(
[ia.Keypoint(0, 0)],
shape=(2, 2, 3)
)
],
bounding_boxes=[
ia.BoundingBoxesOnImage(
[ia.BoundingBox(0, 0, 1, 1)],
shape=(2, 2, 3)
)
],
polygons=[
ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(2, 2, 3)
)
],
line_strings=[
ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 0)])],
shape=(2, 2, 3)
)
]
)
batch_norm.images_aug = batch_norm.images_unaug
batch_norm.heatmaps_aug = batch_norm.heatmaps_unaug
batch_norm.segmentation_maps_aug = batch_norm.segmentation_maps_unaug
batch_norm.keypoints_aug = batch_norm.keypoints_unaug
batch_norm.bounding_boxes_aug = batch_norm.bounding_boxes_unaug
batch_norm.polygons_aug = batch_norm.polygons_unaug
batch_norm.line_strings_aug = batch_norm.line_strings_unaug
batch = batch.fill_from_augmented_normalized_batch(batch_norm)
assert batch.images_aug.shape == (1, 2, 2, 3)
assert ia.is_np_array(batch.heatmaps_aug[0])
assert ia.is_np_array(batch.segmentation_maps_aug[0])
assert batch.keypoints_aug[0][0] == (0, 0)
assert batch.bounding_boxes_aug[0][0].x1 == 0
assert batch.polygons_aug[0][0].exterior[0][0] == 0
assert batch.line_strings_aug[0][0].coords[0][0] == 0
class TestBatch(unittest.TestCase):
def setUp(self):
reseed()
def test___init___no_arguments(self):
batch = ia.Batch()
for attr_name in ATTR_NAMES:
assert getattr(batch, "%s_unaug" % (attr_name,)) is None
assert getattr(batch, "%s_aug" % (attr_name,)) is None
assert batch.data is None
def test___init___all_arguments_provided(self):
# we exploit here that Batch() init does not verify its inputs
batch = ia.Batch(
images=0,
heatmaps=1,
segmentation_maps=2,
keypoints=3,
bounding_boxes=4,
polygons=5,
line_strings=6,
data=7
)
for i, attr_name in enumerate(ATTR_NAMES):
assert getattr(batch, "%s_unaug" % (attr_name,)) == i
assert getattr(batch, "%s_aug" % (attr_name,)) is None
assert batch.data == 7
def test_warnings_for_deprecated_properties(self):
batch = ia.Batch()
# self.assertWarns does not exist in py2.7
deprecated_attr_names = ["images", "heatmaps", "segmentation_maps",
"keypoints", "bounding_boxes"]
for attr_name in deprecated_attr_names:
with self.subTest(attr_name=attr_name),\
warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = getattr(batch, attr_name)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
def test_get_column_names__only_images(self):
batch = ia.Batch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8)
)
names = batch.get_column_names()
assert names == ["images"]
def test_get_column_names__all_columns(self):
batch = ia.Batch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8),
heatmaps=[np.zeros((2, 2, 1), dtype=np.float32)],
segmentation_maps=[np.zeros((2, 2, 1), dtype=np.int32)],
keypoints=[
ia.KeypointsOnImage(
[ia.Keypoint(x=0, y=0)],
shape=(2, 2, 3)
)
],
bounding_boxes=[
ia.BoundingBoxesOnImage(
[ia.BoundingBox(0, 0, 1, 1)],
shape=(2, 2, 3)
)
],
polygons=[
ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(2, 2, 3)
)
],
line_strings=[
ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 0)])],
shape=(2, 2, 3)
)
]
)
names = batch.get_column_names()
assert names == ["images", "heatmaps", "segmentation_maps",
"keypoints", "bounding_boxes", "polygons",
"line_strings"]
def test_to_normalized_batch(self):
batch = ia.Batch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8)
)
batch_norm = batch.to_normalized_batch()
assert batch_norm is batch
def test_to_batch_in_augmentation__only_images(self):
batch = ia.Batch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8)
)
batch_inaug = batch.to_batch_in_augmentation()
assert isinstance(batch_inaug, _BatchInAugmentation)
assert ia.is_np_array(batch_inaug.images)
assert batch_inaug.images.shape == (1, 2, 2, 3)
assert batch_inaug.get_column_names() == ["images"]
def test_to_batch_in_augmentation__all_columns(self):
batch = ia.Batch(
images=np.zeros((1, 2, 2, 3), dtype=np.uint8),
heatmaps=[
ia.HeatmapsOnImage(
np.zeros((2, 2, 1), dtype=np.float32),
shape=(2, 2, 3)
)
],
segmentation_maps=[
ia.SegmentationMapsOnImage(
np.zeros((2, 2, 1), dtype=np.int32),
shape=(2, 2, 3)
)
],
keypoints=[
ia.KeypointsOnImage(
[ia.Keypoint(x=0, y=0)],
shape=(2, 2, 3)
)
],
bounding_boxes=[
ia.BoundingBoxesOnImage(
[ia.BoundingBox(0, 0, 1, 1)],
shape=(2, 2, 3)
)
],
polygons=[
ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(2, 2, 3)
)
],
line_strings=[
ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 0)])],
shape=(2, 2, 3)
)
]
)
batch_inaug = batch.to_batch_in_augmentation()
assert isinstance(batch_inaug, _BatchInAugmentation)
assert ia.is_np_array(batch_inaug.images)
assert batch_inaug.images.shape == (1, 2, 2, 3)
assert isinstance(batch_inaug.heatmaps[0], ia.HeatmapsOnImage)
assert isinstance(batch_inaug.segmentation_maps[0],
ia.SegmentationMapsOnImage)
assert isinstance(batch_inaug.keypoints[0], ia.KeypointsOnImage)
assert isinstance(batch_inaug.bounding_boxes[0],
ia.BoundingBoxesOnImage)
assert isinstance(batch_inaug.polygons[0], ia.PolygonsOnImage)
assert isinstance(batch_inaug.line_strings[0], ia.LineStringsOnImage)
assert batch_inaug.get_column_names() == [
"images", "heatmaps", "segmentation_maps", "keypoints",
"bounding_boxes", "polygons", "line_strings"]
def test_fill_from_batch_in_augmentation(self):
batch = ia.Batch(images=1)
batch_inaug = _BatchInAugmentation(
images=2,
heatmaps=3,
segmentation_maps=4,
keypoints=5,
bounding_boxes=6,
polygons=7,
line_strings=8
)
batch = batch.fill_from_batch_in_augmentation_(batch_inaug)
assert batch.images_aug == 2
assert batch.heatmaps_aug == 3
assert batch.segmentation_maps_aug == 4
assert batch.keypoints_aug == 5
assert batch.bounding_boxes_aug == 6
assert batch.polygons_aug == 7
assert batch.line_strings_aug == 8
def test_deepcopy_no_arguments(self):
batch = ia.Batch()
observed = batch.deepcopy()
keys = list(observed.__dict__.keys())
assert len(keys) >= 14
for attr_name in keys:
assert getattr(observed, attr_name) is None
def test_deepcopy_only_images_provided(self):
images = np.zeros((1, 1, 3), dtype=np.uint8)
batch = ia.Batch(images=images)
observed = batch.deepcopy()
for attr_name in observed.__dict__.keys():
if attr_name != "images_unaug":
assert getattr(observed, attr_name) is None
assert ia.is_np_array(observed.images_unaug)
def test_deepcopy_every_argument_provided(self):
images = np.zeros((1, 1, 1, 3), dtype=np.uint8)
heatmaps = [ia.HeatmapsOnImage(np.zeros((1, 1, 1), dtype=np.float32),
shape=(4, 4, 3))]
segmentation_maps = [
ia.SegmentationMapsOnImage(np.zeros((1, 1), dtype=np.int32),
shape=(5, 5, 3))]
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)],
shape=(6, 6, 3))]
bounding_boxes = [
ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)
], shape=(7, 7, 3))]
polygons = [
ia.PolygonsOnImage([
ia.Polygon([(0, 0), (10, 0), (10, 10)])
], shape=(100, 100, 3))]
line_strings = [
ia.LineStringsOnImage([
ia.LineString([(1, 1), (11, 1), (11, 11)])
], shape=(101, 101, 3))]
data = {"test": 123, "foo": "bar", "test2": [1, 2, 3]}
batch = ia.Batch(
images=images,
heatmaps=heatmaps,
segmentation_maps=segmentation_maps,
keypoints=keypoints,
bounding_boxes=bounding_boxes,
polygons=polygons,
line_strings=line_strings,
data=data
)
observed = batch.deepcopy()
for attr_name in observed.__dict__.keys():
if "_unaug" not in attr_name and attr_name != "data":
assert getattr(observed, attr_name) is None
# must not be identical
assert observed.images_unaug is not images
assert observed.heatmaps_unaug is not heatmaps
assert observed.segmentation_maps_unaug is not segmentation_maps
assert observed.keypoints_unaug is not keypoints
assert observed.bounding_boxes_unaug is not bounding_boxes
assert observed.polygons_unaug is not polygons
assert observed.line_strings_unaug is not line_strings
assert observed.data is not data
# verify that lists were not shallow-copied
assert observed.heatmaps_unaug[0] is not heatmaps[0]
assert observed.segmentation_maps_unaug[0] is not segmentation_maps[0]
assert observed.keypoints_unaug[0] is not keypoints[0]
assert observed.bounding_boxes_unaug[0] is not bounding_boxes[0]
assert observed.polygons_unaug[0] is not polygons[0]
assert observed.line_strings_unaug[0] is not line_strings[0]
assert observed.data["test2"] is not data["test2"]
# but must be equal
assert ia.is_np_array(observed.images_unaug)
assert observed.images_unaug.shape == (1, 1, 1, 3)
assert isinstance(observed.heatmaps_unaug[0], ia.HeatmapsOnImage)
assert isinstance(observed.segmentation_maps_unaug[0],
ia.SegmentationMapsOnImage)
assert isinstance(observed.keypoints_unaug[0], ia.KeypointsOnImage)
assert isinstance(observed.bounding_boxes_unaug[0],
ia.BoundingBoxesOnImage)
assert isinstance(observed.polygons_unaug[0], ia.PolygonsOnImage)
assert isinstance(observed.line_strings_unaug[0], ia.LineStringsOnImage)
assert isinstance(observed.data, dict)
assert observed.heatmaps_unaug[0].shape == (4, 4, 3)
assert observed.segmentation_maps_unaug[0].shape == (5, 5, 3)
assert observed.keypoints_unaug[0].shape == (6, 6, 3)
assert observed.bounding_boxes_unaug[0].shape == (7, 7, 3)
assert observed.polygons_unaug[0].shape == (100, 100, 3)
assert observed.line_strings_unaug[0].shape == (101, 101, 3)
assert observed.heatmaps_unaug[0].arr_0to1.shape == (1, 1, 1)
assert observed.segmentation_maps_unaug[0].arr.shape == (1, 1, 1)
assert observed.keypoints_unaug[0].keypoints[0].x == 1
assert observed.keypoints_unaug[0].keypoints[0].y == 2
assert observed.bounding_boxes_unaug[0].bounding_boxes[0].x1 == 1
assert observed.bounding_boxes_unaug[0].bounding_boxes[0].y1 == 2
assert observed.bounding_boxes_unaug[0].bounding_boxes[0].x2 == 3
assert observed.bounding_boxes_unaug[0].bounding_boxes[0].y2 == 4
assert observed.polygons_unaug[0].polygons[0].exterior[0, 0] == 0
assert observed.polygons_unaug[0].polygons[0].exterior[0, 1] == 0
assert observed.polygons_unaug[0].polygons[0].exterior[1, 0] == 10
assert observed.polygons_unaug[0].polygons[0].exterior[1, 1] == 0
assert observed.polygons_unaug[0].polygons[0].exterior[2, 0] == 10
assert observed.polygons_unaug[0].polygons[0].exterior[2, 1] == 10
assert observed.line_strings_unaug[0].line_strings[0].coords[0, 0] == 1
assert observed.line_strings_unaug[0].line_strings[0].coords[0, 1] == 1
assert observed.line_strings_unaug[0].line_strings[0].coords[1, 0] == 11
assert observed.line_strings_unaug[0].line_strings[0].coords[1, 1] == 1
assert observed.line_strings_unaug[0].line_strings[0].coords[2, 0] == 11
assert observed.line_strings_unaug[0].line_strings[0].coords[2, 1] == 11
assert observed.data["test"] == 123
assert observed.data["foo"] == "bar"
assert observed.data["test2"] == [1, 2, 3]
# TODO test __init__
# test apply_propagation_hooks_
# test invert_apply_propagation_hooks_
class Test_BatchInAugmentation(unittest.TestCase):
def test_empty__all_columns_none(self):
batch = _BatchInAugmentation()
assert batch.empty
def test_empty__with_columns_set(self):
kwargs = [
{"images": [2]},
{"heatmaps": [3]},
{"segmentation_maps": [4]},
{"keypoints": [5]},
{"bounding_boxes": [6]},
{"polygons": [7]},
{"line_strings": [8]}
]
for kwargs_i in kwargs:
batch = _BatchInAugmentation(**kwargs_i)
assert not batch.empty
def test_nb_rows__when_empty(self):
batch = _BatchInAugmentation()
assert batch.nb_rows == 0
def test_nb_rows__with_empty_column(self):
batch = _BatchInAugmentation(images=[])
assert batch.nb_rows == 0
def test_nb_rows__with_columns_set(self):
kwargs = [
{"images": [0]},
{"heatmaps": [0]},
{"segmentation_maps": [0]},
{"keypoints": [0]},
{"bounding_boxes": [0]},
{"polygons": [0]},
{"line_strings": [0]}
]
for kwargs_i in kwargs:
batch = _BatchInAugmentation(**kwargs_i)
assert batch.nb_rows == 1
def test_nb_rows__with_two_columns(self):
batch = _BatchInAugmentation(images=[0, 0], keypoints=[0, 0])
assert batch.nb_rows == 2
def test_columns__when_empty(self):
batch = _BatchInAugmentation()
assert len(batch.columns) == 0
def test_columns__with_empty_column(self):
batch = _BatchInAugmentation(images=[])
columns = batch.columns
assert len(columns) == 0
def test_columns__with_columns_set(self):
kwargs = [
{"images": [0]},
{"heatmaps": [0]},
{"segmentation_maps": [0]},
{"keypoints": [0]},
{"bounding_boxes": [0]},
{"polygons": [0]},
{"line_strings": [0]}
]
for kwargs_i in kwargs:
batch = _BatchInAugmentation(**kwargs_i)
columns = batch.columns
assert len(columns) == 1
assert columns[0].name == list(kwargs_i.keys())[0]
def test_columns__with_two_columns(self):
batch = _BatchInAugmentation(images=[0, 0], keypoints=[1, 1])
columns = batch.columns
assert len(columns) == 2
assert columns[0].name == "images"
assert columns[1].name == "keypoints"
assert columns[0].value == [0, 0]
assert columns[1].value == [1, 1]
def test_get_column_names__with_two_columns(self):
batch = _BatchInAugmentation(images=[0, 0], keypoints=[1, 1])
assert batch.get_column_names() == ["images", "keypoints"]
def test_get_rowwise_shapes__images_is_single_array(self):
batch = _BatchInAugmentation(images=np.zeros((2, 3, 4, 1)))
shapes = batch.get_rowwise_shapes()
assert shapes == [(3, 4, 1), (3, 4, 1)]
def test_get_rowwise_shapes__images_is_multiple_arrays(self):
batch = _BatchInAugmentation(
images=[np.zeros((3, 4, 1)), np.zeros((4, 5, 1))]
)
shapes = batch.get_rowwise_shapes()
assert shapes == [(3, 4, 1), (4, 5, 1)]
def test_get_rowwise_shapes__nonimages(self):
heatmaps = [
ia.HeatmapsOnImage(
np.zeros((1, 2, 1), dtype=np.float32),
shape=(1, 2, 3))
]
segmaps = [
ia.SegmentationMapsOnImage(
np.zeros((1, 2, 1), dtype=np.int32),
shape=(1, 2, 3))
]
keypoints = [
ia.KeypointsOnImage(
[ia.Keypoint(0, 0)],
shape=(1, 2, 3))
]
bounding_boxes = [
ia.BoundingBoxesOnImage(
[ia.BoundingBox(0, 1, 2, 3)],
shape=(1, 2, 3)
)
]
polygons = [
ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(1, 2, 3)
)
]
line_strings = [
ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 0)])],
shape=(1, 2, 3)
)
]
kwargs = [
{"heatmaps": heatmaps},
{"segmentation_maps": segmaps},
{"keypoints": keypoints},
{"bounding_boxes": bounding_boxes},
{"polygons": polygons},
{"line_strings": line_strings}
]
for kwargs_i in kwargs:
batch = _BatchInAugmentation(**kwargs_i)
shapes = batch.get_rowwise_shapes()
assert shapes == [(1, 2, 3)]
def test_subselect_rows_by_indices__none_selected(self):
batch = _BatchInAugmentation(
images=np.zeros((3, 3, 4, 1)),
keypoints=[
ia.KeypointsOnImage(
[ia.Keypoint(0, 0)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(1, 1)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(2, 2)],
shape=(3, 4, 1)
)
]
)
batch_sub = batch.subselect_rows_by_indices([])
assert batch_sub.images is None
assert batch_sub.keypoints is None
def test_subselect_rows_by_indices__two_of_three_selected(self):
batch = _BatchInAugmentation(
images=np.zeros((3, 3, 4, 1)),
keypoints=[
ia.KeypointsOnImage(
[ia.Keypoint(0, 0)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(1, 1)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(2, 2)],
shape=(3, 4, 1)
)
]
)
batch_sub = batch.subselect_rows_by_indices([0, 2])
assert batch_sub.images.shape == (2, 3, 4, 1)
assert batch_sub.keypoints[0].keypoints[0].x == 0
assert batch_sub.keypoints[0].keypoints[0].y == 0
assert batch_sub.keypoints[1].keypoints[0].x == 2
assert batch_sub.keypoints[1].keypoints[0].y == 2
def test_invert_subselect_rows_by_indices__none_selected(self):
images = np.zeros((3, 3, 4, 1), dtype=np.uint8)
images[0, ...] = 0
images[1, ...] = 1
images[2, ...] = 2
batch = _BatchInAugmentation(
images=images,
keypoints=[
ia.KeypointsOnImage(
[ia.Keypoint(0, 0)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(1, 1)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(2, 2)],
shape=(3, 4, 1)
)
]
)
batch_sub = batch.subselect_rows_by_indices([])
batch_inv = batch.invert_subselect_rows_by_indices_([], batch_sub)
assert batch_inv.images.shape == (3, 3, 4, 1)
assert np.max(batch_inv.images[0]) == 0
assert np.max(batch_inv.images[1]) == 1
assert np.max(batch_inv.images[2]) == 2
assert batch_inv.keypoints[0].keypoints[0].x == 0
assert batch_inv.keypoints[0].keypoints[0].y == 0
assert batch_inv.keypoints[1].keypoints[0].x == 1
assert batch_inv.keypoints[1].keypoints[0].y == 1
assert batch_inv.keypoints[2].keypoints[0].x == 2
assert batch_inv.keypoints[2].keypoints[0].y == 2
def test_invert_subselect_rows_by_indices__two_of_three_selected(self):
images = np.zeros((3, 3, 4, 1), dtype=np.uint8)
images[0, ...] = 0
images[1, ...] = 1
images[2, ...] = 2
batch = _BatchInAugmentation(
images=images,
keypoints=[
ia.KeypointsOnImage(
[ia.Keypoint(0, 0)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(1, 1)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(2, 2)],
shape=(3, 4, 1)
)
]
)
batch_sub = batch.subselect_rows_by_indices([0, 2])
batch_sub.images[0, ...] = 10
batch_sub.images[1, ...] = 20
batch_sub.keypoints[0].keypoints[0].x = 10
batch_inv = batch.invert_subselect_rows_by_indices_([0, 2], batch_sub)
assert batch_inv.images.shape == (3, 3, 4, 1)
assert np.max(batch_inv.images[0]) == 10
assert np.max(batch_inv.images[1]) == 1
assert np.max(batch_inv.images[2]) == 20
assert batch_inv.keypoints[0].keypoints[0].x == 10
assert batch_inv.keypoints[0].keypoints[0].y == 0
assert batch_inv.keypoints[1].keypoints[0].x == 1
assert batch_inv.keypoints[1].keypoints[0].y == 1
assert batch_inv.keypoints[2].keypoints[0].x == 2
assert batch_inv.keypoints[2].keypoints[0].y == 2
def test_propagation_hooks_ctx(self):
def propagator(images, augmenter, parents, default):
if ia.is_np_array(images):
return False
else:
return True
hooks = ia.HooksImages(propagator=propagator)
batch = _BatchInAugmentation(
images=np.zeros((3, 3, 4, 1), dtype=np.uint8),
keypoints=[
ia.KeypointsOnImage(
[ia.Keypoint(0, 0)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(1, 1)],
shape=(3, 4, 1)
),
ia.KeypointsOnImage(
[ia.Keypoint(2, 2)],
shape=(3, 4, 1)
)
]
)
with batch.propagation_hooks_ctx(iaa.Identity(), hooks, []) \
as batch_prop:
assert batch_prop.images is None
assert batch_prop.keypoints is not None
assert len(batch_prop.keypoints) == 3
batch_prop.keypoints[0].keypoints[0].x = 10
assert batch.images is not None
assert batch.keypoints is not None
assert batch.keypoints[0].keypoints[0].x == 10
def test_to_batch_in_augmentation(self):
batch = _BatchInAugmentation(images=1)
batch_inaug = batch.to_batch_in_augmentation()
assert batch_inaug is batch
def test_fill_from_batch_in_augmentation(self):
batch = _BatchInAugmentation(images=1)
batch_inaug = _BatchInAugmentation(
images=2,
heatmaps=3,
segmentation_maps=4,
keypoints=5,
bounding_boxes=6,
polygons=7,
line_strings=8
)
batch = batch.fill_from_batch_in_augmentation_(batch_inaug)
assert batch.images == 2
assert batch.heatmaps == 3
assert batch.segmentation_maps == 4
assert batch.keypoints == 5
assert batch.bounding_boxes == 6
assert batch.polygons == 7
assert batch.line_strings == 8
def test_to_batch(self):
batch_before_aug = ia.Batch()
batch_before_aug.images_unaug = 0
batch_before_aug.heatmaps_unaug = 1
batch_before_aug.segmentation_maps_unaug = 2
batch_before_aug.keypoints_unaug = 3
batch_before_aug.bounding_boxes_unaug = 4
batch_before_aug.polygons_unaug = 5
batch_before_aug.line_strings_unaug = 6
batch_inaug = _BatchInAugmentation(
images=10,
heatmaps=20,
segmentation_maps=30,
keypoints=40,
bounding_boxes=50,
polygons=60,
line_strings=70
)
batch = batch_inaug.to_batch(batch_before_aug)
assert batch.images_unaug == 0
assert batch.heatmaps_unaug == 1
assert batch.segmentation_maps_unaug == 2
assert batch.keypoints_unaug == 3
assert batch.bounding_boxes_unaug == 4
assert batch.polygons_unaug == 5
assert batch.line_strings_unaug == 6
assert batch.images_aug == 10
assert batch.heatmaps_aug == 20
assert batch.segmentation_maps_aug == 30
assert batch.keypoints_aug == 40
assert batch.bounding_boxes_aug == 50
assert batch.polygons_aug == 60
assert batch.line_strings_aug == 70
def test_deepcopy(self):
batch = _BatchInAugmentation(
images=np.full((1,), 0, dtype=np.uint8),
heatmaps=np.full((1,), 1, dtype=np.uint8),
segmentation_maps=np.full((1,), 2, dtype=np.uint8),
keypoints=np.full((1,), 3, dtype=np.uint8),
bounding_boxes=np.full((1,), 4, dtype=np.uint8),
polygons=np.full((1,), 5, dtype=np.uint8),
line_strings=np.full((1,), 6, dtype=np.uint8)
)
batch_copy = batch.deepcopy()
assert np.max(batch_copy.images) == 0
assert np.max(batch_copy.heatmaps) == 1
assert np.max(batch_copy.segmentation_maps) == 2
assert np.max(batch_copy.keypoints) == 3
assert np.max(batch_copy.bounding_boxes) == 4
assert np.max(batch_copy.polygons) == 5
assert np.max(batch_copy.line_strings) == 6
================================================
FILE: test/augmentables/test_bbs.py
================================================
from __future__ import print_function, division, absolute_import
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import imgaug as ia
import imgaug.random as iarandom
from imgaug.augmentables.bbs import _LabelOnImageDrawer
from imgaug.testutils import wrap_shift_deprecation, assertWarns
class TestBoundingBox_project_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, cba, *args, **kwargs):
return cba.project_(*args, **kwargs)
def test_project_same_shape(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, (10, 10), (10, 10))
assert np.isclose(bb2.y1, 10)
assert np.isclose(bb2.x1, 20)
assert np.isclose(bb2.y2, 30)
assert np.isclose(bb2.x2, 40)
def test_project_upscale_by_2(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, (10, 10), (20, 20))
assert np.isclose(bb2.y1, 10*2)
assert np.isclose(bb2.x1, 20*2)
assert np.isclose(bb2.y2, 30*2)
assert np.isclose(bb2.x2, 40*2)
def test_project_downscale_by_2(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, (10, 10), (5, 5))
assert np.isclose(bb2.y1, 10*0.5)
assert np.isclose(bb2.x1, 20*0.5)
assert np.isclose(bb2.y2, 30*0.5)
assert np.isclose(bb2.x2, 40*0.5)
def test_project_onto_wider_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, (10, 10), (10, 20))
assert np.isclose(bb2.y1, 10*1)
assert np.isclose(bb2.x1, 20*2)
assert np.isclose(bb2.y2, 30*1)
assert np.isclose(bb2.x2, 40*2)
def test_project_onto_higher_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, (10, 10), (20, 10))
assert np.isclose(bb2.y1, 10*2)
assert np.isclose(bb2.x1, 20*1)
assert np.isclose(bb2.y2, 30*2)
assert np.isclose(bb2.x2, 40*1)
def test_inplaceness(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, (10, 10), (10, 10))
if self._is_inplace:
assert bb2 is bb
else:
assert bb2 is not bb
class TestBoundingBox_project(TestBoundingBox_project_):
@property
def _is_inplace(self):
return False
def _func(self, cba, *args, **kwargs):
return cba.project(*args, **kwargs)
class TestBoundingBox_extend_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, cba, *args, **kwargs):
return cba.extend_(*args, **kwargs)
def test_extend_all_sides_by_1(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, all_sides=1)
assert bb2.y1 == 10-1
assert bb2.y2 == 30+1
assert bb2.x1 == 20-1
assert bb2.x2 == 40+1
def test_extend_all_sides_by_minus_1(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, all_sides=-1)
assert bb2.y1 == 10-(-1)
assert bb2.y2 == 30+(-1)
assert bb2.x1 == 20-(-1)
assert bb2.x2 == 40+(-1)
def test_extend_top_by_1(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, top=1)
assert bb2.y1 == 10-1
assert bb2.y2 == 30+0
assert bb2.x1 == 20-0
assert bb2.x2 == 40+0
def test_extend_right_by_1(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, right=1)
assert bb2.y1 == 10-0
assert bb2.y2 == 30+0
assert bb2.x1 == 20-0
assert bb2.x2 == 40+1
def test_extend_bottom_by_1(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, bottom=1)
assert bb2.y1 == 10-0
assert bb2.y2 == 30+1
assert bb2.x1 == 20-0
assert bb2.x2 == 40+0
def test_extend_left_by_1(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, left=1)
assert bb2.y1 == 10-0
assert bb2.y2 == 30+0
assert bb2.x1 == 20-1
assert bb2.x2 == 40+0
def test_inplaceness(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, all_sides=1)
if self._is_inplace:
assert bb2 is bb
else:
assert bb2 is not bb
class TestBoundingBox_extend(TestBoundingBox_extend_):
@property
def _is_inplace(self):
return False
def _func(self, cba, *args, **kwargs):
return cba.extend(*args, **kwargs)
class TestBoundingBox_clip_out_of_image_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, cba, *args, **kwargs):
return cba.clip_out_of_image_(*args, **kwargs)
def test_clip_out_of_image_with_bb_fully_inside_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_cut = self._func(bb, (100, 100, 3))
assert bb_cut.y1 == 10
assert bb_cut.x1 == 20
assert bb_cut.y2 == 30
assert bb_cut.x2 == 40
def test_clip_out_of_image_with_array_as_shape(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
image = np.zeros((100, 100, 3), dtype=np.uint8)
bb_cut = bb.clip_out_of_image(image)
assert bb_cut.y1 == 10
assert bb_cut.x1 == 20
assert bb_cut.y2 == 30
assert bb_cut.x2 == 40
def test_clip_out_of_image_with_bb_too_high(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_cut = self._func(bb, (20, 100, 3))
assert bb_cut.y1 == 10
assert bb_cut.x1 == 20
assert np.isclose(bb_cut.y2, 20)
assert bb_cut.x2 == 40
def test_clip_out_of_image_with_bb_too_wide(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_cut = self._func(bb, (100, 30, 3))
assert bb_cut.y1 == 10
assert bb_cut.x1 == 20
assert bb_cut.y2 == 30
assert np.isclose(bb_cut.x2, 30)
def test_inplaceness(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, (100, 100, 3))
if self._is_inplace:
assert bb2 is bb
else:
assert bb2 is not bb
class TestBoundingBox_clip_out_of_image(TestBoundingBox_clip_out_of_image_):
@property
def _is_inplace(self):
return False
def _func(self, cba, *args, **kwargs):
return cba.clip_out_of_image(*args, **kwargs)
class TestBoundingBox_shift_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, cba, *args, **kwargs):
def _func_impl():
return cba.shift_(*args, **kwargs)
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_shift_by_x(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_top = self._func(bb, x=1)
assert bb_top.y1 == 10
assert bb_top.x1 == 20 + 1
assert bb_top.y2 == 30
assert bb_top.x2 == 40 + 1
def test_shift_by_y(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_top = self._func(bb, y=1)
assert bb_top.y1 == 10 + 1
assert bb_top.x1 == 20
assert bb_top.y2 == 30 + 1
assert bb_top.x2 == 40
def test_inplaceness(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = self._func(bb, y=0)
if self._is_inplace:
assert bb2 is bb
else:
assert bb2 is not bb
class TestBoundingBox_shift(TestBoundingBox_shift_):
@property
def _is_inplace(self):
return False
def _func(self, cba, *args, **kwargs):
def _func_impl():
return cba.shift(*args, **kwargs)
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_shift_top_by_zero(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_top = self._func(bb, top=0)
assert bb_top.y1 == 10
assert bb_top.x1 == 20
assert bb_top.y2 == 30
assert bb_top.x2 == 40
def test_shift_right_by_zero(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_right = self._func(bb, right=0)
assert bb_right.y1 == 10
assert bb_right.x1 == 20
assert bb_right.y2 == 30
assert bb_right.x2 == 40
def test_shift_bottom_by_zero(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_bottom = self._func(bb, bottom=0)
assert bb_bottom.y1 == 10
assert bb_bottom.x1 == 20
assert bb_bottom.y2 == 30
assert bb_bottom.x2 == 40
def test_shift_left_by_zero(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_left = self._func(bb, left=0)
assert bb_left.y1 == 10
assert bb_left.x1 == 20
assert bb_left.y2 == 30
assert bb_left.x2 == 40
def test_shift_top_by_one(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_top = self._func(bb, top=1)
assert bb_top.y1 == 10+1
assert bb_top.x1 == 20
assert bb_top.y2 == 30+1
assert bb_top.x2 == 40
def test_shift_right_by_one(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_right = self._func(bb, right=1)
assert bb_right.y1 == 10
assert bb_right.x1 == 20-1
assert bb_right.y2 == 30
assert bb_right.x2 == 40-1
def test_shift_bottom_by_one(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_bottom = self._func(bb, bottom=1)
assert bb_bottom.y1 == 10-1
assert bb_bottom.x1 == 20
assert bb_bottom.y2 == 30-1
assert bb_bottom.x2 == 40
def test_shift_left_by_one(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_left = self._func(bb, left=1)
assert bb_left.y1 == 10
assert bb_left.x1 == 20+1
assert bb_left.y2 == 30
assert bb_left.x2 == 40+1
def test_shift_top_by_minus_one(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_top = self._func(bb, top=-1)
assert bb_top.y1 == 10-1
assert bb_top.x1 == 20
assert bb_top.y2 == 30-1
assert bb_top.x2 == 40
def test_shift_right_by_minus_one(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_right = self._func(bb, right=-1)
assert bb_right.y1 == 10
assert bb_right.x1 == 20+1
assert bb_right.y2 == 30
assert bb_right.x2 == 40+1
def test_shift_bottom_by_minus_one(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_bottom = self._func(bb, bottom=-1)
assert bb_bottom.y1 == 10+1
assert bb_bottom.x1 == 20
assert bb_bottom.y2 == 30+1
assert bb_bottom.x2 == 40
def test_shift_left_by_minus_one(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_left = self._func(bb, left=-1)
assert bb_left.y1 == 10
assert bb_left.x1 == 20-1
assert bb_left.y2 == 30
assert bb_left.x2 == 40-1
def test_shift_all_sides_by_individual_amounts(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb_mix = self._func(bb, top=1, bottom=2, left=3, right=4)
assert bb_mix.y1 == 10+1-2
assert bb_mix.x1 == 20+3-4
assert bb_mix.y2 == 30+3-4
assert bb_mix.x2 == 40+1-2
class TestBoundingBox(unittest.TestCase):
def test___init__(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
assert bb.y1 == 10
assert bb.x1 == 20
assert bb.y2 == 30
assert bb.x2 == 40
assert bb.label is None
def test___init___floats(self):
bb = ia.BoundingBox(y1=10.1, x1=20.2, y2=30.3, x2=40.4)
assert np.isclose(bb.y1, 10.1)
assert np.isclose(bb.x1, 20.2)
assert np.isclose(bb.y2, 30.3)
assert np.isclose(bb.x2, 40.4)
assert bb.label is None
def test___init___label(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40, label="foo")
assert bb.y1 == 10
assert bb.x1 == 20
assert bb.y2 == 30
assert bb.x2 == 40
assert bb.label == "foo"
def test___init___wrong_x1_x2_order(self):
bb = ia.BoundingBox(y1=10, x1=40, y2=30, x2=20)
assert bb.y1 == 10
assert bb.x1 == 20
assert bb.y2 == 30
assert bb.x2 == 40
def test___init___wrong_y1_y2_order(self):
bb = ia.BoundingBox(y1=30, x1=20, y2=10, x2=40)
assert bb.y1 == 10
assert bb.x1 == 20
assert bb.y2 == 30
assert bb.x2 == 40
def test_coords_property_ints(self):
bb = ia.BoundingBox(x1=10, y1=20, x2=30, y2=40)
coords = bb.coords
assert np.allclose(coords, [[10, 20], [30, 40]],
atol=1e-4, rtol=0)
def test_coords_property_floats(self):
bb = ia.BoundingBox(x1=10.1, y1=20.2, x2=30.3, y2=40.4)
coords = bb.coords
assert np.allclose(coords, [[10.1, 20.2], [30.3, 40.4]],
atol=1e-4, rtol=0)
def test_xy_int_properties(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
assert bb.y1_int == 10
assert bb.x1_int == 20
assert bb.y2_int == 30
assert bb.x2_int == 40
def test_xy_int_properties_floats(self):
bb = ia.BoundingBox(y1=10.1, x1=20.2, y2=30.6, x2=40.7)
assert bb.y1_int == 10
assert bb.x1_int == 20
assert bb.y2_int == 31
assert bb.x2_int == 41
def test_width(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
assert bb.width == 40 - 20
def test_width_floats(self):
bb = ia.BoundingBox(y1=10.1, x1=20.2, y2=30.3, x2=40.4)
assert np.isclose(bb.width, 40.4 - 20.2)
def test_height(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
assert bb.height == 30 - 10
def test_height_floats(self):
bb = ia.BoundingBox(y1=10.1, x1=20.2, y2=30.3, x2=40.4)
assert np.isclose(bb.height, 30.3 - 10.1)
def test_center_x(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
expected = 20 + (40 - 20)/2
assert np.isclose(bb.center_x, expected)
def test_center_x_floats(self):
bb = ia.BoundingBox(y1=10.1, x1=20.2, y2=30.3, x2=40.4)
expected = 20.2 + (40.4 - 20.2)/2
assert np.isclose(bb.center_x, expected)
def test_center_y(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
expected = 10 + (30 - 10)/2
assert np.isclose(bb.center_y, expected)
def test_center_y_floats(self):
bb = ia.BoundingBox(y1=10.1, x1=20.2, y2=30.3, x2=40.4)
expected = 10.1 + (30.3 - 10.1)/2
assert np.isclose(bb.center_y, expected)
def test_area(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
assert bb.area == (30-10) * (40-20)
def test_area_floats(self):
bb = ia.BoundingBox(y1=10.1, x1=20.2, y2=30.3, x2=40.4)
assert np.isclose(bb.area, (30.3-10.1) * (40.4-20.2))
def test_contains(self):
bb = ia.BoundingBox(y1=1, x1=2, y2=1+4, x2=2+5, label=None)
assert bb.contains(ia.Keypoint(x=2.5, y=1.5)) is True
assert bb.contains(ia.Keypoint(x=2, y=1)) is True
assert bb.contains(ia.Keypoint(x=0, y=0)) is False
def test_intersection(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=10, x1=39, y2=30, x2=59)
bb_inter = bb1.intersection(bb2)
assert bb_inter.x1 == 39
assert bb_inter.x2 == 40
assert bb_inter.y1 == 10
assert bb_inter.y2 == 30
def test_intersection_of_non_overlapping_bbs(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=10, x1=41, y2=30, x2=61)
bb_inter = bb1.intersection(bb2, default=False)
assert bb_inter is False
def test_union(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=10, x1=39, y2=30, x2=59)
bb_union = bb1.union(bb2)
assert bb_union.x1 == 20
assert bb_union.x2 == 59
assert bb_union.y1 == 10
assert bb_union.y2 == 30
def test_iou_of_identical_bbs(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
iou = bb1.iou(bb2)
assert np.isclose(iou, 1.0)
def test_iou_of_non_overlapping_bbs(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=10, x1=41, y2=30, x2=61)
iou = bb1.iou(bb2)
assert np.isclose(iou, 0.0)
def test_iou_of_partially_overlapping_bbs(self):
bb1 = ia.BoundingBox(y1=10, x1=10, y2=20, x2=20)
bb2 = ia.BoundingBox(y1=15, x1=15, y2=25, x2=25)
iou = bb1.iou(bb2)
area_union = 10 * 10 + 10 * 10 - 5 * 5
area_intersection = 5 * 5
iou_expected = area_intersection / area_union
assert np.isclose(iou, iou_expected)
def test_compute_out_of_image_area__fully_inside(self):
bb = ia.BoundingBox(y1=10.1, x1=20.2, y2=30.3, x2=40.4)
image_shape = (100, 200, 3)
area_ooi = bb.compute_out_of_image_area(image_shape)
assert np.isclose(area_ooi, 0.0)
def test_compute_out_of_image_area__partially_ooi(self):
bb = ia.BoundingBox(y1=10, x1=-20, y2=30, x2=40)
image_shape = (100, 200, 3)
area_ooi = bb.compute_out_of_image_area(image_shape)
assert np.isclose(area_ooi, (0-(-20))*(30-10))
def test_compute_out_of_image_area__fully_ooi(self):
bb = ia.BoundingBox(y1=10, x1=-20, y2=30, x2=-10)
image_shape = (100, 200, 3)
area_ooi = bb.compute_out_of_image_area(image_shape)
assert np.isclose(area_ooi, 20*10)
def test_compute_out_of_image_area__zero_sized_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
image_shape = (0, 0, 3)
area_ooi = bb.compute_out_of_image_area(image_shape)
assert np.isclose(area_ooi, bb.area)
def test_compute_out_of_image_area__bb_has_zero_sized_area(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=10, x2=20)
image_shape = (100, 200, 3)
area_ooi = bb.compute_out_of_image_area(image_shape)
assert np.isclose(area_ooi, 0.0)
def test_compute_out_of_image_fraction__inside_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
image_shape = (100, 200, 3)
factor = bb.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_compute_out_of_image_fraction__partially_ooi(self):
bb = ia.BoundingBox(y1=10, x1=-20, y2=30, x2=40)
image_shape = (100, 200, 3)
factor = bb.compute_out_of_image_fraction(image_shape)
expected = (20 * 20) / (20 * 60)
assert np.isclose(factor, expected)
def test_compute_out_of_image_fraction__fully_ooi(self):
bb = ia.BoundingBox(y1=10, x1=-20, y2=30, x2=0)
image_shape = (100, 200, 3)
factor = bb.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 1.0)
def test_compute_out_of_image_fraction__zero_area_inside_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=10, x2=20)
image_shape = (100, 200, 3)
factor = bb.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_compute_out_of_image_fraction__zero_area_ooi(self):
bb = ia.BoundingBox(y1=-10, x1=20, y2=-10, x2=20)
image_shape = (100, 200, 3)
factor = bb.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 1.0)
def test_is_fully_within_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40, label=None)
assert bb.is_fully_within_image((100, 100, 3)) is True
assert bb.is_fully_within_image((20, 100, 3)) is False
assert bb.is_fully_within_image((100, 30, 3)) is False
assert bb.is_fully_within_image((1, 1, 3)) is False
def test_is_partly_within_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40, label=None)
assert bb.is_partly_within_image((100, 100, 3)) is True
assert bb.is_partly_within_image((20, 100, 3)) is True
assert bb.is_partly_within_image((100, 30, 3)) is True
assert bb.is_partly_within_image((1, 1, 3)) is False
def test_is_out_of_image(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40, label=None)
subtests = [
((100, 100, 3), True, True, False),
((100, 100, 3), False, True, False),
((100, 100, 3), True, False, False),
((20, 100, 3), True, True, True),
((20, 100, 3), False, True, False),
((20, 100, 3), True, False, True),
((100, 30, 3), True, True, True),
((100, 30, 3), False, True, False),
((100, 30, 3), True, False, True),
((1, 1, 3), True, True, True),
((1, 1, 3), False, True, True),
((1, 1, 3), True, False, False)
]
for shape, partly, fully, expected in subtests:
with self.subTest(shape=shape, partly=partly, fully=fully):
observed = bb.is_out_of_image(shape,
partly=partly, fully=fully)
assert observed is expected
@mock.patch("imgaug.augmentables.bbs._LabelOnImageDrawer")
def test_draw_label_on_image_mocked(self, mock_drawer):
mock_drawer.return_value = mock_drawer
image = np.zeros((10, 10, 3), dtype=np.uint8)
bb = ia.BoundingBox(y1=1, x1=1, y2=3, x2=3)
result = bb.draw_label_on_image(image)
kwargs = mock_drawer.call_args_list[0][1]
assert kwargs["color"] == (0, 255, 0)
assert kwargs["color_text"] is None
assert kwargs["color_bg"] is None
assert np.isclose(kwargs["alpha"], 1.0)
assert kwargs["size"] == 1
assert kwargs["size_text"] == 20
assert kwargs["height"] == 30
assert kwargs["raise_if_out_of_image"] is False
assert mock_drawer.draw_on_image.call_count == 1
@mock.patch("imgaug.augmentables.bbs._LabelOnImageDrawer")
def test_draw_label_on_image_mocked_inplace(self, mock_drawer):
mock_drawer.return_value = mock_drawer
image = np.zeros((10, 10, 3), dtype=np.uint8)
bb = ia.BoundingBox(y1=1, x1=1, y2=3, x2=3)
result = bb.draw_label_on_image(image, copy=False)
kwargs = mock_drawer.call_args_list[0][1]
assert kwargs["color"] == (0, 255, 0)
assert kwargs["color_text"] is None
assert kwargs["color_bg"] is None
assert np.isclose(kwargs["alpha"], 1.0)
assert kwargs["size"] == 1
assert kwargs["size_text"] == 20
assert kwargs["height"] == 30
assert kwargs["raise_if_out_of_image"] is False
assert mock_drawer.draw_on_image_.call_count == 1
def test_draw_label_on_image(self):
image = np.zeros((100, 70, 3), dtype=np.uint8)
bb = ia.BoundingBox(y1=40, x1=10, y2=50, x2=40)
result = bb.draw_label_on_image(image,
color_bg=(123, 123, 123),
color_text=(222, 222, 222))
color_bg = np.uint8([123, 123, 123]).reshape((1, 1, -1))
color_text = np.uint8([222, 222, 222]).reshape((1, 1, -1))
matches_bg = np.min(result == color_bg, axis=-1)
matches_text = np.min(result == color_text, axis=-1)
assert np.any(matches_bg > 0)
assert np.any(matches_text > 0)
@classmethod
def _get_standard_draw_box_on_image_vars(cls):
image = np.zeros((10, 10, 3), dtype=np.uint8)
bb = ia.BoundingBox(y1=1, x1=1, y2=3, x2=3)
bb_mask = np.zeros(image.shape[0:2], dtype=np.bool)
bb_mask[1:3+1, 1] = True
bb_mask[1:3+1, 3] = True
bb_mask[1, 1:3+1] = True
bb_mask[3, 1:3+1] = True
return image, bb, bb_mask
def test_draw_box_on_image(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
image_bb = bb.draw_box_on_image(
image, color=[255, 255, 255], alpha=1.0, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_bb[bb_mask] == [255, 255, 255])
assert np.all(image_bb[~bb_mask] == [0, 0, 0])
assert np.all(image == 0)
def test_draw_box_on_image_red_color(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
image_bb = bb.draw_box_on_image(
image, color=[255, 0, 0], alpha=1.0, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_bb[bb_mask] == [255, 0, 0])
assert np.all(image_bb[~bb_mask] == [0, 0, 0])
def test_draw_box_on_image_single_int_as_color(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
image_bb = bb.draw_box_on_image(
image, color=128, alpha=1.0, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_bb[bb_mask] == [128, 128, 128])
assert np.all(image_bb[~bb_mask] == [0, 0, 0])
def test_draw_box_on_image_alpha_at_50_percent(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
image_bb = bb.draw_box_on_image(
image + 100, color=[200, 200, 200], alpha=0.5, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_bb[bb_mask] == [150, 150, 150])
assert np.all(image_bb[~bb_mask] == [100, 100, 100])
def test_draw_box_on_image_alpha_at_50_percent_and_float32_image(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
image_bb = bb.draw_box_on_image(
(image+100).astype(np.float32),
color=[200, 200, 200], alpha=0.5, size=1,
copy=True, raise_if_out_of_image=False)
assert np.sum(np.abs((image_bb - [150, 150, 150])[bb_mask])) < 0.1
assert np.sum(np.abs((image_bb - [100, 100, 100])[~bb_mask])) < 0.1
def test_draw_box_on_image_no_copy(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
image_bb = bb.draw_box_on_image(
image, color=[255, 255, 255], alpha=1.0, size=1, copy=False,
raise_if_out_of_image=False)
assert np.all(image_bb[bb_mask] == [255, 255, 255])
assert np.all(image_bb[~bb_mask] == [0, 0, 0])
assert np.all(image[bb_mask] == [255, 255, 255])
assert np.all(image[~bb_mask] == [0, 0, 0])
def test_draw_box_on_image_bb_outside_of_image(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
bb = ia.BoundingBox(y1=-1, x1=-1, y2=2, x2=2)
bb_mask = np.zeros(image.shape[0:2], dtype=np.bool)
bb_mask[2, 0:3] = True
bb_mask[0:3, 2] = True
image_bb = bb.draw_box_on_image(
image, color=[255, 255, 255], alpha=1.0, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_bb[bb_mask] == [255, 255, 255])
assert np.all(image_bb[~bb_mask] == [0, 0, 0])
def test_draw_box_on_image_bb_outside_of_image_and_very_small(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
bb = ia.BoundingBox(y1=-1, x1=-1, y2=1, x2=1)
bb_mask = np.zeros(image.shape[0:2], dtype=np.bool)
bb_mask[0:1+1, 1] = True
bb_mask[1, 0:1+1] = True
image_bb = bb.draw_box_on_image(
image, color=[255, 255, 255], alpha=1.0, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_bb[bb_mask] == [255, 255, 255])
assert np.all(image_bb[~bb_mask] == [0, 0, 0])
def test_draw_box_on_image_size_2(self):
image, bb, _ = self._get_standard_draw_box_on_image_vars()
bb_mask = np.zeros(image.shape[0:2], dtype=np.bool)
bb_mask[0:5, 0:5] = True
bb_mask[2, 2] = False
image_bb = bb.draw_box_on_image(
image, color=[255, 255, 255], alpha=1.0, size=2, copy=True,
raise_if_out_of_image=False)
assert np.all(image_bb[bb_mask] == [255, 255, 255])
assert np.all(image_bb[~bb_mask] == [0, 0, 0])
def test_draw_box_on_image_raise_true_but_bb_partially_inside_image(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
bb = ia.BoundingBox(y1=-1, x1=-1, y2=1, x2=1)
_ = bb.draw_box_on_image(
image, color=[255, 255, 255], alpha=1.0, size=1, copy=True,
raise_if_out_of_image=True)
def test_draw_box_on_image_raise_true_and_bb_fully_outside_image(self):
image, bb, bb_mask = self._get_standard_draw_box_on_image_vars()
bb = ia.BoundingBox(y1=-5, x1=-5, y2=-1, x2=-1)
with self.assertRaises(Exception) as context:
_ = bb.draw_box_on_image(
image, color=[255, 255, 255], alpha=1.0, size=1, copy=True,
raise_if_out_of_image=True)
assert "Cannot draw bounding box" in str(context.exception)
def test_draw_on_image_label_is_none(self):
# if label is None, no label box should be drawn, only the rectangle
# box below the label
image = np.zeros((100, 70, 3), dtype=np.uint8)
bb = ia.BoundingBox(y1=40, x1=10, y2=50, x2=40, label=None)
image_drawn = bb.draw_on_image(image)
expected = bb.draw_box_on_image(image)
assert np.array_equal(image_drawn, expected)
def test_draw_on_image_label_is_str(self):
# if label is None, no label box should be drawn, only the rectangle
# box below the label
image = np.zeros((100, 70, 3), dtype=np.uint8)
bb = ia.BoundingBox(y1=40, x1=10, y2=50, x2=40, label="Foo")
image_drawn = bb.draw_on_image(image)
expected = bb.draw_box_on_image(image)
expected = bb.draw_label_on_image(expected)
assert np.array_equal(image_drawn, expected)
def test_extract_from_image(self):
image = iarandom.RNG(1234).integers(0, 255, size=(10, 10, 3))
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3)
image_sub = bb.extract_from_image(image)
assert np.array_equal(image_sub, image[1:3, 1:3, :])
def test_extract_from_image_no_channels(self):
image = iarandom.RNG(1234).integers(0, 255, size=(10, 10))
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3)
image_sub = bb.extract_from_image(image)
assert np.array_equal(image_sub, image[1:3, 1:3])
def test_extract_from_image_bb_partially_out_of_image(self):
image = iarandom.RNG(1234).integers(0, 255, size=(10, 10, 3))
bb = ia.BoundingBox(y1=8, y2=11, x1=8, x2=11)
image_sub = bb.extract_from_image(image)
image_pad = np.pad(
image,
((0, 1), (0, 1), (0, 0)),
mode="constant",
constant_values=0) # pad at bottom and right each 1px (black)
assert np.array_equal(image_sub, image_pad[8:11, 8:11, :])
def test_extract_from_image_bb_partially_out_of_image_no_channels(self):
image = iarandom.RNG(1234).integers(0, 255, size=(10, 10))
bb = ia.BoundingBox(y1=8, y2=11, x1=8, x2=11)
image_sub = bb.extract_from_image(image)
image_pad = np.pad(
image,
((0, 1), (0, 1)),
mode="constant",
constant_values=0) # pad at bottom and right each 1px (black)
assert np.array_equal(image_sub, image_pad[8:11, 8:11])
def test_extract_from_image_bb_partially_out_of_image_top_left(self):
image = iarandom.RNG(1234).integers(0, 255, size=(10, 10, 3))
bb = ia.BoundingBox(y1=-1, y2=3, x1=-1, x2=4)
image_sub = bb.extract_from_image(image)
image_pad = np.pad(
image,
((1, 0), (1, 0), (0, 0)),
mode="constant",
constant_values=0) # pad at top and left each 1px (black)
assert np.array_equal(image_sub, image_pad[0:4, 0:5, :])
def test_extract_from_image_float_coords(self):
image = iarandom.RNG(1234).integers(0, 255, size=(10, 10, 3))
bb = ia.BoundingBox(y1=1, y2=1.99999, x1=1, x2=1.99999)
image_sub = bb.extract_from_image(image)
assert np.array_equal(image_sub, image[1:1+1, 1:1+1, :])
def test_extract_from_image_bb_height_is_zero(self):
image = iarandom.RNG(1234).integers(0, 255, size=(10, 10, 3))
bb = ia.BoundingBox(y1=1, y2=1, x1=2, x2=4)
image_sub = bb.extract_from_image(image)
assert np.array_equal(image_sub, image[1:1+1, 2:4, :])
def test_extract_from_image_bb_width_is_zero(self):
image = iarandom.RNG(1234).integers(0, 255, size=(10, 10, 3))
bb = ia.BoundingBox(y1=1, y2=1, x1=2, x2=2)
image_sub = bb.extract_from_image(image)
assert np.array_equal(image_sub, image[1:1+1, 2:2+1, :])
def test_to_keypoints(self):
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3)
kps = bb.to_keypoints()
assert len(kps) == 4
assert kps[0].y == 1
assert kps[0].x == 1
assert kps[1].y == 1
assert kps[1].x == 3
assert kps[2].y == 3
assert kps[2].x == 3
assert kps[3].y == 3
assert kps[3].x == 1
def test_to_polygon(self):
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3)
poly = bb.to_polygon()
assert poly.coords_almost_equals([
(1, 1),
(3, 1),
(3, 3,),
(1, 3)
])
def test_coords_almost_equals(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
equal = bb.coords_almost_equals(other)
assert equal
def test_coords_almost_equals__unequal(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = ia.BoundingBox(x1=1+1, y1=3+1, x2=1+1, y2=3+1)
equal = bb.coords_almost_equals(other)
assert not equal
def test_coords_almost_equals__dist_below_max_distance(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3+1e-5)
equal = bb.coords_almost_equals(other, max_distance=1e-4)
assert equal
def test_coords_almost_equals__dist_above_max_distance(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3+1e-3)
equal = bb.coords_almost_equals(other, max_distance=1e-4)
assert not equal
def test_coords_almost_equals__input_is_array(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = np.float32([[1, 3], [1, 3]])
equal = bb.coords_almost_equals(other)
assert equal
def test_coords_almost_equals__input_is_array_not_equal(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = np.float32([[1, 3], [1, 3+0.5]])
equal = bb.coords_almost_equals(other)
assert not equal
def test_coords_almost_equals__input_is_list(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = [[1, 3], [1, 3]]
equal = bb.coords_almost_equals(other)
assert equal
def test_coords_almost_equals__input_is_list_not_equal(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = [[1, 3], [1, 3+0.5]]
equal = bb.coords_almost_equals(other)
assert not equal
def test_coords_almost_equals__bad_datatype(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
with self.assertRaises(ValueError) as cm:
_ = bb.coords_almost_equals(False)
assert "Expected 'other'" in str(cm.exception)
@mock.patch("imgaug.augmentables.bbs.BoundingBox.coords_almost_equals")
def test_almost_equals(self, mock_cae):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
other = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
equal = bb.almost_equals(other, max_distance=1)
assert equal
mock_cae.assert_called_once_with(other, max_distance=1)
def test_almost_equals__labels_none_vs_string(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3, label="foo")
other = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3)
equal = bb.almost_equals(other)
assert not equal
def test_almost_equals__labels_different_strings(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3, label="foo")
other = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3, label="bar")
equal = bb.almost_equals(other)
assert not equal
def test_almost_equals__same_string(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3, label="foo")
other = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3, label="foo")
equal = bb.almost_equals(other)
assert equal
def test_almost_equals__distance_above_threshold(self):
bb = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3, label="foo")
other = ia.BoundingBox(x1=1, y1=3, x2=1, y2=3+1e-1, label="foo")
equal = bb.almost_equals(other, max_distance=1e-2)
assert not equal
def test_from_point_soup__empty_list(self):
with self.assertRaises(AssertionError) as ctx:
_ = ia.BoundingBox.from_point_soup([])
assert "Expected to get at least one point" in str(ctx.exception)
def test_from_point_soup__empty_array(self):
with self.assertRaises(AssertionError) as ctx:
_ = ia.BoundingBox.from_point_soup(np.zeros((0, 2)))
assert "Expected to get at least one point" in str(ctx.exception)
def test_from_point_soup__list_with_single_point(self):
points = [(1, 2)]
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 1
assert bb.y2 == 2
def test_from_point_soup__list_with_single_point__single_level(self):
points = [1, 2]
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 1
assert bb.y2 == 2
def test_from_point_soup__list_with_two_points(self):
points = [(1, 2), (3, 4)]
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 3
assert bb.y2 == 4
def test_from_point_soup__list_with_three_points(self):
points = [(1, 4), (3, 2), (15, 16)]
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 15
assert bb.y2 == 16
def test_from_point_soup__array_with_single_point(self):
points = np.float32([(1, 2)])
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 1
assert bb.y2 == 2
def test_from_point_soup__array_with_single_point__single_level(self):
points = np.float32([1, 2])
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 1
assert bb.y2 == 2
def test_from_point_soup__array_with_two_points__single_level(self):
points = np.float32([1, 2, 3, 4])
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 3
assert bb.y2 == 4
def test_from_point_soup__array_with_two_points(self):
points = np.float32([(1, 2), (3, 4)])
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 3
assert bb.y2 == 4
def test_from_point_soup__array_with_three_points(self):
points = np.float32([(1, 4), (3, 2), (15, 16)])
bb = ia.BoundingBox.from_point_soup(points)
assert bb.x1 == 1
assert bb.y1 == 2
assert bb.x2 == 15
assert bb.y2 == 16
def test_copy(self):
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3, label="test")
bb2 = bb.copy()
assert bb2.y1 == 1
assert bb2.y2 == 3
assert bb2.x1 == 1
assert bb2.x2 == 3
assert bb2.label == "test"
def test_copy_and_replace_attributes(self):
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3, label="test")
bb2 = bb.copy(y1=10, x1=20, y2=30, x2=40, label="test2")
assert bb2.y1 == 10
assert bb2.x1 == 20
assert bb2.y2 == 30
assert bb2.x2 == 40
assert bb2.label == "test2"
def test_deepcopy(self):
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3, label=["test"])
bb2 = bb.deepcopy()
bb2.label[0] = "foo"
assert bb2.y1 == 1
assert bb2.y2 == 3
assert bb2.x1 == 1
assert bb2.x2 == 3
assert bb2.label[0] == "foo"
assert bb.label[0] == "test"
def test_deepcopy_and_replace_attributes(self):
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3, label="test")
bb2 = bb.deepcopy(y1=10, y2=30, x1=15, x2=35, label="asd")
assert bb2.y1 == 10
assert bb2.y2 == 30
assert bb2.x1 == 15
assert bb2.x2 == 35
assert bb2.label == "asd"
assert bb.label == "test"
def test___getitem__(self):
cba = ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)
assert np.allclose(cba[0], (1, 2))
assert np.allclose(cba[1], (3, 4))
def test___iter__(self):
cba = ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)
for i, xy in enumerate(cba):
assert i in [0, 1]
if i == 0:
assert np.allclose(xy, (1, 2))
elif i == 1:
assert np.allclose(xy, (3, 4))
assert i == 1
def test_string_conversion(self):
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3)
assert (
bb.__str__()
== bb.__repr__()
== "BoundingBox("
"x1=1.0000, y1=1.0000, x2=3.0000, y2=3.0000, "
"label=None)"
)
def test_string_conversion_with_label(self):
bb = ia.BoundingBox(y1=1, y2=3, x1=1, x2=3, label="foo")
assert (
bb.__str__()
== bb.__repr__()
== "BoundingBox("
"x1=1.0000, y1=1.0000, x2=3.0000, y2=3.0000, "
"label=foo)"
)
class TestBoundingBoxesOnImage_items_setter(unittest.TestCase):
def test_with_list_of_bounding_boxes(self):
bbs = [ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=3, y1=4, x2=5, y2=6)]
bbsoi = ia.BoundingBoxesOnImage([], shape=(10, 20, 3))
bbsoi.items = bbs
assert np.all([
(bb_i.x1 == bb_j.x1
and bb_i.y1 == bb_j.y1
and bb_i.x2 == bb_j.x2
and bb_i.y2 == bb_j.y2)
for bb_i, bb_j
in zip(bbsoi.bounding_boxes, bbs)
])
class TestBoundingBoxesOnImage_on_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, cbaoi, *args, **kwargs):
return cbaoi.on_(*args, **kwargs)
def test_on_same_height_width(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_projected = self._func(bbsoi, (40, 50))
assert bbsoi_projected.bounding_boxes[0].y1 == 10
assert bbsoi_projected.bounding_boxes[0].x1 == 20
assert bbsoi_projected.bounding_boxes[0].y2 == 30
assert bbsoi_projected.bounding_boxes[0].x2 == 40
assert bbsoi_projected.bounding_boxes[1].y1 == 15
assert bbsoi_projected.bounding_boxes[1].x1 == 25
assert bbsoi_projected.bounding_boxes[1].y2 == 35
assert bbsoi_projected.bounding_boxes[1].x2 == 45
assert bbsoi_projected.shape == (40, 50)
def test_on_upscaled_by_2(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_projected = self._func(bbsoi, (40*2, 50*2, 3))
assert bbsoi_projected.bounding_boxes[0].y1 == 10*2
assert bbsoi_projected.bounding_boxes[0].x1 == 20*2
assert bbsoi_projected.bounding_boxes[0].y2 == 30*2
assert bbsoi_projected.bounding_boxes[0].x2 == 40*2
assert bbsoi_projected.bounding_boxes[1].y1 == 15*2
assert bbsoi_projected.bounding_boxes[1].x1 == 25*2
assert bbsoi_projected.bounding_boxes[1].y2 == 35*2
assert bbsoi_projected.bounding_boxes[1].x2 == 45*2
assert bbsoi_projected.shape == (40*2, 50*2, 3)
def test_on_upscaled_by_2_with_shape_given_as_array(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_projected = self._func(bbsoi, np.zeros((40*2, 50*2, 3), dtype=np.uint8))
assert bbsoi_projected.bounding_boxes[0].y1 == 10*2
assert bbsoi_projected.bounding_boxes[0].x1 == 20*2
assert bbsoi_projected.bounding_boxes[0].y2 == 30*2
assert bbsoi_projected.bounding_boxes[0].x2 == 40*2
assert bbsoi_projected.bounding_boxes[1].y1 == 15*2
assert bbsoi_projected.bounding_boxes[1].x1 == 25*2
assert bbsoi_projected.bounding_boxes[1].y2 == 35*2
assert bbsoi_projected.bounding_boxes[1].x2 == 45*2
assert bbsoi_projected.shape == (40*2, 50*2, 3)
def test_inplaceness(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi2 = self._func(bbsoi, (40, 50))
if self._is_inplace:
assert bbsoi2 is bbsoi
else:
assert bbsoi2 is not bbsoi
class TestBoundingBoxesOnImage_on(TestBoundingBoxesOnImage_on_):
@property
def _is_inplace(self):
return False
def _func(self, cbaoi, *args, **kwargs):
return cbaoi.on(*args, **kwargs)
class TestBoundingBoxesOnImage_clip_out_of_image_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, cbaoi, *args, **kwargs):
return cbaoi.clip_out_of_image_()
def test_clip_out_of_image(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_clip = self._func(bbsoi)
assert len(bbsoi_clip.bounding_boxes) == 2
assert bbsoi_clip.bounding_boxes[0].y1 == 10
assert bbsoi_clip.bounding_boxes[0].x1 == 20
assert bbsoi_clip.bounding_boxes[0].y2 == 30
assert bbsoi_clip.bounding_boxes[0].x2 == 40
assert bbsoi_clip.bounding_boxes[1].y1 == 15
assert bbsoi_clip.bounding_boxes[1].x1 == 25
assert bbsoi_clip.bounding_boxes[1].y2 == 35
assert np.isclose(bbsoi_clip.bounding_boxes[1].x2, 50)
def test_inplaceness(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi2 = self._func(bbsoi, (40, 50))
if self._is_inplace:
assert bbsoi2 is bbsoi
else:
assert bbsoi2 is not bbsoi
class TestBoundingBoxesOnImage_clip_out_of_image(TestBoundingBoxesOnImage_clip_out_of_image_):
@property
def _is_inplace(self):
return False
def _func(self, cbaoi, *args, **kwargs):
return cbaoi.clip_out_of_image()
class TestBoundingBoxesOnImage(unittest.TestCase):
def test___init__(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
assert bbsoi.bounding_boxes == [bb1, bb2]
assert bbsoi.shape == (40, 50, 3)
def test___init___array_as_shape(self):
image = np.zeros((40, 50, 3), dtype=np.uint8)
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
with assertWarns(self, ia.DeprecationWarning):
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=image)
assert bbsoi.bounding_boxes == [bb1, bb2]
assert bbsoi.shape == (40, 50, 3)
def test_items(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
items = bbsoi.items
assert items == [bb1, bb2]
def test_items_empty(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(40, 50, 3))
items = bbsoi.items
assert items == []
def test_height(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
assert bbsoi.height == 40
def test_width(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
assert bbsoi.width == 50
def test_empty_when_bbs_not_empty(self):
bb = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bbsoi = ia.BoundingBoxesOnImage([bb], shape=(40, 50, 3))
assert not bbsoi.empty
def test_empty_when_bbs_actually_empty(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(40, 50, 3))
assert bbsoi.empty
def test_from_xyxy_array_float(self):
xyxy = np.float32([
[0.0, 0.0, 1.0, 1.0],
[1.0, 2.0, 3.0, 4.0]
])
bbsoi = ia.BoundingBoxesOnImage.from_xyxy_array(xyxy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 2
assert np.allclose(bbsoi.bounding_boxes[0].x1, 0.0)
assert np.allclose(bbsoi.bounding_boxes[0].y1, 0.0)
assert np.allclose(bbsoi.bounding_boxes[0].x2, 1.0)
assert np.allclose(bbsoi.bounding_boxes[0].y2, 1.0)
assert np.allclose(bbsoi.bounding_boxes[1].x1, 1.0)
assert np.allclose(bbsoi.bounding_boxes[1].y1, 2.0)
assert np.allclose(bbsoi.bounding_boxes[1].x2, 3.0)
assert np.allclose(bbsoi.bounding_boxes[1].y2, 4.0)
assert bbsoi.shape == (40, 50, 3)
def test_from_xyxy_array_float_3d(self):
xyxy = np.float32([
[
[0.0, 0.0],
[1.0, 1.0]
],
[
[1.0, 2.0],
[3.0, 4.0]
]
])
bbsoi = ia.BoundingBoxesOnImage.from_xyxy_array(xyxy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 2
assert np.allclose(bbsoi.bounding_boxes[0].x1, 0.0)
assert np.allclose(bbsoi.bounding_boxes[0].y1, 0.0)
assert np.allclose(bbsoi.bounding_boxes[0].x2, 1.0)
assert np.allclose(bbsoi.bounding_boxes[0].y2, 1.0)
assert np.allclose(bbsoi.bounding_boxes[1].x1, 1.0)
assert np.allclose(bbsoi.bounding_boxes[1].y1, 2.0)
assert np.allclose(bbsoi.bounding_boxes[1].x2, 3.0)
assert np.allclose(bbsoi.bounding_boxes[1].y2, 4.0)
assert bbsoi.shape == (40, 50, 3)
def test_from_xyxy_array_int32(self):
xyxy = np.int32([
[0, 0, 1, 1],
[1, 2, 3, 4]
])
bbsoi = ia.BoundingBoxesOnImage.from_xyxy_array(xyxy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 2
assert np.allclose(bbsoi.bounding_boxes[0].x1, 0.0)
assert np.allclose(bbsoi.bounding_boxes[0].y1, 0.0)
assert np.allclose(bbsoi.bounding_boxes[0].x2, 1.0)
assert np.allclose(bbsoi.bounding_boxes[0].y2, 1.0)
assert np.allclose(bbsoi.bounding_boxes[1].x1, 1.0)
assert np.allclose(bbsoi.bounding_boxes[1].y1, 2.0)
assert np.allclose(bbsoi.bounding_boxes[1].x2, 3.0)
assert np.allclose(bbsoi.bounding_boxes[1].y2, 4.0)
assert bbsoi.shape == (40, 50, 3)
def test_from_xyxy_array_empty_array(self):
xyxy = np.zeros((0, 4), dtype=np.float32)
bbsoi = ia.BoundingBoxesOnImage.from_xyxy_array(xyxy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 0
assert bbsoi.shape == (40, 50, 3)
def test_from_point_soups__2d_array(self):
xy = np.float32([
[7, 3,
11, 5,
1, 7,
12, 19]
])
bbsoi = ia.BoundingBoxesOnImage.from_point_soups(
xy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 1
assert bbsoi.bounding_boxes[0].x1 == 1
assert bbsoi.bounding_boxes[0].y1 == 3
assert bbsoi.bounding_boxes[0].x2 == 12
assert bbsoi.bounding_boxes[0].y2 == 19
assert bbsoi.shape == (40, 50, 3)
def test_from_point_soups__3d_array(self):
xy = np.float32([
[
[7, 3],
[11, 5],
[1, 7],
[12, 19]
]
])
bbsoi = ia.BoundingBoxesOnImage.from_point_soups(
xy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 1
assert bbsoi.bounding_boxes[0].x1 == 1
assert bbsoi.bounding_boxes[0].y1 == 3
assert bbsoi.bounding_boxes[0].x2 == 12
assert bbsoi.bounding_boxes[0].y2 == 19
assert bbsoi.shape == (40, 50, 3)
def test_from_point_soups__2d_list(self):
xy = [
[7, 3,
11, 5,
1, 7,
12, 19]
]
bbsoi = ia.BoundingBoxesOnImage.from_point_soups(
xy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 1
assert bbsoi.bounding_boxes[0].x1 == 1
assert bbsoi.bounding_boxes[0].y1 == 3
assert bbsoi.bounding_boxes[0].x2 == 12
assert bbsoi.bounding_boxes[0].y2 == 19
assert bbsoi.shape == (40, 50, 3)
def test_from_point_soups__empty_array(self):
xy = np.zeros((0, 4), dtype=np.float32)
bbsoi = ia.BoundingBoxesOnImage.from_point_soups(
xy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 0
assert bbsoi.shape == (40, 50, 3)
def test_from_point_soups__empty_list(self):
xy = []
bbsoi = ia.BoundingBoxesOnImage.from_point_soups(
xy, shape=(40, 50, 3))
assert len(bbsoi.bounding_boxes) == 0
assert bbsoi.shape == (40, 50, 3)
def test_to_xyxy_array(self):
xyxy = np.float32([
[0.0, 0.0, 1.0, 1.0],
[1.0, 2.0, 3.0, 4.0]
])
bbsoi = ia.BoundingBoxesOnImage.from_xyxy_array(xyxy, shape=(40, 50, 3))
xyxy_out = bbsoi.to_xyxy_array()
assert np.allclose(xyxy, xyxy_out)
assert xyxy_out.dtype.name == "float32"
def test_to_xyxy_array_convert_to_int32(self):
xyxy = np.float32([
[0.0, 0.0, 1.0, 1.0],
[1.0, 2.0, 3.0, 4.0]
])
bbsoi = ia.BoundingBoxesOnImage.from_xyxy_array(xyxy, shape=(40, 50, 3))
xyxy_out = bbsoi.to_xyxy_array(dtype=np.int32)
assert np.allclose(xyxy.astype(np.int32), xyxy_out)
assert xyxy_out.dtype.name == "int32"
def test_to_xyxy_array_no_bbs_to_convert(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(40, 50, 3))
xyxy_out = bbsoi.to_xyxy_array(dtype=np.int32)
assert xyxy_out.shape == (0, 4)
def test_to_xy_array(self):
xyxy = np.float32([
[0.0, 0.0, 1.0, 1.0],
[1.0, 2.0, 3.0, 4.0]
])
bbsoi = ia.BoundingBoxesOnImage.from_xyxy_array(xyxy, shape=(40, 50, 3))
xy_out = bbsoi.to_xy_array()
expected = np.float32([
[0.0, 0.0],
[1.0, 1.0],
[1.0, 2.0],
[3.0, 4.0]
])
assert xy_out.shape == (4, 2)
assert np.allclose(xy_out, expected)
assert xy_out.dtype.name == "float32"
def test_to_xy_array__empty_instance(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(1, 2, 3))
xy_out = bbsoi.to_xy_array()
assert xy_out.shape == (0, 2)
assert xy_out.dtype.name == "float32"
def test_fill_from_xyxy_array___empty_array(self):
xyxy = np.zeros((0, 4), dtype=np.float32)
bbsoi = ia.BoundingBoxesOnImage([], shape=(2, 2, 3))
bbsoi = bbsoi.fill_from_xyxy_array_(xyxy)
assert len(bbsoi.bounding_boxes) == 0
def test_fill_from_xyxy_array___empty_list(self):
xyxy = []
bbsoi = ia.BoundingBoxesOnImage([], shape=(2, 2, 3))
bbsoi = bbsoi.fill_from_xyxy_array_(xyxy)
assert len(bbsoi.bounding_boxes) == 0
def test_fill_from_xyxy_array___array_with_two_coords(self):
xyxy = np.array(
[(100, 101, 102, 103),
(200, 201, 202, 203)], dtype=np.float32)
bbsoi = ia.BoundingBoxesOnImage(
[ia.BoundingBox(1, 2, 3, 4),
ia.BoundingBox(10, 20, 30, 40)],
shape=(2, 2, 3))
bbsoi = bbsoi.fill_from_xyxy_array_(xyxy)
assert len(bbsoi.bounding_boxes) == 2
assert bbsoi.bounding_boxes[0].x1 == 100
assert bbsoi.bounding_boxes[0].y1 == 101
assert bbsoi.bounding_boxes[0].x2 == 102
assert bbsoi.bounding_boxes[0].y2 == 103
assert bbsoi.bounding_boxes[1].x1 == 200
assert bbsoi.bounding_boxes[1].y1 == 201
assert bbsoi.bounding_boxes[1].x2 == 202
assert bbsoi.bounding_boxes[1].y2 == 203
def test_fill_from_xyxy_array___list_with_two_coords(self):
xyxy = [(100, 101, 102, 103),
(200, 201, 202, 203)]
bbsoi = ia.BoundingBoxesOnImage(
[ia.BoundingBox(1, 2, 3, 4),
ia.BoundingBox(10, 20, 30, 40)],
shape=(2, 2, 3))
bbsoi = bbsoi.fill_from_xyxy_array_(xyxy)
assert len(bbsoi.bounding_boxes) == 2
assert bbsoi.bounding_boxes[0].x1 == 100
assert bbsoi.bounding_boxes[0].y1 == 101
assert bbsoi.bounding_boxes[0].x2 == 102
assert bbsoi.bounding_boxes[0].y2 == 103
assert bbsoi.bounding_boxes[1].x1 == 200
assert bbsoi.bounding_boxes[1].y1 == 201
assert bbsoi.bounding_boxes[1].x2 == 202
assert bbsoi.bounding_boxes[1].y2 == 203
def test_fill_from_xy_array___empty_array(self):
xy = np.zeros((0, 2), dtype=np.float32)
bbsoi = ia.BoundingBoxesOnImage([], shape=(2, 2, 3))
bbsoi = bbsoi.fill_from_xy_array_(xy)
assert len(bbsoi.bounding_boxes) == 0
def test_fill_from_xy_array___empty_list(self):
xy = []
bbsoi = ia.BoundingBoxesOnImage([], shape=(2, 2, 3))
bbsoi = bbsoi.fill_from_xy_array_(xy)
assert len(bbsoi.bounding_boxes) == 0
def test_fill_from_xy_array___array_with_two_coords(self):
xy = np.array(
[(100, 101),
(102, 103),
(200, 201),
(202, 203)], dtype=np.float32)
bbsoi = ia.BoundingBoxesOnImage(
[ia.BoundingBox(1, 2, 3, 4),
ia.BoundingBox(10, 20, 30, 40)],
shape=(2, 2, 3))
bbsoi = bbsoi.fill_from_xy_array_(xy)
assert len(bbsoi.bounding_boxes) == 2
assert bbsoi.bounding_boxes[0].x1 == 100
assert bbsoi.bounding_boxes[0].y1 == 101
assert bbsoi.bounding_boxes[0].x2 == 102
assert bbsoi.bounding_boxes[0].y2 == 103
assert bbsoi.bounding_boxes[1].x1 == 200
assert bbsoi.bounding_boxes[1].y1 == 201
assert bbsoi.bounding_boxes[1].x2 == 202
assert bbsoi.bounding_boxes[1].y2 == 203
def test_fill_from_xy_array___list_with_two_coords(self):
xy = [(100, 101),
(102, 103),
(200, 201),
(202, 203)]
bbsoi = ia.BoundingBoxesOnImage(
[ia.BoundingBox(1, 2, 3, 4),
ia.BoundingBox(10, 20, 30, 40)],
shape=(2, 2, 3))
bbsoi = bbsoi.fill_from_xy_array_(xy)
assert len(bbsoi.bounding_boxes) == 2
assert bbsoi.bounding_boxes[0].x1 == 100
assert bbsoi.bounding_boxes[0].y1 == 101
assert bbsoi.bounding_boxes[0].x2 == 102
assert bbsoi.bounding_boxes[0].y2 == 103
assert bbsoi.bounding_boxes[1].x1 == 200
assert bbsoi.bounding_boxes[1].y1 == 201
assert bbsoi.bounding_boxes[1].x2 == 202
assert bbsoi.bounding_boxes[1].y2 == 203
def test_draw_on_image(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=45)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
image = np.zeros(bbsoi.shape, dtype=np.uint8)
image_drawn = bbsoi.draw_on_image(
image,
color=[0, 255, 0], alpha=1.0, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_drawn[10-1, 20-1, :] == [0, 0, 0])
assert np.all(image_drawn[10-1, 20-0, :] == [0, 0, 0])
assert np.all(image_drawn[10-0, 20-1, :] == [0, 0, 0])
assert np.all(image_drawn[10-0, 20-0, :] == [0, 255, 0])
assert np.all(image_drawn[10+1, 20+1, :] == [0, 0, 0])
assert np.all(image_drawn[30-1, 40-1, :] == [0, 0, 0])
assert np.all(image_drawn[30+1, 40-0, :] == [0, 0, 0])
assert np.all(image_drawn[30+0, 40+1, :] == [0, 0, 0])
assert np.all(image_drawn[30+0, 40+0, :] == [0, 255, 0])
assert np.all(image_drawn[30+1, 40+1, :] == [0, 0, 0])
assert np.all(image_drawn[15-1, 25-1, :] == [0, 0, 0])
assert np.all(image_drawn[15-1, 25-0, :] == [0, 0, 0])
assert np.all(image_drawn[15-0, 25-1, :] == [0, 0, 0])
assert np.all(image_drawn[15-0, 25-0, :] == [0, 255, 0])
assert np.all(image_drawn[15+1, 25+1, :] == [0, 0, 0])
assert np.all(image_drawn[35-1, 45-1, :] == [0, 0, 0])
assert np.all(image_drawn[35+1, 45+0, :] == [0, 0, 0])
assert np.all(image_drawn[35+0, 45+1, :] == [0, 0, 0])
assert np.all(image_drawn[35+0, 45+0, :] == [0, 255, 0])
assert np.all(image_drawn[35+1, 45+1, :] == [0, 0, 0])
def test_remove_out_of_image_(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_removed = bbsoi.remove_out_of_image_(fully=True, partly=True)
assert len(bbsoi_removed.bounding_boxes) == 1
assert bbsoi_removed.bounding_boxes[0] == bb1
assert bbsoi_removed is bbsoi
def test_remove_out_of_image(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_removed = bbsoi.remove_out_of_image(fully=True, partly=True)
assert len(bbsoi_removed.bounding_boxes) == 1
assert bbsoi_removed.bounding_boxes[0] == bb1
assert bbsoi_removed is not bbsoi
def test_remove_out_of_image_fraction_(self):
item1 = ia.BoundingBox(y1=1, x1=5, y2=6, x2=9)
item2 = ia.BoundingBox(y1=1, x1=5, y2=6, x2=15)
item3 = ia.BoundingBox(y1=1, x1=15, y2=6, x2=25)
cbaoi = ia.BoundingBoxesOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_reduced = cbaoi.remove_out_of_image_fraction_(0.6)
assert len(cbaoi_reduced.items) == 2
assert cbaoi_reduced.items == [item1, item2]
assert cbaoi_reduced is cbaoi
def test_remove_out_of_image_fraction(self):
item1 = ia.BoundingBox(y1=1, x1=5, y2=6, x2=9)
item2 = ia.BoundingBox(y1=1, x1=5, y2=6, x2=15)
item3 = ia.BoundingBox(y1=1, x1=15, y2=6, x2=25)
cbaoi = ia.BoundingBoxesOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_reduced = cbaoi.remove_out_of_image_fraction(0.6)
assert len(cbaoi_reduced.items) == 2
assert cbaoi_reduced.items == [item1, item2]
assert cbaoi_reduced is not cbaoi
def test_shift_(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_shifted = bbsoi.shift_(y=2)
assert len(bbsoi_shifted.bounding_boxes) == 2
assert bbsoi_shifted.bounding_boxes[0].y1 == 10 + 2
assert bbsoi_shifted.bounding_boxes[0].x1 == 20
assert bbsoi_shifted.bounding_boxes[0].y2 == 30 + 2
assert bbsoi_shifted.bounding_boxes[0].x2 == 40
assert bbsoi_shifted.bounding_boxes[1].y1 == 15 + 2
assert bbsoi_shifted.bounding_boxes[1].x1 == 25
assert bbsoi_shifted.bounding_boxes[1].y2 == 35 + 2
assert bbsoi_shifted.bounding_boxes[1].x2 == 51
assert bbsoi_shifted is bbsoi
def test_shift(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_shifted = bbsoi.shift(y=2)
assert len(bbsoi_shifted.bounding_boxes) == 2
assert bbsoi_shifted.bounding_boxes[0].y1 == 10 + 2
assert bbsoi_shifted.bounding_boxes[0].x1 == 20
assert bbsoi_shifted.bounding_boxes[0].y2 == 30 + 2
assert bbsoi_shifted.bounding_boxes[0].x2 == 40
assert bbsoi_shifted.bounding_boxes[1].y1 == 15 + 2
assert bbsoi_shifted.bounding_boxes[1].x1 == 25
assert bbsoi_shifted.bounding_boxes[1].y2 == 35 + 2
assert bbsoi_shifted.bounding_boxes[1].x2 == 51
assert bbsoi_shifted is not bbsoi
def test_shift__deprecated_args(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
bbsoi_shifted = bbsoi.shift(right=1)
assert len(bbsoi_shifted.bounding_boxes) == 2
assert bbsoi_shifted.bounding_boxes[0].y1 == 10
assert bbsoi_shifted.bounding_boxes[0].x1 == 20 - 1
assert bbsoi_shifted.bounding_boxes[0].y2 == 30
assert bbsoi_shifted.bounding_boxes[0].x2 == 40 - 1
assert bbsoi_shifted.bounding_boxes[1].y1 == 15
assert bbsoi_shifted.bounding_boxes[1].x1 == 25 - 1
assert bbsoi_shifted.bounding_boxes[1].y2 == 35
assert bbsoi_shifted.bounding_boxes[1].x2 == 51 - 1
assert bbsoi_shifted is not bbsoi
assert (
"These are deprecated. Use `x` and `y` instead."
in str(caught_warnings[-1].message)
)
def test_to_keypoints_on_image(self):
bbsoi = ia.BoundingBoxesOnImage(
[ia.BoundingBox(0, 1, 2, 3),
ia.BoundingBox(10, 20, 30, 40)],
shape=(1, 2, 3))
kpsoi = bbsoi.to_keypoints_on_image()
assert len(kpsoi.keypoints) == 2*4
assert kpsoi.keypoints[0].x == 0
assert kpsoi.keypoints[0].y == 1
assert kpsoi.keypoints[1].x == 2
assert kpsoi.keypoints[1].y == 1
assert kpsoi.keypoints[2].x == 2
assert kpsoi.keypoints[2].y == 3
assert kpsoi.keypoints[3].x == 0
assert kpsoi.keypoints[3].y == 3
assert kpsoi.keypoints[4].x == 10
assert kpsoi.keypoints[4].y == 20
assert kpsoi.keypoints[5].x == 30
assert kpsoi.keypoints[5].y == 20
assert kpsoi.keypoints[6].x == 30
assert kpsoi.keypoints[6].y == 40
assert kpsoi.keypoints[7].x == 10
assert kpsoi.keypoints[7].y == 40
def test_to_keypoints_on_image__empty_instance(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(1, 2, 3))
kpsoi = bbsoi.to_keypoints_on_image()
assert len(kpsoi.keypoints) == 0
def test_invert_to_keypoints_on_image_(self):
bbsoi = ia.BoundingBoxesOnImage(
[ia.BoundingBox(0, 1, 2, 3),
ia.BoundingBox(10, 20, 30, 40)],
shape=(1, 2, 3))
kpsoi = ia.KeypointsOnImage(
[ia.Keypoint(100, 101), ia.Keypoint(102, 103),
ia.Keypoint(104, 105), ia.Keypoint(106, 107),
ia.Keypoint(110, 120), ia.Keypoint(130, 140),
ia.Keypoint(150, 160), ia.Keypoint(170, 180)],
shape=(10, 20, 30))
bbsoi_inv = bbsoi.invert_to_keypoints_on_image_(kpsoi)
assert len(bbsoi_inv.bounding_boxes) == 2
assert bbsoi_inv.shape == (10, 20, 30)
assert bbsoi_inv.bounding_boxes[0].x1 == 100
assert bbsoi_inv.bounding_boxes[0].y1 == 101
assert bbsoi_inv.bounding_boxes[0].x2 == 106
assert bbsoi_inv.bounding_boxes[0].y2 == 107
assert bbsoi_inv.bounding_boxes[1].x1 == 110
assert bbsoi_inv.bounding_boxes[1].y1 == 120
assert bbsoi_inv.bounding_boxes[1].x2 == 170
assert bbsoi_inv.bounding_boxes[1].y2 == 180
def test_invert_to_keypoints_on_image___empty_instance(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(1, 2, 3))
kpsoi = ia.KeypointsOnImage([], shape=(10, 20, 30))
bbsoi_inv = bbsoi.invert_to_keypoints_on_image_(kpsoi)
assert len(bbsoi_inv.bounding_boxes) == 0
assert bbsoi_inv.shape == (10, 20, 30)
def test_to_polygons_on_image(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
psoi = bbsoi.to_polygons_on_image()
assert psoi.shape == (40, 50, 3)
assert len(psoi.items) == 2
assert psoi.items[0].coords_almost_equals([
(20, 10),
(40, 10),
(40, 30),
(20, 30)
])
assert psoi.items[1].coords_almost_equals([
(25, 15),
(51, 15),
(51, 35),
(25, 35)
])
def test_to_polygons_on_image__empty_instance(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(40, 50, 3))
psoi = bbsoi.to_polygons_on_image()
assert psoi.shape == (40, 50, 3)
assert len(psoi.items) == 0
def test_copy(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_copy = bbsoi.copy()
assert len(bbsoi.bounding_boxes) == 2
assert bbsoi_copy.bounding_boxes[0].y1 == 10
assert bbsoi_copy.bounding_boxes[0].x1 == 20
assert bbsoi_copy.bounding_boxes[0].y2 == 30
assert bbsoi_copy.bounding_boxes[0].x2 == 40
assert bbsoi_copy.bounding_boxes[1].y1 == 15
assert bbsoi_copy.bounding_boxes[1].x1 == 25
assert bbsoi_copy.bounding_boxes[1].y2 == 35
assert bbsoi_copy.bounding_boxes[1].x2 == 51
bbsoi_copy.bounding_boxes[0].y1 = 0
assert bbsoi.bounding_boxes[0].y1 == 0
assert bbsoi_copy.bounding_boxes[0].y1 == 0
def test_copy_bounding_boxes_set(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bb3 = ia.BoundingBox(y1=15+1, x1=25+1, y2=35+1, x2=51+1)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_copy = bbsoi.copy(bounding_boxes=[bb3])
assert bbsoi_copy is not bbsoi
assert bbsoi_copy.shape == (40, 50, 3)
assert bbsoi_copy.bounding_boxes == [bb3]
def test_copy_shape_set(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_copy = bbsoi.copy(shape=(40+1, 50+1, 3))
assert bbsoi_copy is not bbsoi
assert bbsoi_copy.shape == (40+1, 50+1, 3)
assert bbsoi_copy.bounding_boxes == [bb1, bb2]
def test_deepcopy(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_copy = bbsoi.deepcopy()
assert len(bbsoi.bounding_boxes) == 2
assert bbsoi_copy.bounding_boxes[0].y1 == 10
assert bbsoi_copy.bounding_boxes[0].x1 == 20
assert bbsoi_copy.bounding_boxes[0].y2 == 30
assert bbsoi_copy.bounding_boxes[0].x2 == 40
assert bbsoi_copy.bounding_boxes[1].y1 == 15
assert bbsoi_copy.bounding_boxes[1].x1 == 25
assert bbsoi_copy.bounding_boxes[1].y2 == 35
assert bbsoi_copy.bounding_boxes[1].x2 == 51
bbsoi_copy.bounding_boxes[0].y1 = 0
assert bbsoi.bounding_boxes[0].y1 == 10
assert bbsoi_copy.bounding_boxes[0].y1 == 0
def test_deepcopy_bounding_boxes_set(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bb3 = ia.BoundingBox(y1=15+1, x1=25+1, y2=35+1, x2=51+1)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_copy = bbsoi.deepcopy(bounding_boxes=[bb3])
assert bbsoi_copy is not bbsoi
assert bbsoi_copy.shape == (40, 50, 3)
assert bbsoi_copy.bounding_boxes == [bb3]
def test_deepcopy_shape_set(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bbsoi_copy = bbsoi.deepcopy(shape=(40+1, 50+1, 3))
assert bbsoi_copy is not bbsoi
assert bbsoi_copy.shape == (40+1, 50+1, 3)
assert len(bbsoi_copy.bounding_boxes) == 2
assert bbsoi_copy.bounding_boxes[0].coords_almost_equals(bb1)
assert bbsoi_copy.bounding_boxes[1].coords_almost_equals(bb2)
def test___getitem__(self):
cbas = [
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=2, y1=3, x2=4, y2=5)
]
cbasoi = ia.BoundingBoxesOnImage(cbas, shape=(3, 4, 3))
assert cbasoi[0] is cbas[0]
assert cbasoi[1] is cbas[1]
assert cbasoi[0:2] == cbas
def test___iter__(self):
cbas = [ia.BoundingBox(x1=0, y1=0, x2=2, y2=2),
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]
cbasoi = ia.BoundingBoxesOnImage(cbas, shape=(40, 50, 3))
for i, cba in enumerate(cbasoi):
assert cba is cbas[i]
def test___iter___empty(self):
cbasoi = ia.BoundingBoxesOnImage([], shape=(40, 50, 3))
i = 0
for _cba in cbasoi:
i += 1
assert i == 0
def test___len__(self):
cbas = [ia.BoundingBox(x1=0, y1=0, x2=2, y2=2),
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]
cbasoi = ia.BoundingBoxesOnImage(cbas, shape=(40, 50, 3))
assert len(cbasoi) == 2
def test_string_conversion(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40)
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51)
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bb1_expected = "BoundingBox(x1=20.0000, y1=10.0000, " \
"x2=40.0000, y2=30.0000, label=None)"
bb2_expected = "BoundingBox(x1=25.0000, y1=15.0000, " \
"x2=51.0000, y2=35.0000, label=None)"
expected = "BoundingBoxesOnImage([%s, %s], shape=(40, 50, 3))" % (
bb1_expected, bb2_expected)
assert (
bbsoi.__repr__()
== bbsoi.__str__()
== expected
)
def test_string_conversion_labels_are_not_none(self):
bb1 = ia.BoundingBox(y1=10, x1=20, y2=30, x2=40, label="foo")
bb2 = ia.BoundingBox(y1=15, x1=25, y2=35, x2=51, label="bar")
bbsoi = ia.BoundingBoxesOnImage([bb1, bb2], shape=(40, 50, 3))
bb1_expected = "BoundingBox(x1=20.0000, y1=10.0000, " \
"x2=40.0000, y2=30.0000, label=foo)"
bb2_expected = "BoundingBox(x1=25.0000, y1=15.0000, " \
"x2=51.0000, y2=35.0000, label=bar)"
expected = "BoundingBoxesOnImage([%s, %s], shape=(40, 50, 3))" % (
bb1_expected, bb2_expected)
assert (
bbsoi.__repr__()
== bbsoi.__str__()
== expected
)
class Test_LabelOnImageDrawer(unittest.TestCase):
def test_draw_on_image_(self):
height = 30
image = np.full((100, 50, 3), 100, dtype=np.uint8)
bb = ia.BoundingBox(x1=5, x2=20, y1=50, y2=60)
drawer = _LabelOnImageDrawer(color_text=(255, 255, 255),
color_bg=(0, 0, 0),
height=height)
image_drawn = drawer.draw_on_image_(np.copy(image), bb)
frac_colors_as_expected = np.average(
np.logical_or(image_drawn[50-1-height:50-1, 5-1:20+1, :] == 0,
image_drawn[50-1-height:50-1, 5-1:20+1, :] == 255)
)
assert np.all(image_drawn[:50-1-height, :, :] == 100)
assert np.all(image_drawn[50-1:, :, :] == 100)
assert np.all(image_drawn[:, :5-1, :] == 100)
assert np.all(image_drawn[:, 20+1:, :] == 100)
assert frac_colors_as_expected > 0.75
def test_draw_on_image(self):
image = np.full((20, 30, 3), 100, dtype=np.uint8)
bb = ia.BoundingBox(x1=1, x2=6, y1=2, y2=10)
drawer = _LabelOnImageDrawer(color_text=(255, 255, 255),
color_bg=(0, 0, 0))
image_drawn_inplace = drawer.draw_on_image_(np.copy(image), bb)
image_drawn = drawer.draw_on_image_(image, bb)
assert np.array_equal(image_drawn, image_drawn_inplace)
def test__do_raise_if_out_of_image__bb_is_fully_inside(self):
drawer = _LabelOnImageDrawer(raise_if_out_of_image=True)
image = np.zeros((20, 30, 3), dtype=np.uint8)
bb = ia.BoundingBox(x1=1, x2=6, y1=2, y2=10)
# assert no exception
drawer._do_raise_if_out_of_image(image, bb)
def test__do_raise_if_out_of_image__bb_is_partially_outside(self):
drawer = _LabelOnImageDrawer(raise_if_out_of_image=True)
image = np.zeros((20, 30, 3), dtype=np.uint8)
bb = ia.BoundingBox(x1=30-5, x2=30+1, y1=2, y2=10)
# assert no exception
drawer._do_raise_if_out_of_image(image, bb)
def test__do_raise_if_out_of_image__bb_is_fully_outside(self):
drawer = _LabelOnImageDrawer(raise_if_out_of_image=True)
image = np.zeros((20, 30, 3), dtype=np.uint8)
bb = ia.BoundingBox(x1=30+1, x2=30+6, y1=2, y2=10)
with self.assertRaises(Exception):
drawer._do_raise_if_out_of_image(image, bb)
def test__preprocess_colors__only_main_color_set(self):
drawer = _LabelOnImageDrawer(color=(0, 255, 0))
color_text, color_bg = drawer._preprocess_colors()
assert np.array_equal(color_text, [0, 0, 0])
assert np.array_equal(color_bg, [0, 255, 0])
def test__preprocess_colors__subcolors_set(self):
drawer = _LabelOnImageDrawer(color_text=(128, 129, 130),
color_bg=(131, 132, 133))
color_text, color_bg = drawer._preprocess_colors()
assert np.array_equal(color_text, [128, 129, 130])
assert np.array_equal(color_bg, [131, 132, 133])
def test__preprocess_colors__text_not_set_must_be_black(self):
drawer = _LabelOnImageDrawer(color=(255, 255, 255),
color_bg=(255, 255, 255))
color_text, color_bg = drawer._preprocess_colors()
assert np.array_equal(color_text, [0, 0, 0])
assert np.array_equal(color_bg, [255, 255, 255])
def test__compute_bg_corner_coords__standard_bb(self):
height = 30
for size in [1, 2]:
with self.subTest(size=size):
drawer = _LabelOnImageDrawer(size=size, height=height)
bb = ia.BoundingBox(x1=10, x2=30, y1=60, y2=90)
image = np.zeros((100, 200, 3), dtype=np.uint8)
x1, y1, x2, y2 = drawer._compute_bg_corner_coords(image, bb)
assert np.isclose(x1, max(bb.x1 - size + 1, 0))
assert np.isclose(y1, max(bb.y1 - 1 - height, 0))
assert np.isclose(x2, min(bb.x2 + size, image.shape[1]-1))
assert np.isclose(y2, min(bb.y1 - 1, image.shape[0]-1))
def test__compute_bg_corner_coords__zero_sized_bb(self):
height = 30
size = 1
drawer = _LabelOnImageDrawer(size=1, height=height)
bb = ia.BoundingBox(x1=10, x2=10, y1=60, y2=90)
image = np.zeros((100, 200, 3), dtype=np.uint8)
x1, y1, x2, y2 = drawer._compute_bg_corner_coords(image, bb)
assert np.isclose(x1, bb.x1 - size + 1)
assert np.isclose(y1, bb.y1 - 1 - height)
assert np.isclose(x2, bb.x2 + size)
assert np.isclose(y2, bb.y1 - 1)
def test__draw_label_arr__label_is_none(self):
drawer = _LabelOnImageDrawer()
height = 50
width = 100
nb_channels = 3
color_text = np.uint8([0, 255, 0])
color_bg = np.uint8([255, 0, 0])
size_text = 20
label_arr = drawer._draw_label_arr(None, height, width, nb_channels,
np.uint8,
color_text, color_bg, size_text)
frac_textcolor = np.average(
np.min(label_arr == color_text.reshape((1, 1, -1)), axis=-1)
)
frac_bgcolor = np.average(
np.min(label_arr == color_bg.reshape((1, 1, -1)), axis=-1)
)
assert label_arr.dtype.name == "uint8"
assert label_arr.shape == (height, width, nb_channels)
assert frac_textcolor > 0.02
assert frac_bgcolor > 0.8
# not all pixels of the text might be drawn with exactly the text
# color
assert frac_textcolor + frac_bgcolor > 0.75
def test__draw_label_arr__label_is_str(self):
drawer = _LabelOnImageDrawer()
height = 50
width = 100
nb_channels = 3
color_text = np.uint8([0, 255, 0])
color_bg = np.uint8([255, 0, 0])
size_text = 20
label_arr = drawer._draw_label_arr("Fooo", height, width, nb_channels,
np.uint8,
color_text, color_bg, size_text)
frac_textcolor = np.average(
np.min(label_arr == color_text.reshape((1, 1, -1)), axis=-1)
)
frac_bgcolor = np.average(
np.min(label_arr == color_bg.reshape((1, 1, -1)), axis=-1)
)
assert label_arr.dtype.name == "uint8"
assert label_arr.shape == (height, width, nb_channels)
assert frac_textcolor > 0.02
assert frac_bgcolor > 0.8
# not all pixels of the text might be drawn with exactly the text
# color
assert frac_textcolor + frac_bgcolor > 0.75
def test__blend_label_arr__alpha_is_1(self):
drawer = _LabelOnImageDrawer(alpha=1)
image = np.full((50, 60, 3), 100, dtype=np.uint8)
label_arr = np.full((10, 20, 3), 200, dtype=np.uint8)
x1 = 15
x2 = 15 + 20
y1 = 10
y2 = 10 + 10
image_blend = drawer._blend_label_arr_with_image_(image, label_arr,
x1, y1, x2, y2)
assert np.all(image_blend[:, :15, :] == 100)
assert np.all(image_blend[:, 15+20:, :] == 100)
assert np.all(image_blend[:10, :, :] == 100)
assert np.all(image_blend[10+10:, :, :] == 100)
assert np.all(image_blend[10:10+10, 15:15+20, :] == 200)
def test__blend_label_arr__alpha_is_075(self):
drawer = _LabelOnImageDrawer(alpha=0.75)
image = np.full((50, 60, 3), 100, dtype=np.uint8)
label_arr = np.full((10, 20, 3), 200, dtype=np.uint8)
x1 = 15
x2 = 15 + 20
y1 = 10
y2 = 10 + 10
image_blend = drawer._blend_label_arr_with_image_(image, label_arr,
x1, y1, x2, y2)
assert np.all(image_blend[:, :15, :] == 100)
assert np.all(image_blend[:, 15+20:, :] == 100)
assert np.all(image_blend[:10, :, :] == 100)
assert np.all(image_blend[10+10:, :, :] == 100)
assert np.all(image_blend[10:10+10, 15:15+20, :] == 100+75)
================================================
FILE: test/augmentables/test_heatmaps.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
import imgaug as ia
# TODO add tests for:
# hooks is_activated
# hooks is_propagating
# hooks preprocess
# hooks postprocess
# HeatmapsOnImage.__init__()
# HeatmapsOnImage.get_arr()
# HeatmapsOnImage.to_uint8()
# HeatmapsOnImage.from_0to1()
# HeatmapsOnImage.copy()
# HeatmapsOnImage.deepcopy()
class TestHeatmapsOnImage_draw(unittest.TestCase):
def test_basic_functionality(self):
heatmaps_arr = np.float32([
[0.5, 0.0, 0.0, 0.5],
[0.0, 1.0, 1.0, 0.0],
[0.0, 1.0, 1.0, 0.0],
[0.5, 0.0, 0.0, 0.5],
])
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(4, 4, 3))
heatmaps_drawn = heatmaps.draw()[0]
assert heatmaps_drawn.shape == (4, 4, 3)
v1 = heatmaps_drawn[0, 1]
v2 = heatmaps_drawn[0, 0]
v3 = heatmaps_drawn[1, 1]
v1_coords = [(0, 1), (0, 2), (1, 0), (1, 3), (2, 0), (2, 3), (3, 1),
(3, 2)]
v2_coords = [(0, 0), (0, 3), (3, 0), (3, 3)]
v3_coords = [(1, 1), (1, 2), (2, 1), (2, 2)]
for y, x in v1_coords:
assert np.allclose(heatmaps_drawn[y, x], v1)
for y, x in v2_coords:
assert np.allclose(heatmaps_drawn[y, x], v2)
for y, x in v3_coords:
assert np.allclose(heatmaps_drawn[y, x], v3)
def test_use_size_arg_with_different_shape_than_heatmap_arr_shape(self):
# size differs from heatmap array size
heatmaps_arr = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(2, 2, 3))
heatmaps_drawn = heatmaps.draw(size=(4, 4))[0]
assert heatmaps_drawn.shape == (4, 4, 3)
v1 = heatmaps_drawn[0, 0]
v2 = heatmaps_drawn[0, -1]
for y in sm.xrange(4):
for x in sm.xrange(2):
assert np.allclose(heatmaps_drawn[y, x], v1)
for y in sm.xrange(4):
for x in sm.xrange(2, 4):
assert np.allclose(heatmaps_drawn[y, x], v2)
# TODO test other cmaps
class TestHeatmapsOnImage_draw_on_image(unittest.TestCase):
@property
def heatmaps(self):
heatmaps_arr = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
return ia.HeatmapsOnImage(heatmaps_arr, shape=(2, 2, 3))
def test_cmap_is_none(self):
heatmaps = self.heatmaps
image = np.uint8([
[0, 0, 0, 255],
[0, 0, 0, 255],
[0, 0, 0, 255],
[0, 0, 0, 255]
])
image = np.tile(image[..., np.newaxis], (1, 1, 3))
heatmaps_drawn = heatmaps.draw_on_image(image, alpha=0.5, cmap=None)[0]
assert heatmaps_drawn.shape == (4, 4, 3)
assert np.all(heatmaps_drawn[0:4, 0:2, :] == 0)
assert (
np.all(heatmaps_drawn[0:4, 2:3, :] == 128)
or np.all(heatmaps_drawn[0:4, 2:3, :] == 127))
assert (
np.all(heatmaps_drawn[0:4, 3:4, :] == 255)
or np.all(heatmaps_drawn[0:4, 3:4, :] == 254))
def test_cmap_is_none_and_resize_is_image(self):
heatmaps = self.heatmaps
image = np.uint8([
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]
])
image = np.tile(image[..., np.newaxis], (1, 1, 3))
heatmaps_drawn = heatmaps.draw_on_image(
image, alpha=0.5, resize="image", cmap=None)[0]
assert heatmaps_drawn.shape == (2, 2, 3)
assert np.all(heatmaps_drawn[0:2, 0, :] == 0)
assert (
np.all(heatmaps_drawn[0:2, 1, :] == 128)
or np.all(heatmaps_drawn[0:2, 1, :] == 127))
class TestHeatmapsOnImage_invert(unittest.TestCase):
@property
def heatmaps_arr(self):
return np.float32([
[0.0, 5.0, 10.0],
[-1.0, -2.0, 7.5]
])
@property
def expected_arr(self):
return np.float32([
[8.0, 3.0, -2.0],
[9.0, 10.0, 0.5]
])
def test_with_2d_input_array(self):
# (H, W)
heatmaps_arr = self.heatmaps_arr
expected = self.expected_arr
heatmaps = ia.HeatmapsOnImage(heatmaps_arr,
shape=(2, 3),
min_value=-2.0,
max_value=10.0)
assert np.allclose(heatmaps.get_arr(), heatmaps_arr)
assert np.allclose(heatmaps.invert().get_arr(), expected)
def test_with_3d_input_array(self):
# (H, W, 1)
heatmaps_arr = self.heatmaps_arr
expected = self.expected_arr
heatmaps = ia.HeatmapsOnImage(heatmaps_arr[..., np.newaxis],
shape=(2, 3),
min_value=-2.0,
max_value=10.0)
assert np.allclose(heatmaps.get_arr(),
heatmaps_arr[..., np.newaxis])
assert np.allclose(heatmaps.invert().get_arr(),
expected[..., np.newaxis])
class TestHeatmapsOnImage_pad(unittest.TestCase):
@property
def heatmaps(self):
heatmaps_arr = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
return ia.HeatmapsOnImage(heatmaps_arr, shape=(2, 2, 3))
def test_defaults(self):
heatmaps = self.heatmaps
heatmaps_padded = heatmaps.pad(top=1, right=2, bottom=3, left=4)
assert heatmaps_padded.arr_0to1.shape == (2+(1+3), 2+(4+2), 1)
assert np.allclose(
heatmaps_padded.arr_0to1[:, :, 0],
np.float32([
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
])
)
def test_mode_constant_with_cval_050(self):
heatmaps = self.heatmaps
heatmaps_padded = heatmaps.pad(top=1, right=2, bottom=3, left=4,
cval=0.5)
assert heatmaps_padded.arr_0to1.shape == (2+(1+3), 2+(4+2), 1)
assert np.allclose(
heatmaps_padded.arr_0to1[:, :, 0],
np.float32([
[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[0.5, 0.5, 0.5, 0.5, 0.0, 1.0, 0.5, 0.5],
[0.5, 0.5, 0.5, 0.5, 0.0, 1.0, 0.5, 0.5],
[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5],
[0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
])
)
def test_mode_edge(self):
heatmaps = self.heatmaps
heatmaps_padded = heatmaps.pad(top=1, right=2, bottom=3, left=4,
mode="edge")
assert heatmaps_padded.arr_0to1.shape == (2+(1+3), 2+(4+2), 1)
assert np.allclose(
heatmaps_padded.arr_0to1[:, :, 0],
np.float32([
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0],
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0]
])
)
class TestHeatmapsOnImage_pad_to_aspect_ratio(unittest.TestCase):
@property
def heatmaps(self):
heatmaps_arr = np.float32([
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0]
])
return ia.HeatmapsOnImage(heatmaps_arr, shape=(2, 2, 3))
def test_square_ratio_with_default_mode_and_cval(self):
heatmaps = self.heatmaps
heatmaps_padded = heatmaps.pad_to_aspect_ratio(1.0)
assert heatmaps_padded.arr_0to1.shape == (3, 3, 1)
assert np.allclose(
heatmaps_padded.arr_0to1[:, :, 0],
np.float32([
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 0.0]
])
)
def test_square_ratio_with_cval_050(self):
heatmaps = self.heatmaps
heatmaps_padded = heatmaps.pad_to_aspect_ratio(1.0, cval=0.5)
assert heatmaps_padded.arr_0to1.shape == (3, 3, 1)
assert np.allclose(
heatmaps_padded.arr_0to1[:, :, 0],
np.float32([
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.5, 0.5, 0.5]
])
)
def test_square_ratio_with_edge_mode(self):
heatmaps = self.heatmaps
heatmaps_padded = heatmaps.pad_to_aspect_ratio(1.0, mode="edge")
assert heatmaps_padded.arr_0to1.shape == (3, 3, 1)
assert np.allclose(
heatmaps_padded.arr_0to1[:, :, 0],
np.float32([
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0]
])
)
def test_wider_than_high_ratio_with_cval_010(self):
heatmaps = self.heatmaps
heatmaps_padded = heatmaps.pad_to_aspect_ratio(2.0, cval=0.1)
assert heatmaps_padded.arr_0to1.shape == (2, 4, 1)
assert np.allclose(
heatmaps_padded.arr_0to1[:, :, 0],
np.float32([
[0.0, 0.0, 1.0, 0.1],
[0.0, 0.0, 1.0, 0.1]
])
)
def test_higher_than_wide_ratio_with_cval_010(self):
heatmaps = self.heatmaps
heatmaps_padded = heatmaps.pad_to_aspect_ratio(0.25, cval=0.1)
assert heatmaps_padded.arr_0to1.shape == (12, 3, 1)
assert np.allclose(
heatmaps_padded.arr_0to1[:, :, 0],
np.float32([
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1],
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1],
[0.1, 0.1, 0.1]
])
)
class TestHeatmapsOnImage_avg_pool(unittest.TestCase):
def test_with_kernel_size_2(self):
heatmaps_arr = np.float32([
[0.0, 0.0, 0.5, 1.0],
[0.0, 0.0, 0.5, 1.0],
[0.0, 0.0, 0.5, 1.0],
[0.0, 0.0, 0.5, 1.0]
])
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(4, 4, 3))
heatmaps_pooled = heatmaps.avg_pool(2)
assert heatmaps_pooled.arr_0to1.shape == (2, 2, 1)
assert np.allclose(
heatmaps_pooled.arr_0to1[:, :, 0],
np.float32([[0.0, 0.75],
[0.0, 0.75]])
)
class TestHeatmapsOnImage_max_pool(unittest.TestCase):
def test_with_kernel_size_2(self):
heatmaps_arr = np.float32([
[0.0, 0.0, 0.5, 1.0],
[0.0, 0.0, 0.5, 1.0],
[0.0, 0.0, 0.5, 1.0],
[0.0, 0.0, 0.5, 1.0]
])
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(4, 4, 3))
heatmaps_pooled = heatmaps.max_pool(2)
assert heatmaps_pooled.arr_0to1.shape == (2, 2, 1)
assert np.allclose(
heatmaps_pooled.arr_0to1[:, :, 0],
np.float32([[0.0, 1.0],
[0.0, 1.0]])
)
class TestHeatmapsOnImage_resize(unittest.TestCase):
def test_resize_to_exact_shape(self):
heatmaps_arr = np.float32([
[0.0, 1.0]
])
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(4, 4, 3))
heatmaps_scaled = heatmaps.resize((4, 4), interpolation="nearest")
assert heatmaps_scaled.arr_0to1.shape == (4, 4, 1)
assert heatmaps_scaled.arr_0to1.dtype.name == "float32"
assert np.allclose(
heatmaps_scaled.arr_0to1[:, :, 0],
np.float32([
[0.0, 0.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0]
])
)
def test_resize_to_twice_the_size(self):
heatmaps_arr = np.float32([
[0.0, 1.0]
])
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(4, 4, 3))
heatmaps_scaled = heatmaps.resize(2.0, interpolation="nearest")
assert heatmaps_scaled.arr_0to1.shape == (2, 4, 1)
assert heatmaps_scaled.arr_0to1.dtype.name == "float32"
assert np.allclose(
heatmaps_scaled.arr_0to1[:, :, 0],
np.float32([
[0.0, 0.0, 1.0, 1.0],
[0.0, 0.0, 1.0, 1.0]
])
)
class TestHeatmapsOnImage_from_uint8(unittest.TestCase):
def test_3d_uint8_array(self):
hm = ia.HeatmapsOnImage.from_uint8(
np.uint8([
[0, 128, 255],
[255, 128, 0]
])[..., np.newaxis],
(20, 30, 3)
)
assert hm.shape == (20, 30, 3)
assert hm.arr_0to1.shape == (2, 3, 1)
assert np.allclose(hm.arr_0to1[..., 0], np.float32([
[0, 128/255, 1.0],
[1.0, 128/255, 0]
]))
def test_2d_uint8_array(self):
hm = ia.HeatmapsOnImage.from_uint8(
np.uint8([
[0, 128, 255],
[255, 128, 0]
]),
(20, 30, 3)
)
assert hm.shape == (20, 30, 3)
assert hm.arr_0to1.shape == (2, 3, 1)
assert np.allclose(hm.arr_0to1[..., 0], np.float32([
[0, 128/255, 1.0],
[1.0, 128/255, 0]
]))
def test_min_value_and_max_value(self):
# min_value, max_value
hm = ia.HeatmapsOnImage.from_uint8(
np.uint8([
[0, 128, 255],
[255, 128, 0]
])[..., np.newaxis],
(20, 30, 3),
min_value=-1.0,
max_value=2.0
)
assert hm.shape == (20, 30, 3)
assert hm.arr_0to1.shape == (2, 3, 1)
assert np.allclose(hm.arr_0to1[..., 0], np.float32([
[0, 128/255, 1.0],
[1.0, 128/255, 0]
]))
assert np.allclose(hm.min_value, -1.0)
assert np.allclose(hm.max_value, 2.0)
class TestHeatmapsOnImage_change_normalization(unittest.TestCase):
def test_increase_max_value(self):
# (0.0, 1.0) -> (0.0, 2.0)
arr = np.float32([
[0.0, 0.5, 1.0],
[1.0, 0.5, 0.0]
])
observed = ia.HeatmapsOnImage.change_normalization(
arr, (0.0, 1.0), (0.0, 2.0))
expected = np.float32([
[0.0, 1.0, 2.0],
[2.0, 1.0, 0.0]
])
assert np.allclose(observed, expected)
def test_decrease_min_and_max_value(self):
# (0.0, 1.0) -> (-1.0, 0.0)
arr = np.float32([
[0.0, 0.5, 1.0],
[1.0, 0.5, 0.0]
])
observed = ia.HeatmapsOnImage.change_normalization(
arr, (0.0, 1.0), (-1.0, 0.0))
expected = np.float32([
[-1.0, -0.5, 0.0],
[0.0, -0.5, -1.0]
])
assert np.allclose(observed, expected)
def test_increase_min_and_max_value__non_standard_source(self):
# (-1.0, 1.0) -> (1.0, 3.0)
arr = np.float32([
[-1.0, 0.0, 1.0],
[1.0, 0.0, -1.0]
])
observed = ia.HeatmapsOnImage.change_normalization(
arr, (-1.0, 1.0), (1.0, 3.0))
expected = np.float32([
[1.0, 2.0, 3.0],
[3.0, 2.0, 1.0]
])
assert np.allclose(observed, expected)
def test_value_ranges_given_as_heatmaps_on_image(self):
# (-1.0, 1.0) -> (1.0, 3.0)
# value ranges given as HeatmapsOnImage
arr = np.float32([
[-1.0, 0.0, 1.0],
[1.0, 0.0, -1.0]
])
source = ia.HeatmapsOnImage(
np.float32([[0.0]]), min_value=-1.0, max_value=1.0, shape=(1, 1, 3))
target = ia.HeatmapsOnImage(
np.float32([[1.0]]), min_value=1.0, max_value=3.0, shape=(1, 1, 3))
observed = ia.HeatmapsOnImage.change_normalization(arr, source, target)
expected = np.float32([
[1.0, 2.0, 3.0],
[3.0, 2.0, 1.0]
])
assert np.allclose(observed, expected)
================================================
FILE: test/augmentables/test_kps.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import imgaug as ia
from imgaug.testutils import assertWarns
class TestKeypoint_project_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, kp, from_shape, to_shape):
return kp.project_(from_shape, to_shape)
def test_project_same_image_size(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, (10, 10), (10, 10))
assert kp2.y == 1
assert kp2.x == 2
def test_project_onto_higher_image(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, (10, 10), (20, 10))
assert kp2.y == 2
assert kp2.x == 2
def test_project_onto_wider_image(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, (10, 10), (10, 20))
assert kp2.y == 1
assert kp2.x == 4
def test_project_onto_higher_and_wider_image(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, (10, 10), (20, 20))
assert kp2.y == 2
assert kp2.x == 4
def test_inplaceness(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, (10, 10), (10, 10))
if self._is_inplace:
assert kp is kp2
else:
assert kp is not kp2
class TestKeypoint_project(TestKeypoint_project_):
@property
def _is_inplace(self):
return False
def _func(self, kp, from_shape, to_shape):
return kp.project(from_shape, to_shape)
class TestKeypoint_shift_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, kp, *args, **kwargs):
return kp.shift_(*args, **kwargs)
def test_shift_on_y_axis(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, y=1)
assert kp2.y == 2
assert kp2.x == 2
def test_shift_on_y_axis_by_negative_amount(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, y=-1)
assert kp2.y == 0
assert kp2.x == 2
def test_shift_on_x_axis(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, x=1)
assert kp2.y == 1
assert kp2.x == 3
def test_shift_on_x_axis_by_negative_amount(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, x=-1)
assert kp2.y == 1
assert kp2.x == 1
def test_shift_on_both_axis(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, y=1, x=2)
assert kp2.y == 2
assert kp2.x == 4
def test_inplaceness(self):
kp = ia.Keypoint(y=1, x=2)
kp2 = self._func(kp, x=1)
if self._is_inplace:
assert kp is kp2
else:
assert kp is not kp2
class TestKeypoint_shift(TestKeypoint_shift_):
@property
def _is_inplace(self):
return False
def _func(self, kp, *args, **kwargs):
return kp.shift(*args, **kwargs)
class TestKeypoint(unittest.TestCase):
def test___init__(self):
kp = ia.Keypoint(y=1, x=2)
assert kp.y == 1
assert kp.x == 2
def test___init___negative_values(self):
kp = ia.Keypoint(y=-1, x=-2)
assert kp.y == -1
assert kp.x == -2
def test___init___floats(self):
kp = ia.Keypoint(y=1.5, x=2.5)
assert np.isclose(kp.y, 1.5)
assert np.isclose(kp.x, 2.5)
def test_coords(self):
kp = ia.Keypoint(x=1, y=1.5)
coords = kp.coords
assert np.allclose(coords, [1, 1.5], atol=1e-8, rtol=0)
def test_x_int(self):
kp = ia.Keypoint(y=1, x=2)
assert kp.x == 2
assert kp.x_int == 2
def test_x_int_for_float_inputs(self):
kp = ia.Keypoint(y=1, x=2.7)
assert np.isclose(kp.x, 2.7)
assert kp.x_int == 3
def test_y_int(self):
kp = ia.Keypoint(y=1, x=2)
assert kp.y == 1
assert kp.y_int == 1
def test_y_int_for_float_inputs(self):
kp = ia.Keypoint(y=1.7, x=2)
assert np.isclose(kp.y, 1.7)
assert kp.y_int == 2
def test_xy(self):
kp = ia.Keypoint(x=2, y=1.7)
assert np.allclose(kp.xy, (2, 1.7))
def test_xy_int(self):
kp = ia.Keypoint(x=1.3, y=1.6)
xy = kp.xy_int
assert np.allclose(xy, (1, 2))
assert xy.dtype.name == "int32"
def test_is_out_of_image(self):
kp = ia.Keypoint(y=1, x=2)
image_shape = (10, 20, 3)
ooi = kp.is_out_of_image(image_shape)
assert not ooi
def test_is_out_of_image__ooi_y(self):
kp = ia.Keypoint(y=11, x=2)
image_shape = (10, 20, 3)
ooi = kp.is_out_of_image(image_shape)
assert ooi
def test_is_out_of_image__ooi_x(self):
kp = ia.Keypoint(y=1, x=21)
image_shape = (10, 20, 3)
ooi = kp.is_out_of_image(image_shape)
assert ooi
def test_compute_out_of_image_fraction(self):
kp = ia.Keypoint(y=1, x=2)
image_shape = (10, 20, 3)
fraction = kp.compute_out_of_image_fraction(image_shape)
assert np.isclose(fraction, 0.0)
def test_compute_out_of_image_fraction_ooi_y(self):
kp = ia.Keypoint(y=11, x=2)
image_shape = (10, 20, 3)
fraction = kp.compute_out_of_image_fraction(image_shape)
assert np.isclose(fraction, 1.0)
def test_compute_out_of_image_fraction_ooi_x(self):
kp = ia.Keypoint(y=1, x=21)
image_shape = (10, 20, 3)
fraction = kp.compute_out_of_image_fraction(image_shape)
assert np.isclose(fraction, 1.0)
def test_draw_on_image(self):
kp = ia.Keypoint(x=0, y=0)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
image_kp = kp.draw_on_image(
image, color=(0, 255, 0), alpha=1, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kp[0, 0, :] == [0, 255, 0])
assert np.all(image_kp[1:, :, :] == 10)
assert np.all(image_kp[:, 1:, :] == 10)
def test_draw_on_image_kp_at_top_left_corner_size_1(self):
kp = ia.Keypoint(x=4, y=4)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
image_kp = kp.draw_on_image(
image, color=(0, 255, 0), alpha=1, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kp[4, 4, :] == [0, 255, 0])
assert np.all(image_kp[:4, :, :] == 10)
assert np.all(image_kp[:, :4, :] == 10)
def test_draw_on_image_kp_at_top_left_corner_size_5(self):
kp = ia.Keypoint(x=0, y=0)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
image_kp = kp.draw_on_image(
image, color=(0, 255, 0), alpha=1, size=5, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kp[:3, :3, :] == [0, 255, 0])
assert np.all(image_kp[3:, :, :] == 10)
assert np.all(image_kp[:, 3:, :] == 10)
def test_draw_on_image_kp_at_top_left_corner_custom_color_and_alpha(self):
kp = ia.Keypoint(x=0, y=0)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
image_kp = kp.draw_on_image(
image, color=(0, 200, 0), alpha=0.5, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kp[0, 0, :] == [0 + 5, 100 + 5, 0 + 5])
assert np.all(image_kp[1:, :, :] == 10)
assert np.all(image_kp[:, 1:, :] == 10)
def test_draw_on_image_kp_somewhere_inside_image_size_5(self):
kp = ia.Keypoint(x=4, y=4)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
image_kp = kp.draw_on_image(
image, color=(0, 255, 0), alpha=1, size=5, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kp[2:, 2:, :] == [0, 255, 0])
assert np.all(image_kp[:2, :, :] == 10)
assert np.all(image_kp[:, :2, :] == 10)
def test_draw_on_image_kp_at_bottom_right_corner_size_5(self):
kp = ia.Keypoint(x=5, y=5)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
image_kp = kp.draw_on_image(
image, color=(0, 255, 0), alpha=1, size=5, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kp[3:, 3:, :] == [0, 255, 0])
assert np.all(image_kp[:3, :, :] == 10)
assert np.all(image_kp[:, :3, :] == 10)
def test_draw_on_image_kp_outside_image(self):
kp = ia.Keypoint(x=-1, y=-1)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
image_kp = kp.draw_on_image(
image, color=(0, 255, 0), alpha=1, size=5, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kp[:2, :2, :] == [0, 255, 0])
assert np.all(image_kp[2:, :, :] == 10)
assert np.all(image_kp[:, 2:, :] == 10)
def test_generate_similar_points_manhattan_0_steps_list(self):
kp = ia.Keypoint(y=4, x=5)
kps_manhatten = kp.generate_similar_points_manhattan(
0, 1.0, return_array=False)
assert len(kps_manhatten) == 1
assert kps_manhatten[0].y == 4
assert kps_manhatten[0].x == 5
def test_generate_similar_points_manhattan_1_step_list(self):
kp = ia.Keypoint(y=4, x=5)
kps_manhatten = kp.generate_similar_points_manhattan(
1, 1.0, return_array=False)
assert len(kps_manhatten) == 5
expected = [(4, 5), (3, 5), (4, 6), (5, 5), (4, 4)]
for y, x in expected:
assert any([
np.allclose(
[y, x],
[kp_manhatten.y, kp_manhatten.x]
)
for kp_manhatten
in kps_manhatten
])
def test_generate_similar_points_manhattan_1_step_array(self):
kp = ia.Keypoint(y=4, x=5)
kps_manhatten = kp.generate_similar_points_manhattan(
1, 1.0, return_array=True)
assert kps_manhatten.shape == (5, 2)
expected = [(4, 5), (3, 5), (4, 6), (5, 5), (4, 4)]
for y, x in expected:
assert any([
np.allclose(
[y, x],
[kp_manhatten_y, kp_manhatten_x]
)
for kp_manhatten_x, kp_manhatten_y
in kps_manhatten
])
def test_coords_almost_equals(self):
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = ia.Keypoint(x=1, y=1.5)
equal = kp1.coords_almost_equals(kp2)
assert equal
def test_coords_almost_equals__unequal(self):
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = ia.Keypoint(x=1, y=1.5+10.0)
equal = kp1.coords_almost_equals(kp2)
assert not equal
def test_coords_almost_equals__distance_below_threshold(self):
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = ia.Keypoint(x=1, y=1.5+1e-2)
equal = kp1.coords_almost_equals(kp2, max_distance=1e-1)
assert equal
def test_coords_almost_equals__distance_exceeds_threshold(self):
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = ia.Keypoint(x=1, y=1.5+1e-2)
equal = kp1.coords_almost_equals(kp2, max_distance=1e-3)
assert not equal
def test_coords_almost_equals__array(self):
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = np.float32([1, 1.5])
equal = kp1.coords_almost_equals(kp2)
assert equal
def test_coords_almost_equals__array_unequal(self):
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = np.float32([1, 1.5+1.0])
equal = kp1.coords_almost_equals(kp2)
assert not equal
def test_coords_almost_equals__tuple(self):
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = (1, 1.5)
equal = kp1.coords_almost_equals(kp2)
assert equal
def test_coords_almost_equals__tuple_unequal(self):
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = (1, 1.5+1.0)
equal = kp1.coords_almost_equals(kp2)
assert not equal
@mock.patch("imgaug.augmentables.kps.Keypoint.coords_almost_equals")
def test_almost_equals(self, mock_cae):
mock_cae.return_value = "foo"
kp1 = ia.Keypoint(x=1, y=1.5)
kp2 = ia.Keypoint(x=1, y=1.5)
result = kp1.almost_equals(kp2, max_distance=2)
assert result == "foo"
mock_cae.assert_called_once_with(kp2, max_distance=2)
def test_string_conversion_ints(self):
kp = ia.Keypoint(y=1, x=2)
assert (
kp.__repr__()
== kp.__str__()
== "Keypoint(x=2.00000000, y=1.00000000)"
)
def test_string_conversion_floats(self):
kp = ia.Keypoint(y=1.2, x=2.7)
assert (
kp.__repr__()
== kp.__str__()
== "Keypoint(x=2.70000000, y=1.20000000)"
)
class TestKeypointsOnImage_items_setter(unittest.TestCase):
def test_with_list_of_keypoints(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpsoi = ia.KeypointsOnImage(keypoints=[], shape=(10, 20, 3))
kpsoi.items = kps
assert np.all([
kp_i.x == kp_j.x and kp_i.y == kp_j.y
for kp_i, kp_j
in zip(kpsoi.keypoints, kps)
])
class TestKeypointsOnImage_on_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, kpsoi, *args, **kwargs):
return kpsoi.on_(*args, **kwargs)
def test_same_image_size(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(10, 20, 3))
kpi2 = self._func(kpi, (10, 20, 3))
assert np.all([
kp_i.x == kp_j.x and kp_i.y == kp_j.y
for kp_i, kp_j
in zip(kpi.keypoints, kpi2.keypoints)
])
assert kpi2.shape == (10, 20, 3)
def test_wider_image(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(10, 20, 3))
kpi2 = self._func(kpi, (20, 40, 3))
assert kpi2.keypoints[0].x == 2
assert kpi2.keypoints[0].y == 4
assert kpi2.keypoints[1].x == 6
assert kpi2.keypoints[1].y == 8
assert kpi2.shape == (20, 40, 3)
def test_wider_image_shape_given_as_array(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(10, 20, 3))
image = np.zeros((20, 40, 3), dtype=np.uint8)
kpi2 = self._func(kpi, image)
assert kpi2.keypoints[0].x == 2
assert kpi2.keypoints[0].y == 4
assert kpi2.keypoints[1].x == 6
assert kpi2.keypoints[1].y == 8
assert kpi2.shape == image.shape
def test_inplaceness(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(10, 20, 3))
kpi2 = self._func(kpi, (10, 20, 3))
if self._is_inplace:
assert kpi is kpi2
else:
assert kpi is not kpi2
class TestKeypointsOnImage_on(TestKeypointsOnImage_on_):
@property
def _is_inplace(self):
return False
def _func(self, kpsoi, *args, **kwargs):
return kpsoi.on(*args, **kwargs)
class TestKeypointsOnImage_shift_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, kpsoi, *args, **kwargs):
return kpsoi.shift_(*args, **kwargs)
def test_shift_by_zero_on_both_axis(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = self._func(kpi, x=0, y=0)
assert kpi2.keypoints[0].x == 1
assert kpi2.keypoints[0].y == 2
assert kpi2.keypoints[1].x == 3
assert kpi2.keypoints[1].y == 4
def test_shift_by_1_on_x_axis(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = self._func(kpi, x=1)
assert kpi2.keypoints[0].x == 1 + 1
assert kpi2.keypoints[0].y == 2
assert kpi2.keypoints[1].x == 3 + 1
assert kpi2.keypoints[1].y == 4
def test_shift_by_negative_1_on_x_axis(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = self._func(kpi, x=-1)
assert kpi2.keypoints[0].x == 1 - 1
assert kpi2.keypoints[0].y == 2
assert kpi2.keypoints[1].x == 3 - 1
assert kpi2.keypoints[1].y == 4
def test_shift_by_1_on_y_axis(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = self._func(kpi, y=1)
assert kpi2.keypoints[0].x == 1
assert kpi2.keypoints[0].y == 2 + 1
assert kpi2.keypoints[1].x == 3
assert kpi2.keypoints[1].y == 4 + 1
def test_shift_by_negative_1_on_y_axis(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = self._func(kpi, y=-1)
assert kpi2.keypoints[0].x == 1
assert kpi2.keypoints[0].y == 2 - 1
assert kpi2.keypoints[1].x == 3
assert kpi2.keypoints[1].y == 4 - 1
def test_shift_on_both_axis(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = self._func(kpi, x=1, y=2)
assert kpi2.keypoints[0].x == 1 + 1
assert kpi2.keypoints[0].y == 2 + 2
assert kpi2.keypoints[1].x == 3 + 1
assert kpi2.keypoints[1].y == 4 + 2
def test_inplaceness(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = self._func(kpi, x=0, y=0)
if self._is_inplace:
assert kpi is kpi2
else:
assert kpi is not kpi2
class TestKeypointsOnImage_shift(TestKeypointsOnImage_shift_):
@property
def _is_inplace(self):
return False
def _func(self, kpsoi, *args, **kwargs):
return kpsoi.shift(*args, **kwargs)
class TestKeypointsOnImage(unittest.TestCase):
def test_items(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpsoi = ia.KeypointsOnImage(kps, shape=(40, 50, 3))
items = kpsoi.items
assert items == kps
def test_items_empty(self):
kpsoi = ia.KeypointsOnImage([], shape=(40, 50, 3))
items = kpsoi.items
assert items == []
def test_height(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(10, 20, 3))
assert kpi.height == 10
def test_width(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(10, 20, 3))
assert kpi.width == 20
def test_shape_is_array(self):
image = np.zeros((10, 20, 3), dtype=np.uint8)
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
with assertWarns(self, ia.DeprecationWarning):
kpi = ia.KeypointsOnImage(
keypoints=kps,
shape=image
)
assert kpi.shape == (10, 20, 3)
def test_draw_on_image(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
image_kps = kpi.draw_on_image(
image, color=[0, 255, 0], size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kps[kps_mask] == [0, 255, 0])
assert np.all(image_kps[~kps_mask] == [10, 10, 10])
def test_draw_on_image_alpha_is_50_percent(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
image_kps = kpi.draw_on_image(
image, color=[0, 255, 0], alpha=0.5, size=1, copy=True,
raise_if_out_of_image=False)
bg_plus_color_at_alpha = [int(0.5*10+0),
int(0.5*10+0.5*255),
int(10*0.5+0)]
assert np.all(image_kps[kps_mask] == bg_plus_color_at_alpha)
assert np.all(image_kps[~kps_mask] == [10, 10, 10])
def test_draw_on_image_size_3(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
image_kps = kpi.draw_on_image(
image, color=[0, 255, 0], size=3, copy=True,
raise_if_out_of_image=False)
kps_mask_size3 = np.copy(kps_mask)
kps_mask_size3[2-1:2+1+1, 1-1:1+1+1] = 1
kps_mask_size3[4-1:4+1+1, 3-1:3+1+1] = 1
assert np.all(image_kps[kps_mask_size3] == [0, 255, 0])
assert np.all(image_kps[~kps_mask_size3] == [10, 10, 10])
def test_draw_on_image_blue_color(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
image_kps = kpi.draw_on_image(
image, color=[0, 0, 255], size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kps[kps_mask] == [0, 0, 255])
assert np.all(image_kps[~kps_mask] == [10, 10, 10])
def test_draw_on_image_single_int_as_color(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
image_kps = kpi.draw_on_image(
image, color=255, size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kps[kps_mask] == [255, 255, 255])
assert np.all(image_kps[~kps_mask] == [10, 10, 10])
def test_draw_on_image_copy_is_false(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
image2 = np.copy(image)
image_kps = kpi.draw_on_image(
image2, color=[0, 255, 0], size=1, copy=False,
raise_if_out_of_image=False)
assert np.all(image2 == image_kps)
assert np.all(image_kps[kps_mask] == [0, 255, 0])
assert np.all(image_kps[~kps_mask] == [10, 10, 10])
assert np.all(image2[kps_mask] == [0, 255, 0])
assert np.all(image2[~kps_mask] == [10, 10, 10])
def test_draw_on_image_keypoint_is_outside_of_image(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(
keypoints=kps + [ia.Keypoint(x=100, y=100)],
shape=(5, 5, 3)
)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
image_kps = kpi.draw_on_image(
image, color=[0, 255, 0], size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kps[kps_mask] == [0, 255, 0])
assert np.all(image_kps[~kps_mask] == [10, 10, 10])
def test_draw_on_image_keypoint_is_outside_of_image_and_raise_true(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(
keypoints=kps + [ia.Keypoint(x=100, y=100)],
shape=(5, 5, 3)
)
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
with self.assertRaises(Exception) as context:
_ = kpi.draw_on_image(
image, color=[0, 255, 0], size=1, copy=True,
raise_if_out_of_image=True)
assert "Cannot draw keypoint" in str(context.exception)
def test_draw_on_image_one_kp_at_bottom_right_corner(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(
keypoints=kps + [ia.Keypoint(x=5, y=5)],
shape=(5, 5, 3))
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
image_kps = kpi.draw_on_image(
image, color=[0, 255, 0], size=1, copy=True,
raise_if_out_of_image=False)
assert np.all(image_kps[kps_mask] == [0, 255, 0])
assert np.all(image_kps[~kps_mask] == [10, 10, 10])
def test_draw_on_image_one_kp_at_bottom_right_corner_and_raise_true(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(
keypoints=kps + [ia.Keypoint(x=5, y=5)],
shape=(5, 5, 3))
image = np.zeros((5, 5, 3), dtype=np.uint8) + 10
kps_mask = np.zeros(image.shape[0:2], dtype=np.bool)
kps_mask[2, 1] = 1
kps_mask[4, 3] = 1
with self.assertRaises(Exception) as context:
_ = kpi.draw_on_image(
image, color=[0, 255, 0], size=1, copy=True,
raise_if_out_of_image=True)
assert "Cannot draw keypoint" in str(context.exception)
@classmethod
def _test_clip_remove_frac(cls, func, inplace):
item1 = ia.Keypoint(x=5, y=1)
item2 = ia.Keypoint(x=15, y=1)
cbaoi = ia.KeypointsOnImage([item1, item2], shape=(10, 10, 3))
cbaoi_reduced = func(cbaoi)
assert len(cbaoi_reduced.items) == 1
assert np.allclose(cbaoi_reduced.to_xy_array(), [item1.xy])
if inplace:
assert cbaoi_reduced is cbaoi
else:
assert cbaoi_reduced is not cbaoi
assert len(cbaoi.items) == 2
def test_remove_out_of_image_fraction_(self):
def _func(cbaoi):
return cbaoi.remove_out_of_image_fraction_(0.6)
self._test_clip_remove_frac(_func, True)
def test_remove_out_of_image_fraction(self):
def _func(cbaoi):
return cbaoi.remove_out_of_image_fraction(0.6)
self._test_clip_remove_frac(_func, False)
def test_clip_out_of_image_fraction_(self):
def _func(cbaoi):
return cbaoi.clip_out_of_image_()
self._test_clip_remove_frac(_func, True)
def test_clip_out_of_image_fraction(self):
def _func(cbaoi):
return cbaoi.clip_out_of_image()
self._test_clip_remove_frac(_func, False)
def test_to_xy_array(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
observed = kpi.to_xy_array()
expected = np.float32([
[1, 2],
[3, 4]
])
assert np.allclose(observed, expected)
def test_from_xy_array(self):
arr = np.float32([
[1, 2],
[3, 4]
])
kpi = ia.KeypointsOnImage.from_xy_array(arr, shape=(5, 5, 3))
assert np.isclose(kpi.keypoints[0].x, 1)
assert np.isclose(kpi.keypoints[0].y, 2)
assert np.isclose(kpi.keypoints[1].x, 3)
assert np.isclose(kpi.keypoints[1].y, 4)
def test_fill_from_xy_array___empty_array(self):
xy = np.zeros((0, 2), dtype=np.float32)
kps = ia.KeypointsOnImage([], shape=(2, 2, 3))
kps = kps.fill_from_xy_array_(xy)
assert len(kps.keypoints) == 0
def test_fill_from_xy_array___empty_list(self):
xy = []
kps = ia.KeypointsOnImage([], shape=(2, 2, 3))
kps = kps.fill_from_xy_array_(xy)
assert len(kps.keypoints) == 0
def test_fill_from_xy_array___array_with_two_coords(self):
xy = np.array([(0, 0), (1, 2)], dtype=np.float32)
kps = ia.KeypointsOnImage([ia.Keypoint(10, 20), ia.Keypoint(30, 40)],
shape=(2, 2, 3))
kps = kps.fill_from_xy_array_(xy)
assert len(kps.keypoints) == 2
assert kps.keypoints[0].x == 0
assert kps.keypoints[0].y == 0
assert kps.keypoints[1].x == 1
assert kps.keypoints[1].y == 2
def test_fill_from_xy_array___list_with_two_coords(self):
xy = [(0, 0), (1, 2)]
kps = ia.KeypointsOnImage([ia.Keypoint(10, 20), ia.Keypoint(30, 40)],
shape=(2, 2, 3))
kps = kps.fill_from_xy_array_(xy)
assert len(kps.keypoints) == 2
assert kps.keypoints[0].x == 0
assert kps.keypoints[0].y == 0
assert kps.keypoints[1].x == 1
assert kps.keypoints[1].y == 2
def test_to_keypoint_image_size_1(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
image = kpi.to_keypoint_image(size=1)
kps_mask = np.zeros((5, 5, 2), dtype=np.bool)
kps_mask[2, 1, 0] = 1
kps_mask[4, 3, 1] = 1
assert np.all(image[kps_mask] == 255)
assert np.all(image[~kps_mask] == 0)
def test_to_keypoint_image_size_3(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
image = kpi.to_keypoint_image(size=3)
kps_mask = np.zeros((5, 5, 2), dtype=np.bool)
kps_mask[2-1:2+1+1, 1-1:1+1+1, 0] = 1
kps_mask[4-1:4+1+1, 3-1:3+1+1, 1] = 1
assert np.all(image[kps_mask] >= 128)
assert np.all(image[~kps_mask] == 0)
def test_from_keypoint_image(self):
kps_image = np.zeros((5, 5, 2), dtype=np.uint8)
kps_image[2, 1, 0] = 255
kps_image[4, 3, 1] = 255
kpi2 = ia.KeypointsOnImage.from_keypoint_image(
kps_image, nb_channels=3)
assert kpi2.shape == (5, 5, 3)
assert len(kpi2.keypoints) == 2
assert kpi2.keypoints[0].y == 2.5
assert kpi2.keypoints[0].x == 1.5
assert kpi2.keypoints[1].y == 4.5
assert kpi2.keypoints[1].x == 3.5
def test_from_keypoint_image_dict_as_if_not_found_thresh_20(self):
kps_image = np.zeros((5, 5, 2), dtype=np.uint8)
kps_image[2, 1, 0] = 255
kps_image[4, 3, 1] = 10
kpi2 = ia.KeypointsOnImage.from_keypoint_image(
kps_image,
if_not_found_coords={"x": -1, "y": -2},
threshold=20,
nb_channels=3)
assert kpi2.shape == (5, 5, 3)
assert len(kpi2.keypoints) == 2
assert kpi2.keypoints[0].y == 2.5
assert kpi2.keypoints[0].x == 1.5
assert kpi2.keypoints[1].y == -2
assert kpi2.keypoints[1].x == -1
def test_from_keypoint_image_tuple_as_if_not_found_thresh_20(self):
kps_image = np.zeros((5, 5, 2), dtype=np.uint8)
kps_image[2, 1, 0] = 255
kps_image[4, 3, 1] = 10
kpi2 = ia.KeypointsOnImage.from_keypoint_image(
kps_image,
if_not_found_coords=(-1, -2),
threshold=20,
nb_channels=3)
assert kpi2.shape == (5, 5, 3)
assert len(kpi2.keypoints) == 2
assert kpi2.keypoints[0].y == 2.5
assert kpi2.keypoints[0].x == 1.5
assert kpi2.keypoints[1].y == -2
assert kpi2.keypoints[1].x == -1
def test_from_keypoint_image_none_as_if_not_found_thresh_20(self):
kps_image = np.zeros((5, 5, 2), dtype=np.uint8)
kps_image[2, 1, 0] = 255
kps_image[4, 3, 1] = 10
kpi2 = ia.KeypointsOnImage.from_keypoint_image(
kps_image,
if_not_found_coords=None,
threshold=20,
nb_channels=3)
assert kpi2.shape == (5, 5, 3)
assert len(kpi2.keypoints) == 1
assert kpi2.keypoints[0].y == 2.5
assert kpi2.keypoints[0].x == 1.5
def test_from_keypoint_image_bad_datatype_as_if_not_found(self):
kps_image = np.zeros((5, 5, 2), dtype=np.uint8)
kps_image[2, 1, 0] = 255
kps_image[4, 3, 1] = 10
with self.assertRaises(Exception) as context:
_ = ia.KeypointsOnImage.from_keypoint_image(
kps_image,
if_not_found_coords="exception-please",
threshold=20,
nb_channels=3)
assert "Expected if_not_found_coords to be" in str(context.exception)
@classmethod
def _get_single_keypoint_distance_map(cls):
# distance map for one keypoint at (x=2, y=3) on (5, 5, 3) image
distance_map_xx = np.float32([
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4],
[0, 1, 2, 3, 4]
])
distance_map_yy = np.float32([
[0, 0, 0, 0, 0],
[1, 1, 1, 1, 1],
[2, 2, 2, 2, 2],
[3, 3, 3, 3, 3],
[4, 4, 4, 4, 4]
])
distance_map = np.sqrt(
(distance_map_xx - 2)**2
+ (distance_map_yy - 3)**2)
return distance_map[..., np.newaxis]
def test_to_distance_maps(self):
kpi = ia.KeypointsOnImage(
keypoints=[ia.Keypoint(x=2, y=3)],
shape=(5, 5, 3))
distance_map = kpi.to_distance_maps()
expected = self._get_single_keypoint_distance_map()
assert distance_map.shape == (5, 5, 1)
assert np.allclose(distance_map, expected)
def test_to_distance_maps_inverted(self):
kpi = ia.KeypointsOnImage(
keypoints=[ia.Keypoint(x=2, y=3)],
shape=(5, 5, 3))
distance_map = kpi.to_distance_maps(inverted=True)
expected = self._get_single_keypoint_distance_map()
expected_inv = np.divide(np.ones_like(expected), expected+1)
assert distance_map.shape == (5, 5, 1)
assert np.allclose(distance_map, expected_inv)
@classmethod
def _get_two_points_keypoint_distance_map(cls):
# distance map for two keypoints at (x=2, y=3) and (x=1, y=0) on
# (4, 4, 3) image
#
# Visualization of positions on (4, 4) map (X=position, 1=KP 1 is
# closest, 2=KP 2 is closest, B=close to both):
#
# [1, X, 1, 1]
# [1, 1, 1, B]
# [B, 2, 2, 2]
# [2, 2, X, 2]
#
distance_map_x = np.float32([
[(0-1)**2, (1-1)**2, (2-1)**2, (3-1)**2],
[(0-1)**2, (1-1)**2, (2-1)**2, (3-1)**2],
[(0-1)**2, (1-2)**2, (2-2)**2, (3-2)**2],
[(0-2)**2, (1-2)**2, (2-2)**2, (3-2)**2],
])
distance_map_y = np.float32([
[(0-0)**2, (0-0)**2, (0-0)**2, (0-0)**2],
[(1-0)**2, (1-0)**2, (1-0)**2, (1-0)**2],
[(2-0)**2, (2-3)**2, (2-3)**2, (2-3)**2],
[(3-3)**2, (3-3)**2, (3-3)**2, (3-3)**2],
])
distance_map = np.sqrt(distance_map_x + distance_map_y)
return distance_map
def test_to_distance_maps_two_keypoints(self):
# TODO this test could have been done a bit better by simply splitting
# the distance maps, one per keypoint, considering the function
# returns one distance map per keypoint
kpi = ia.KeypointsOnImage(
keypoints=[ia.Keypoint(x=2, y=3), ia.Keypoint(x=1, y=0)],
shape=(4, 4, 3))
distance_map = kpi.to_distance_maps()
expected = self._get_two_points_keypoint_distance_map()
assert np.allclose(np.min(distance_map, axis=2), expected)
def test_to_distance_maps_two_keypoints_inverted(self):
kpi = ia.KeypointsOnImage(
keypoints=[ia.Keypoint(x=2, y=3), ia.Keypoint(x=1, y=0)],
shape=(4, 4, 3))
distance_map_inv = kpi.to_distance_maps(inverted=True)
expected = self._get_two_points_keypoint_distance_map()
expected_inv = np.divide(np.ones_like(expected), expected+1)
assert np.allclose(np.max(distance_map_inv, axis=2), expected_inv)
@classmethod
def _get_distance_maps_for_from_dmap_tests(cls):
distance_map1 = np.float32([
[2, 2, 2, 2, 2],
[2, 1, 1, 1, 2],
[2, 1, 0, 1, 2],
[2, 1, 1, 1, 2]
])
distance_map2 = np.float32([
[4, 3, 2, 2, 2],
[4, 3, 2, 1, 1],
[4, 3, 2, 1, 0.1],
[4, 3, 2, 1, 1]
])
distance_maps = np.concatenate([
distance_map1[..., np.newaxis],
distance_map2[..., np.newaxis]
], axis=2)
return distance_maps
def test_from_distance_maps(self):
distance_maps = self._get_distance_maps_for_from_dmap_tests()
kpi = ia.KeypointsOnImage.from_distance_maps(distance_maps)
assert len(kpi.keypoints) == 2
assert kpi.keypoints[0].x == 2
assert kpi.keypoints[0].y == 2
assert kpi.keypoints[1].x == 4
assert kpi.keypoints[1].y == 2
assert kpi.shape == (4, 5)
def test_from_distance_maps_nb_channels_4(self):
distance_maps = self._get_distance_maps_for_from_dmap_tests()
kpi = ia.KeypointsOnImage.from_distance_maps(distance_maps,
nb_channels=4)
assert len(kpi.keypoints) == 2
assert kpi.keypoints[0].x == 2
assert kpi.keypoints[0].y == 2
assert kpi.keypoints[1].x == 4
assert kpi.keypoints[1].y == 2
assert kpi.shape == (4, 5, 4)
def test_from_distance_maps_inverted(self):
distance_maps = self._get_distance_maps_for_from_dmap_tests()
distance_maps_inv = np.divide(
np.ones_like(distance_maps),
distance_maps+1)
kpi = ia.KeypointsOnImage.from_distance_maps(distance_maps_inv,
inverted=True)
assert len(kpi.keypoints) == 2
assert kpi.keypoints[0].x == 2
assert kpi.keypoints[0].y == 2
assert kpi.keypoints[1].x == 4
assert kpi.keypoints[1].y == 2
assert kpi.shape == (4, 5)
def test_from_distance_maps_if_not_found_is_tuple_thresh_009(self):
distance_maps = self._get_distance_maps_for_from_dmap_tests()
kpi = ia.KeypointsOnImage.from_distance_maps(
distance_maps, if_not_found_coords=(1, 1), threshold=0.09)
assert len(kpi.keypoints) == 2
assert kpi.keypoints[0].x == 2
assert kpi.keypoints[0].y == 2
assert kpi.keypoints[1].x == 1
assert kpi.keypoints[1].y == 1
assert kpi.shape == (4, 5)
def test_from_distance_maps_if_not_found_is_dict_thresh_009(self):
distance_maps = self._get_distance_maps_for_from_dmap_tests()
kpi = ia.KeypointsOnImage.from_distance_maps(
distance_maps,
if_not_found_coords={"x": 1, "y": 2},
threshold=0.09)
assert len(kpi.keypoints) == 2
assert kpi.keypoints[0].x == 2
assert kpi.keypoints[0].y == 2
assert kpi.keypoints[1].x == 1
assert kpi.keypoints[1].y == 2
assert kpi.shape == (4, 5)
def test_from_distance_maps_if_not_found_is_none_thresh_009(self):
distance_maps = self._get_distance_maps_for_from_dmap_tests()
kpi = ia.KeypointsOnImage.from_distance_maps(
distance_maps,
if_not_found_coords=None,
threshold=0.09)
assert len(kpi.keypoints) == 1
assert kpi.keypoints[0].x == 2
assert kpi.keypoints[0].y == 2
assert kpi.shape == (4, 5)
def test_from_distance_maps_bad_datatype_for_if_not_found(self):
distance_maps = self._get_distance_maps_for_from_dmap_tests()
with self.assertRaises(Exception) as context:
_ = ia.KeypointsOnImage.from_distance_maps(
distance_maps,
if_not_found_coords=False,
threshold=0.09)
assert "Expected if_not_found_coords to be" in str(context.exception)
def test_to_keypoints_on_image(self):
kps = ia.KeypointsOnImage([ia.Keypoint(0, 0), ia.Keypoint(1, 2)],
shape=(1, 2, 3))
kps.deepcopy = mock.MagicMock()
kps.deepcopy.return_value = "foo"
kps_cp = kps.to_keypoints_on_image()
assert kps.deepcopy.call_count == 1
assert kps_cp == "foo"
def test_invert_to_keypoints_on_image_(self):
kps1 = ia.KeypointsOnImage([ia.Keypoint(0, 0), ia.Keypoint(1, 2)],
shape=(2, 3, 4))
kps2 = ia.KeypointsOnImage([ia.Keypoint(10, 10), ia.Keypoint(11, 12)],
shape=(3, 4, 5))
kps3 = kps1.invert_to_keypoints_on_image_(kps2)
assert kps3 is not kps2
assert kps3.shape == (3, 4, 5)
assert kps3.keypoints[0].x == 10
assert kps3.keypoints[0].y == 10
assert kps3.keypoints[1].x == 11
assert kps3.keypoints[1].y == 12
def test_copy(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = kpi.copy()
assert kpi2.keypoints[0].x == 1
assert kpi2.keypoints[0].y == 2
assert kpi2.keypoints[1].x == 3
assert kpi2.keypoints[1].y == 4
kps[0].x = 100
assert kpi2.keypoints[0].x == 100
assert kpi2.keypoints[0].y == 2
assert kpi2.keypoints[1].x == 3
assert kpi2.keypoints[1].y == 4
def test_copy_keypoints_set(self):
kp1 = ia.Keypoint(x=1, y=2)
kp2 = ia.Keypoint(x=3, y=4)
kp3 = ia.Keypoint(x=5, y=6)
kpsoi = ia.KeypointsOnImage([kp1, kp2], shape=(40, 50, 3))
kpsoi_copy = kpsoi.copy(keypoints=[kp3])
assert kpsoi_copy is not kpsoi
assert kpsoi_copy.shape == (40, 50, 3)
assert kpsoi_copy.keypoints == [kp3]
def test_copy_shape_set(self):
kp1 = ia.Keypoint(x=1, y=2)
kp2 = ia.Keypoint(x=3, y=4)
kpsoi = ia.KeypointsOnImage([kp1, kp2], shape=(40, 50, 3))
kpsoi_copy = kpsoi.copy(shape=(40+1, 50+1, 3))
assert kpsoi_copy is not kpsoi
assert kpsoi_copy.shape == (40+1, 50+1, 3)
assert kpsoi_copy.keypoints == [kp1, kp2]
def test_deepcopy(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
kpi2 = kpi.deepcopy()
assert kpi2.keypoints[0].x == 1
assert kpi2.keypoints[0].y == 2
assert kpi2.keypoints[1].x == 3
assert kpi2.keypoints[1].y == 4
kps[0].x = 100
assert kpi2.keypoints[0].x == 1
assert kpi2.keypoints[0].y == 2
assert kpi2.keypoints[1].x == 3
assert kpi2.keypoints[1].y == 4
def test_deepcopy_keypoints_set(self):
kp1 = ia.Keypoint(x=1, y=2)
kp2 = ia.Keypoint(x=3, y=4)
kp3 = ia.Keypoint(x=5, y=6)
kpsoi = ia.KeypointsOnImage([kp1, kp2], shape=(40, 50, 3))
kpsoi_copy = kpsoi.deepcopy(keypoints=[kp3])
assert kpsoi_copy is not kpsoi
assert kpsoi_copy.shape == (40, 50, 3)
assert kpsoi_copy.keypoints == [kp3]
def test_deepcopy_shape_set(self):
kp1 = ia.Keypoint(x=1, y=2)
kp2 = ia.Keypoint(x=3, y=4)
kpsoi = ia.KeypointsOnImage([kp1, kp2], shape=(40, 50, 3))
kpsoi_copy = kpsoi.deepcopy(shape=(40+1, 50+1, 3))
assert kpsoi_copy is not kpsoi
assert kpsoi_copy.shape == (40+1, 50+1, 3)
assert len(kpsoi_copy.keypoints) == 2
assert kpsoi_copy.keypoints[0].coords_almost_equals(kp1)
assert kpsoi_copy.keypoints[1].coords_almost_equals(kp2)
def test___getitem__(self):
cbas = [
ia.Keypoint(x=1, y=2),
ia.Keypoint(x=2, y=3)
]
cbasoi = ia.KeypointsOnImage(cbas, shape=(3, 4, 3))
assert cbasoi[0] is cbas[0]
assert cbasoi[1] is cbas[1]
assert cbasoi[0:2] == cbas
def test___iter__(self):
cbas = [ia.Keypoint(x=1, y=2),
ia.Keypoint(x=3, y=4)]
cbasoi = ia.KeypointsOnImage(cbas, shape=(40, 50, 3))
for i, cba in enumerate(cbasoi):
assert cba is cbas[i]
def test___iter___empty(self):
cbasoi = ia.KeypointsOnImage([], shape=(40, 50, 3))
i = 0
for _cba in cbasoi:
i += 1
assert i == 0
def test___len__(self):
cbas = [ia.Keypoint(x=1, y=2),
ia.Keypoint(x=3, y=4)]
cbasoi = ia.KeypointsOnImage(cbas, shape=(40, 50, 3))
assert len(cbasoi) == 2
def test_string_conversion(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
kpi = ia.KeypointsOnImage(keypoints=kps, shape=(5, 5, 3))
expected = (
"KeypointsOnImage(["
"Keypoint(x=1.00000000, y=2.00000000), "
"Keypoint(x=3.00000000, y=4.00000000)"
"], shape=(5, 5, 3)"
")"
)
assert (
kpi.__repr__()
== kpi.__str__()
== expected
)
================================================
FILE: test/augmentables/test_lines.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import imgaug as ia
from imgaug.testutils import reseed, wrap_shift_deprecation, assertWarns
from imgaug.augmentables.lines import LineString, LineStringsOnImage
from imgaug.augmentables.kps import Keypoint
from imgaug.augmentables.heatmaps import HeatmapsOnImage
class TestLineString_project_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, ls, from_shape, to_shape):
return ls.project_(from_shape, to_shape)
def test_project_to_2x_image_size(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
ls_proj = self._func(ls, (10, 10), (20, 20))
assert np.allclose(ls_proj.coords, [(0, 0), (2, 0), (4, 2)])
def test_project_to_2x_image_width(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
ls_proj = self._func(ls, (10, 10), (10, 20))
assert np.allclose(ls_proj.coords, [(0, 0), (2, 0), (4, 1)])
def test_project_to_2x_image_height(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
ls_proj = self._func(ls, (10, 10), (20, 10))
assert np.allclose(ls_proj.coords, [(0, 0), (1, 0), (2, 2)])
def test_inplaceness(self):
ls = ia.LineString([(0, 0), (1, 0)])
ls2 = self._func(ls, (10, 10), (10, 10))
if self._is_inplace:
assert ls is ls2
else:
assert ls is not ls2
class TestLineString_project(TestLineString_project_):
@property
def _is_inplace(self):
return False
def _func(self, ls, from_shape, to_shape):
return ls.project(from_shape, to_shape)
class TestLineString_shift_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, ls, *args, **kwargs):
def _func_impl():
return ls.shift_(*args, **kwargs)
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_shift_along_xy(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
ls_shift = self._func(ls.deepcopy(), x=1, y=2)
assert ls_shift.coords_almost_equals(
[(0+1, 0+2), (1+1, 0+2), (2+1, 1+2)])
def test_inplaceness(self):
ls = ia.LineString([(0, 0), (1, 0)])
ls2 = self._func(ls, y=0)
if self._is_inplace:
assert ls is ls2
else:
assert ls is not ls2
class TestLineString_shift(TestLineString_shift_):
@property
def _is_inplace(self):
return False
def _func(self, ls, *args, **kwargs):
def _func_impl():
return ls.shift(*args, **kwargs)
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_shift_by_positive_args(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
assert self._func(ls.deepcopy(), top=1).coords_almost_equals(
[(0, 1), (1, 1), (2, 2)])
assert self._func(ls.deepcopy(), right=1).coords_almost_equals(
[(-1, 0), (0, 0), (1, 1)])
assert self._func(ls.deepcopy(), bottom=1).coords_almost_equals(
[(0, -1), (1, -1), (2, 0)])
assert self._func(ls.deepcopy(), left=1).coords_almost_equals(
[(1, 0), (2, 0), (3, 1)])
def test_shift_by_negative_values(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
assert self._func(ls.deepcopy(), top=-1).coords_almost_equals(
[(0, -1), (1, -1), (2, 0)])
assert self._func(ls.deepcopy(), right=-1).coords_almost_equals(
[(1, 0), (2, 0), (3, 1)])
assert self._func(ls.deepcopy(), bottom=-1).coords_almost_equals(
[(0, 1), (1, 1), (2, 2)])
assert self._func(ls.deepcopy(), left=-1).coords_almost_equals(
[(-1, 0), (0, 0), (1, 1)])
def test_shift_by_positive_values_all_arguments_provided(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
assert self._func(
ls.deepcopy(), top=1, right=2, bottom=3, left=4
).coords_almost_equals(
[(0-2+4, 0+1-3), (1-2+4, 0+1-3), (2-2+4, 1+1-3)])
def test_shift_of_empty_line_string(self):
ls = LineString([])
assert self._func(
ls.deepcopy(), top=1, right=2, bottom=3, left=4
).coords_almost_equals([])
class TestLineString(unittest.TestCase):
def setUp(self):
reseed()
def test___init___float32_array_as_coords(self):
ls = LineString(np.float32([[0, 0], [1, 2]]))
assert np.allclose(ls.coords, np.float32([[0, 0], [1, 2]]))
assert ls.label is None
def test___init___list_of_tuples_as_coords(self):
ls = LineString([(0, 0), (1, 2)])
assert np.allclose(ls.coords, np.float32([[0, 0], [1, 2]]))
assert ls.label is None
def test___init___empty_list_as_coords(self):
ls = LineString([])
assert ls.coords.shape == (0, 2)
assert ls.label is None
def test___init___label_set(self):
ls = LineString([], label="test")
assert ls.coords.shape == (0, 2)
assert ls.label == "test"
def test_length_with_triangle(self):
ls = LineString(np.float32([[0, 0], [1, 0], [1, 1]]))
assert np.isclose(ls.length, 2.0)
def test_length_with_realworld_line_string(self):
ls = LineString(np.float32([[0, 0], [1, 2], [4, 5]]))
assert np.isclose(ls.length,
np.sqrt(1**2 + 2**2) + np.sqrt(3**2 + 3**2))
def test_length_with_single_point(self):
ls = LineString([(0, 0)])
assert np.isclose(ls.length, 0.0)
def test_length_with_zero_points(self):
ls = LineString([])
assert np.isclose(ls.length, 0.0)
def test_xx_three_points(self):
ls = LineString(np.float32([[0, 0], [1, 0], [2, 1]]))
assert np.allclose(ls.xx, np.float32([0, 1, 2]))
def test_xx_no_points(self):
ls = LineString([])
assert np.allclose(ls.xx, np.zeros((0,), dtype=np.float32))
def test_yy_three_points(self):
ls = LineString(np.float32([[0, 0], [0, 1], [0, 2]]))
assert np.allclose(ls.yy, np.float32([0, 1, 2]))
def test_yy_no_points(self):
ls = LineString([])
assert np.allclose(ls.yy, np.zeros((0,), dtype=np.float32))
def test_xx_int_three_points(self):
ls = LineString(np.float32([[0, 0], [1.4, 0], [2.6, 1]]))
assert ls.xx_int.dtype.name == "int32"
assert np.array_equal(ls.xx_int, np.int32([0, 1, 3]))
def test_xx_int_no_points(self):
ls = LineString([])
assert ls.xx_int.dtype.name == "int32"
assert np.array_equal(ls.xx_int, np.zeros((0,), dtype=np.int32))
def test_yy_int_three_points(self):
ls = LineString(np.float32([[0, 0], [0, 1.4], [1, 2.6]]))
assert ls.yy_int.dtype.name == "int32"
assert np.array_equal(ls.yy_int, np.int32([0, 1, 3]))
def test_yy_int_no_points(self):
ls = LineString([])
assert ls.yy_int.dtype.name == "int32"
assert np.array_equal(ls.yy_int, np.zeros((0,), dtype=np.int32))
def test_height_three_points(self):
ls = LineString(np.float32([[0, 0], [0, 1.4], [1, 2.6]]))
assert np.isclose(ls.height, 2.6)
def test_height_no_points(self):
ls = LineString([])
assert np.isclose(ls.height, 0.0)
def test_width_three_points(self):
ls = LineString(np.float32([[0, 0], [1.4, 0], [2.6, 1]]))
assert np.isclose(ls.width, 2.6)
def test_width_no_points(self):
ls = LineString([])
assert np.isclose(ls.width, 0.0)
def test_get_pointwise_inside_image_mask_with_single_point_tuple(self):
ls = LineString([(0, 0), (1.4, 0), (2.6, 1)])
mask = ls.get_pointwise_inside_image_mask((2, 2))
assert np.array_equal(mask, [True, True, False])
def test_get_pointwise_inside_image_mask_with_array(self):
ls = LineString([(0, 0), (1.4, 0), (2.6, 1)])
mask = ls.get_pointwise_inside_image_mask(
np.zeros((2, 2), dtype=np.uint8))
assert np.array_equal(mask, [True, True, False])
def test_get_pointwise_inside_image_mask_with_single_point_ls(self):
ls = LineString([(0, 0)])
mask = ls.get_pointwise_inside_image_mask((2, 2))
assert np.array_equal(mask, [True])
def test_get_pointwise_inside_image_mask_with_zero_points_ls(self):
ls = LineString([])
mask = ls.get_pointwise_inside_image_mask((2, 2))
assert mask.shape == (0,)
def test_compute_neighbour_distances_three_points(self):
ls = LineString([(0, 0), (1.4, 0), (2.6, 1)])
dists = ls.compute_neighbour_distances()
assert np.allclose(dists, [np.sqrt(1.4**2), np.sqrt(1.2**2+1**2)])
def test_compute_neighbour_distances_two_points(self):
ls = LineString([(0, 0), (1.4, 0)])
dists = ls.compute_neighbour_distances()
assert np.allclose(dists, [np.sqrt(1.4**2)])
def test_compute_neighbour_distances_single_point(self):
ls = LineString([(0, 0)])
dists = ls.compute_neighbour_distances()
assert dists.shape == (0,)
def test_compute_neighbour_distances_zero_points(self):
ls = LineString([])
dists = ls.compute_neighbour_distances()
assert dists.shape == (0,)
def test_compute_pointwise_distances_to_point_at_origin(self):
ls = LineString([(0, 0), (5, 0), (5, 5)])
dists = ls.compute_pointwise_distances((0, 0))
assert np.allclose(dists, [0,
5,
np.sqrt(5**2 + 5**2)])
def test_compute_pointwise_distances_to_point_at_x1_y1(self):
ls = LineString([(0, 0), (5, 0), (5, 5)])
dists = ls.compute_pointwise_distances((1, 1))
assert np.allclose(dists, [np.sqrt(1**2 + 1**2),
np.sqrt(4**2 + 1**2),
np.sqrt(4**2 + 4**2)])
def test_compute_pointwise_distances_from_empty_line_string(self):
ls = LineString([])
dists = ls.compute_pointwise_distances((1, 1))
assert dists == []
def test_compute_pointwise_distances_to_keypoint_at_origin(self):
ls = LineString([(0, 0), (5, 0), (5, 5)])
dists = ls.compute_pointwise_distances(Keypoint(x=0, y=0))
assert np.allclose(dists, [0, 5, np.sqrt(5**2 + 5**2)])
def test_compute_pointwise_distances_to_keypoint_at_x1_y1(self):
ls = LineString([(0, 0), (5, 0), (5, 5)])
dists = ls.compute_pointwise_distances(Keypoint(x=1, y=1))
assert np.allclose(dists, [np.sqrt(1**2 + 1**2),
np.sqrt(4**2 + 1**2),
np.sqrt(4**2 + 4**2)])
def test_compute_pointwise_distances_to_other_line_string_at_origin(self):
# line string
ls = LineString([(0, 0), (5, 0), (5, 5)])
other = LineString([(0, 0)])
dists = ls.compute_pointwise_distances(other)
assert np.allclose(dists, [0,
5,
np.sqrt(5**2 + 5**2)])
def test_compute_pointwise_distances_to_other_line_string_at_x1_y1(self):
ls = LineString([(0, 0), (5, 0), (5, 5)])
other = LineString([(1, 1)])
dists = ls.compute_pointwise_distances(other)
assert np.allclose(dists, [np.sqrt(1**2 + 1**2),
np.sqrt(4**2 + 1**2),
np.sqrt(4**2 + 4**2)])
def test_compute_pointwise_distances_to_other_line_string_two_points(self):
ls = LineString([(0, 0), (5, 0), (5, 5)])
other = LineString([(0, -1), (5, -1)])
dists = ls.compute_pointwise_distances(other)
assert np.allclose(dists, [np.sqrt(0**2 + 1**2),
np.sqrt(0**2 + 1**2),
np.sqrt(0**2 + 6**2)])
def test_compute_pointwise_distances_to_other_empty_line_string(self):
ls = LineString([(0, 0), (5, 0), (5, 5)])
other = LineString([])
dists = ls.compute_pointwise_distances(other, default=False)
assert dists is False
def test_compute_distance_from_three_point_line_string(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
points = [(0, 0), (1, 0), (0, 1), (-0.5, -0.6)]
expecteds = [0, 0, 1, np.sqrt(0.5**2 + 0.6**2)]
for point, expected in zip(points, expecteds):
with self.subTest(point=point):
assert np.isclose(ls.compute_distance(point), expected)
def test_compute_distance_from_single_point_line_string(self):
ls = LineString([(0, 0)])
points = [(0, 0), (-0.5, -0.6)]
expecteds = [0, np.sqrt(0.5**2 + 0.6**2)]
for point, expected in zip(points, expecteds):
with self.subTest(point=point):
assert np.isclose(ls.compute_distance(point), expected)
def test_compute_distance_from_empty_line_string_no_default(self):
ls = LineString([])
assert ls.compute_distance((0, 0)) is None
def test_compute_distance_from_empty_line_string_with_default(self):
ls = LineString([])
assert ls.compute_distance((0, 0), default=-1) == -1
def test_compute_distance_to_keypoint(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
assert np.isclose(ls.compute_distance(ia.Keypoint(x=0, y=1)), 1)
def test_compute_distance_to_line_string(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
points = [
[(0, 0)],
[(0, 1)],
[(0, 0), (0, 1)],
[(-1, -1), (-1, 1)]
]
expecteds = [0, 1, 0, 1]
for point, expected in zip(points, expecteds):
with self.subTest(point=point):
assert np.isclose(ls.compute_distance(LineString(point)),
expected)
def test_compute_distance_to_invalid_datatype(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
with self.assertRaises(ValueError):
assert ls.compute_distance("foo")
def test_contains_tuple(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
points = [(100, 0), (0, 0), (1, 0), (2, 1), (0+1e-8, 0), (0-1, 0)]
expecteds = [False, True, True, True, True, False]
for point, expected in zip(points, expecteds):
with self.subTest(point=point):
assert ls.contains(point) is expected
def test_contains_tuple_max_distance(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
points = [(0+1e-8, 0), (0-1, 0)]
max_distances = [0, 2]
expecteds = [False, True]
for point, max_distance, expected in zip(points, max_distances,
expecteds):
with self.subTest(point=point, max_distance=max_distance):
assert (
ls.contains(point, max_distance=max_distance)
is expected
)
def test_contains_keypoint(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
points = [(100, 0), (0, 0), (1, 0), (2, 1), (0+1e-8, 0), (0-1, 0)]
expecteds = [False, True, True, True, True, False]
for point, expected in zip(points, expecteds):
with self.subTest(point=point):
assert (
ls.contains(Keypoint(x=point[0], y=point[1]))
is expected
)
def test_contains_with_single_point_line_string(self):
ls = LineString([(0, 0)])
assert ls.contains((0, 0))
assert not ls.contains((1, 0))
def test_contains_with_empty_line_string(self):
ls = LineString([])
assert not ls.contains((0, 0))
assert not ls.contains((1, 0))
def test_project_empty_line_string_to_2x_image_size(self):
ls = LineString([])
ls_proj = ls.project((10, 10), (20, 20))
assert ls_proj.coords.shape == (0, 2)
def test_compute_out_of_image_fraction__no_points(self):
ls = LineString([])
image_shape = (100, 200, 3)
factor = ls.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_compute_out_of_image_fraction__one_point(self):
ls = LineString([(1.0, 2.0)])
image_shape = (100, 200, 3)
factor = ls.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_compute_out_of_image_fraction__one_point__ooi(self):
ls = LineString([(-10.0, -20.0)])
image_shape = (100, 200, 3)
factor = ls.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 1.0)
def test_compute_out_of_image_fraction__two_points(self):
ls = LineString([(1.0, 2.0), (10.0, 20.0)])
image_shape = (100, 200, 3)
factor = ls.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_compute_out_of_image_fraction__three_points_at_same_pos(self):
ls = LineString([(10.0, 20.0), (10.0, 20.0), (10.0, 20.0)])
image_shape = (100, 200, 3)
factor = ls.compute_out_of_image_fraction(image_shape)
assert len(ls.coords) == 3
assert np.isclose(factor, 0.0)
def test_compute_out_of_image_fraction__partially_ooi(self):
ls = LineString([(9.0, 1.0), (11.0, 1.0)])
image_shape = (10, 10, 3)
factor = ls.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.5, atol=1e-3)
def test_compute_out_of_image_fraction__leaves_image_multiple_times(self):
ls = LineString([(9.0, 1.0), (11.0, 1.0), (11.0, 3.0),
(9.0, 3.0), (9.0, 5.0), (11.0, 5.0)])
image_shape = (10, 10, 3)
factor = ls.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.5, atol=1e-3)
def test_compute_out_of_image_fraction__fully_ooi(self):
ls = LineString([(15.0, 15.0), (20.0, 15.0)])
image_shape = (10, 10, 3)
factor = ls.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 1.0)
def test_is_fully_within_image_with_simple_line_string(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
assert ls.is_fully_within_image((10, 10))
assert ls.is_fully_within_image((2, 3))
assert not ls.is_fully_within_image((2, 2))
assert not ls.is_fully_within_image((1, 1))
def test_is_fully_within_image_with_negative_coords_line_string(self):
ls = LineString([(-1, 0), (1, 0), (2, 1)])
assert not ls.is_fully_within_image((10, 10))
assert not ls.is_fully_within_image((2, 3))
assert not ls.is_fully_within_image((2, 2))
assert not ls.is_fully_within_image((1, 1))
def test_is_fully_within_image_with_single_point_line_string(self):
ls = LineString([(0, 0)])
assert ls.is_fully_within_image((10, 10))
assert ls.is_fully_within_image((2, 3))
assert ls.is_fully_within_image((2, 2))
assert ls.is_fully_within_image((1, 1))
def test_is_fully_within_image_with_empty_line_string(self):
ls = LineString([])
assert not ls.is_fully_within_image((10, 10))
assert not ls.is_fully_within_image((2, 3))
assert not ls.is_fully_within_image((2, 2))
assert not ls.is_fully_within_image((1, 1))
def test_is_fully_within_image_with_empty_line_string_default_set(self):
ls = LineString([])
assert ls.is_fully_within_image((10, 10), default=True)
assert ls.is_fully_within_image((2, 3), default=True)
assert ls.is_fully_within_image((2, 2), default=True)
assert ls.is_fully_within_image((1, 1), default=True)
assert ls.is_fully_within_image((10, 10), default=None) is None
def test_is_partly_within_image_with_simple_line_string(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
assert ls.is_partly_within_image((10, 10))
assert ls.is_partly_within_image((2, 3))
assert ls.is_partly_within_image((2, 2))
assert ls.is_partly_within_image((1, 1))
def test_is_partly_within_image_with_simple_line_string2(self):
ls = LineString([(1, 0), (2, 0), (3, 1)])
assert ls.is_partly_within_image((10, 10))
assert ls.is_partly_within_image((2, 3))
assert ls.is_partly_within_image((2, 2))
assert not ls.is_partly_within_image((1, 1))
def test_is_partly_within_image_with_ls_cutting_through_image(self):
# line string that cuts through the middle of the image,
# with both points outside of a BB (0, 0), (10, 10)
ls = LineString([(-1, 5), (11, 5)])
assert ls.is_partly_within_image((100, 100))
assert ls.is_partly_within_image((10, 12))
assert ls.is_partly_within_image((10, 10))
assert ls.is_partly_within_image((10, 1))
assert not ls.is_partly_within_image((1, 1))
def test_is_partly_within_image_with_line_string_around_rectangle(self):
# line string around inner rectangle of (-1, -1), (11, 11)
ls = LineString([(-1, -1), (11, -1), (11, 11), (-1, 11)])
assert ls.is_partly_within_image((100, 100))
assert ls.is_partly_within_image((12, 12))
assert not ls.is_partly_within_image((10, 10))
def test_is_partly_within_image_with_single_point_line_string(self):
ls = LineString([(11, 11)])
assert ls.is_partly_within_image((100, 100))
assert ls.is_partly_within_image((12, 12))
assert not ls.is_partly_within_image((10, 10))
def test_is_partly_within_image_with_empty_line_string(self):
ls = LineString([])
assert not ls.is_partly_within_image((100, 100))
assert not ls.is_partly_within_image((10, 10))
assert ls.is_partly_within_image((100, 100), default=True)
assert ls.is_partly_within_image((10, 10), default=True)
assert ls.is_partly_within_image((100, 100), default=None) is None
assert ls.is_partly_within_image((10, 10), default=None) is None
def test_is_out_of_image_with_simple_line_string(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
assert not ls.is_out_of_image((10, 10))
assert ls.is_out_of_image((1, 1), fully=False, partly=True)
assert not ls.is_out_of_image((1, 1), fully=True, partly=False)
assert ls.is_out_of_image((1, 1), fully=True, partly=True)
assert not ls.is_out_of_image((1, 1), fully=False, partly=False)
def test_is_out_of_image_with_empty_line_string(self):
ls = LineString([])
assert ls.is_out_of_image((10, 10))
assert not ls.is_out_of_image((10, 10), default=False)
assert ls.is_out_of_image((10, 10), default=None) is None
def test_clip_out_of_image_with_simple_line_string(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((2, 2))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((2, 1))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, 0), (1, 0)])
def test_clip_out_of_image_with_shifted_simple_line_string(self):
# same as above, all coords at x+5, y+5
ls = LineString([(5, 5), (6, 5), (7, 6)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((4, 4))
assert len(lss_clipped) == 0
def test_clip_out_of_image_with_ls_partially_outside_image(self):
# line that leaves image plane and comes back
ls = LineString([(0, 0), (1, 0), (3, 0),
(3, 2), (1, 2), (0, 2)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((10, 2))
assert len(lss_clipped) == 2
assert _coords_eq(lss_clipped[0], [(0, 0), (1, 0), (2, 0)])
assert _coords_eq(lss_clipped[1], [(2, 2), (1, 2), (0, 2)])
lss_clipped = ls.clip_out_of_image((10, 1))
assert len(lss_clipped) == 2
assert _coords_eq(lss_clipped[0], [(0, 0), (1, 0)])
assert _coords_eq(lss_clipped[1], [(1, 2), (0, 2)])
def test_clip_out_of_image_with_ls_partially_ooi_less_points(self):
# same as above, but removing first and last point
# so that only one point before and after out of image part remain
ls = LineString([(1, 0), (3, 0),
(3, 2), (1, 2)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((10, 2))
assert len(lss_clipped) == 2
assert _coords_eq(lss_clipped[0], [(1, 0), (2, 0)])
assert _coords_eq(lss_clipped[1], [(2, 2), (1, 2)])
lss_clipped = ls.clip_out_of_image((10, 1))
assert len(lss_clipped) == 0
def test_clip_out_of_image_when_only_one_point_remains(self):
# same as above, but only one point out of image remains
ls = LineString([(1, 0), (3, 0),
(1, 2)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((10, 2))
assert len(lss_clipped) == 2
assert _coords_eq(lss_clipped[0], [(1, 0), (2, 0)])
assert _coords_eq(lss_clipped[1], [(2, 1), (1, 2)])
lss_clipped = ls.clip_out_of_image((10, 1))
assert len(lss_clipped) == 0
def test_clip_out_of_image_with_ls_leaving_image_multiple_times(self):
# line string that leaves image, comes back, then leaves again, then
# comes back again
ls = LineString([(1, 0), (3, 0), # leaves
(3, 1), (1, 1), # comes back
(1, 2), (3, 2), # leaves
(3, 3), (1, 3)]) # comes back
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((10, 2))
assert len(lss_clipped) == 3 # from above: 1s line, 2nd+3rd, 4th
assert _coords_eq(lss_clipped[0], [(1, 0), (2, 0)])
assert _coords_eq(lss_clipped[1], [(2, 1), (1, 1), (1, 2), (2, 2)])
assert _coords_eq(lss_clipped[2], [(2, 3), (1, 3)])
def test_clip_out_of_image_with_ls_that_enters_image_from_outside(self):
# line string that starts out of image and ends within the image plane
for y in [1, 0]:
with self.subTest(y=y):
# one point inside image
ls = LineString([(-10, y), (3, y)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (3, y)])
lss_clipped = ls.clip_out_of_image((2, 1))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (1, y)])
lss_clipped = ls.clip_out_of_image((1, 1))
if y == 1:
assert len(lss_clipped) == 0
else:
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (1, y)])
# two points inside image
ls = LineString([(-10, y), (3, y), (5, y)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (3, y), (5, y)])
lss_clipped = ls.clip_out_of_image((10, 4))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (3, y), (4, y)])
lss_clipped = ls.clip_out_of_image((2, 1))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (1, y)])
lss_clipped = ls.clip_out_of_image((1, 1))
if y == 1:
assert len(lss_clipped) == 0
else:
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (1, y)])
def test_clip_out_of_image_with_ls_that_leaves_image_from_inside(self):
# line string that starts within the image plane and ends outside
for y in [1, 0]:
with self.subTest(y=y):
# one point inside image
ls = LineString([(2, y), (5, y)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(2, y), (5, y)])
lss_clipped = ls.clip_out_of_image((10, 4))
assert _coords_eq(lss_clipped[0], [(2, y), (4, y)])
# two points inside image
ls = LineString([(1, y), (2, y), (5, y)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(1, y), (2, y), (5, y)])
lss_clipped = ls.clip_out_of_image((10, 4))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(1, y), (2, y), (4, y)])
lss_clipped = ls.clip_out_of_image((2, 1))
assert len(lss_clipped) == 0
# two points outside image
ls = LineString([(2, y), (5, y), (6, y)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(2, y), (5, y), (6, y)])
lss_clipped = ls.clip_out_of_image((10, 4))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(2, y), (4, y)])
lss_clipped = ls.clip_out_of_image((2, 1))
assert len(lss_clipped) == 0
def test_clip_out_of_image_with_ls_that_cuts_through_image(self):
# line string that cuts through the image plane in the center
for y in [1, 0]:
ls = LineString([(-5, y), (5, y)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (5, y)])
lss_clipped = ls.clip_out_of_image((4, 4))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, y), (4, y)])
def test_clip_out_of_image_with_ls_that_runs_through_diagonal_corners(self):
# line string that cuts through the image plane from the bottom left
# corner to the top right corner
ls = LineString([(-5, -5), (5, 5)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, 0), (5, 5)])
lss_clipped = ls.clip_out_of_image((4, 4))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, 0), (4, 4)])
def test_clip_out_of_image_with_ls_that_overlaps_with_image_edge(self):
# line string that overlaps with the bottom edge
ls = LineString([(1, 0), (4, 0)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((3, 3))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(1, 0), (3, 0)])
def test_clip_out_of_image_with_ls_that_overlaps_with_image_edge2(self):
# same as above, multiple points on line
ls = LineString([(1, 0), (4, 0), (5, 0), (6, 0), (7, 0)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((5, 5))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(1, 0), (4, 0), (5, 0)])
lss_clipped = ls.clip_out_of_image((5, 4))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(1, 0), (4, 0)])
lss_clipped = ls.clip_out_of_image((5, 2))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(1, 0), (2, 0)])
def test_clip_out_of_image_with_ls_that_overlaps_with_image_edge3(self):
# line string that starts outside the image, intersects with the
# bottom left corner and overlaps with the bottom border
ls = LineString([(-5, 0), (5, 0)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, 0), (5, 0)])
lss_clipped = ls.clip_out_of_image((10, 5))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, 0), (5, 0)])
lss_clipped = ls.clip_out_of_image((10, 4))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], [(0, 0), (4, 0)])
def test_clip_out_of_image_with_ls_that_contains_a_single_point(self):
# line string that contains a single point
ls = LineString([(2, 2)])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 1
assert _coords_eq(lss_clipped[0], ls)
lss_clipped = ls.clip_out_of_image((1, 1))
assert len(lss_clipped) == 0
def test_clip_out_of_image_with_ls_that_is_empty(self):
# line string that is empty
ls = LineString([])
lss_clipped = ls.clip_out_of_image((10, 10))
assert len(lss_clipped) == 0
def test_clip_out_of_image_and_is_fully_within_image(self):
# combine clip + is_fully_within_image
sizes = [(200, 400), (400, 800), (800, 1600), (1600, 3200),
(3200, 6400)]
sizes = sizes + [(w, h) for h, w in sizes]
for h, w in sizes:
ls = LineString([(0, 10), (w, 10), (w, h), (w-10, h-10)])
lss_clipped = ls.clip_out_of_image((h, w))
assert len(lss_clipped) == 2
assert lss_clipped[0].is_fully_within_image((h, w))
assert lss_clipped[1].is_fully_within_image((h, w))
ls = LineString([(0, 10), (w+10, 10), (w+10, h-10), (w-10, h-10)])
lss_clipped = ls.clip_out_of_image((h, w))
assert len(lss_clipped) == 2
assert lss_clipped[0].is_fully_within_image((h, w))
assert lss_clipped[1].is_fully_within_image((h, w))
ls = LineString([(-10, 10), (w+10, 10), (w-10, h-10)])
lss_clipped = ls.clip_out_of_image((h, w))
assert len(lss_clipped) == 2
assert lss_clipped[0].is_fully_within_image((h, w))
assert lss_clipped[1].is_fully_within_image((h, w))
def test_draw_mask(self):
ls = LineString([(0, 1), (5, 1), (5, 5)])
arr = ls.draw_mask(
(10, 10), size_lines=1, size_points=0, raise_if_out_of_image=False)
assert np.all(arr[1, 0:5])
assert np.all(arr[1:5, 5])
assert not np.any(arr[0, :])
assert not np.any(arr[2:, 0:5])
def test_draw_mask_of_empty_line_string(self):
ls = LineString([])
arr = ls.draw_mask(
(10, 10), size_lines=1, raise_if_out_of_image=False)
assert not np.any(arr)
def test_draw_line_heatmap_array(self):
ls = LineString([(0, 1), (5, 1), (5, 5)])
arr = ls.draw_lines_heatmap_array(
(10, 10), alpha=0.5, size=1, raise_if_out_of_image=False)
assert _drawing_allclose(arr[1, 0:5], 0.5)
assert _drawing_allclose(arr[1:5, 5], 0.5)
assert _drawing_allclose(arr[0, :], 0.0)
assert _drawing_allclose(arr[2:, 0:5], 0.0)
def test_draw_line_heatmap_array_with_empty_line_string(self):
ls = LineString([])
arr = ls.draw_lines_heatmap_array(
(10, 10), alpha=0.5, size=1, raise_if_out_of_image=False)
assert _drawing_allclose(arr, 0.0)
def test_draw_points_heatmap_array(self):
ls = LineString([(0, 1), (5, 1), (5, 5)])
arr = ls.draw_points_heatmap_array(
(10, 10), alpha=0.5, size=1, raise_if_out_of_image=False)
assert _drawing_allclose(arr[1, 0], 0.5)
assert _drawing_allclose(arr[1, 5], 0.5)
assert _drawing_allclose(arr[5, 5], 0.5)
assert _drawing_allclose(arr[0, :], 0.0)
assert _drawing_allclose(arr[2:, 0:5], 0.0)
def test_draw_points_heatmap_array_with_empty_line_string(self):
ls = LineString([])
arr = ls.draw_points_heatmap_array(
(10, 10), alpha=0.5, size=1, raise_if_out_of_image=False)
assert _drawing_allclose(arr, 0.0)
def test_draw_heatmap_array_calls_other_drawing_functions(self):
ls = LineString([(0, 1), (9, 1)])
module_name = "imgaug.augmentables.lines."
line_fname = "%sLineString.draw_lines_heatmap_array" % (module_name,)
points_fname = "%sLineString.draw_points_heatmap_array" % (module_name,)
with mock.patch(line_fname, return_value=1) as mock_line, \
mock.patch(points_fname, return_value=2) as mock_points:
_arr = ls.draw_heatmap_array(
(10, 10),
alpha_lines=0.9, alpha_points=0.8,
size_lines=3, size_points=5,
antialiased=True,
raise_if_out_of_image=True)
assert mock_line.call_count == 1
assert mock_points.call_count == 1
assert mock_line.call_args_list[0][0][0] == (10, 10)
assert np.isclose(mock_line.call_args_list[0][1]["alpha"], 0.9)
assert mock_line.call_args_list[0][1]["size"] == 3
assert mock_line.call_args_list[0][1]["antialiased"] is True
assert mock_line.call_args_list[0][1]["raise_if_out_of_image"] \
is True
assert mock_points.call_args_list[0][0][0] == (10, 10)
assert np.isclose(mock_points.call_args_list[0][1]["alpha"], 0.8)
assert mock_points.call_args_list[0][1]["size"] == 5
assert mock_points.call_args_list[0][1]["raise_if_out_of_image"] \
is True
def test_draw_heatmap_array_without_mocking(self):
ls = LineString([(0, 1), (5, 1), (5, 5)])
arr = ls.draw_heatmap_array((10, 10),
alpha_lines=0.9, alpha_points=0.5,
size_lines=1, size_points=3,
antialiased=False,
raise_if_out_of_image=False)
assert _drawing_allclose(arr[1, 0:5], 0.9)
assert _drawing_allclose(arr[1, 0:5], 0.9)
assert _drawing_allclose(arr[1, 0:5], 0.9)
assert _drawing_allclose(arr[2:5, 5], 0.9)
assert _drawing_allclose(arr[2:5, 5], 0.9)
assert _drawing_allclose(arr[2:5, 5], 0.9)
assert _drawing_allclose(arr[0, 0:2], 0.5)
assert _drawing_allclose(arr[2, 0:2], 0.5)
assert _drawing_allclose(arr[0, 4:6 + 1], 0.5)
assert _drawing_allclose(arr[2, 4], 0.5)
assert _drawing_allclose(arr[2, 6], 0.5)
assert _drawing_allclose(arr[4, 4], 0.5)
assert _drawing_allclose(arr[4, 6], 0.5)
assert _drawing_allclose(arr[6, 4:6 + 1], 0.5)
assert _drawing_allclose(arr[0, 3], 0.0)
assert _drawing_allclose(arr[7:, :], 0.0)
def test_draw_heatmap_array_with_empty_line_string(self):
ls = LineString([])
arr = ls.draw_heatmap_array((10, 10))
assert arr.shape == (10, 10)
assert np.sum(arr) == 0
def test_draw_line_on_image_with_image_of_zeros(self):
# image of 0s
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_lines_on_image(
np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=1.0, size=1,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[0, :, :] == 0)
assert np.all(img[1, :, 0] == 10)
assert np.all(img[1, :, 1] == 200)
assert np.all(img[1, :, 2] == 20)
assert np.all(img[2, :, :] == 0)
def test_draw_line_on_image_with_2d_image_of_zeros(self):
# image of 0s, 2D input image
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_lines_on_image(
np.zeros((3, 10), dtype=np.uint8),
color=200,
alpha=1.0, size=1,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[0, :] == 0)
assert np.all(img[1, :] == 200)
assert np.all(img[2, :] == 0)
def test_draw_line_on_image_of_ones(self):
# image of 1s
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_lines_on_image(
np.ones((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=1.0, size=1,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[0, :, :] == 1)
assert np.all(img[1, :, 0] == 10)
assert np.all(img[1, :, 1] == 200)
assert np.all(img[1, :, 2] == 20)
assert np.all(img[2, :, :] == 1)
def test_draw_line_on_image_alpha_at_50_percent(self):
# alpha=0.5
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_lines_on_image(
np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=0.5, size=1,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[0, :, :] == 0)
assert np.all(img[1, :, 0] == 5)
assert np.all(img[1, :, 1] == 100)
assert np.all(img[1, :, 2] == 10)
assert np.all(img[2, :, :] == 0)
def test_draw_line_on_image_alpha_at_50_percent_with_background(self):
# alpha=0.5 with background
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_lines_on_image(
10 + np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=0.5, size=1,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[0, :, :] == 10)
assert np.all(img[1, :, 0] == 5+5)
assert np.all(img[1, :, 1] == 5+100)
assert np.all(img[1, :, 2] == 5+10)
assert np.all(img[2, :, :] == 10)
def test_draw_line_on_image_with_size_3(self):
# size=3
ls = LineString([(0, 5), (9, 5)])
img = ls.draw_lines_on_image(
np.zeros((10, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=1.0, size=3,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[5-1:5+1+1, :, 0] == 10)
assert np.all(img[5-1:5+1+1, :, 1] == 200)
assert np.all(img[5-1:5+1+1, :, 2] == 20)
assert np.all(img[:5-1, :, :] == 0)
assert np.all(img[5+1+1:, :, :] == 0)
def test_draw_line_on_image_with_2d_image_and_size_3(self):
# size=3, 2D input image
ls = LineString([(0, 5), (9, 5)])
img = ls.draw_lines_on_image(
np.zeros((10, 10), dtype=np.uint8),
color=200,
alpha=1.0, size=3,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[5-1:5+1+1, :] == 200)
assert np.all(img[:5-1, :] == 0)
assert np.all(img[5+1+1:, :] == 0)
def test_draw_line_on_image_with_size_3_and_antialiasing(self):
# size=3, antialiasing
ls = LineString([(0, 0), (9, 9)])
img = ls.draw_lines_on_image(
np.zeros((10, 10, 3), dtype=np.uint8),
color=(100, 100, 100),
alpha=1.0, size=3,
antialiased=False,
raise_if_out_of_image=False
)
img_aa = ls.draw_lines_on_image(
np.zeros((10, 10, 3), dtype=np.uint8),
color=(100, 100, 100),
alpha=1.0, size=3,
antialiased=True,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert img_aa.dtype.name == "uint8"
assert np.sum(img) > 5 * 3 * 100
assert np.sum(img_aa) > 5 * 3 * 100
assert not np.array_equal(img, img_aa)
assert np.all(img[:3, -3:, :] == 0)
assert np.all(img_aa[:3, -3:, :] == 0)
assert np.all(img[-3:, :3, :] == 0)
assert np.all(img_aa[-3:, :3, :] == 0)
def test_draw_line_on_image_with_line_partially_outside_image(self):
# line partially outside if image
ls = LineString([(-1, 1), (9, 1)])
img = ls.draw_lines_on_image(
np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=1.0, size=1,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[0, :, :] == 0)
assert np.all(img[1, :, 0] == 10)
assert np.all(img[1, :, 1] == 200)
assert np.all(img[1, :, 2] == 20)
assert np.all(img[2, :, :] == 0)
def test_draw_line_on_image_with_line_fully_outside_image(self):
# line fully outside if image
ls = LineString([(-10, 1), (-9, 1)])
img = ls.draw_lines_on_image(
np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=1.0, size=1,
antialiased=False,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img == 0)
def test_draw_line_on_image_with_line_fully_ooi_and_raise_true(self):
# raise_if_out_of_image=True
ls = LineString([(0-5, 5), (-1, 5)])
with self.assertRaises(Exception) as context:
_img = ls.draw_lines_on_image(
np.zeros((10, 10, 3), dtype=np.uint8),
color=(100, 100, 100),
alpha=1.0, size=3,
antialiased=False,
raise_if_out_of_image=True
)
assert "Cannot draw line string " in str(context.exception)
def test_draw_line_on_image_with_line_part_inside_img_and_raise_true(self):
# raise_if_out_of_image=True BUT line is partially inside image
# (no point is inside image though)
ls = LineString([(-1, 5), (11, 5)])
_img = ls.draw_lines_on_image(
np.zeros((10, 10, 3), dtype=np.uint8),
color=(100, 100, 100),
alpha=1.0, size=3,
antialiased=False,
raise_if_out_of_image=True
)
def test_draw_points_on_image_with_image_of_zeros(self):
# iamge of 0s
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_points_on_image(
np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 255, 20),
alpha=1.0, size=3,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[0:2, 0:2, 0] == 10)
assert np.all(img[0:2, 0:2, 1] == 255)
assert np.all(img[0:2, 0:2, 2] == 20)
assert np.all(img[0:2, -2:, 0] == 10)
assert np.all(img[0:2, -2:, 1] == 255)
assert np.all(img[0:2, -2:, 2] == 20)
assert np.all(img[:, 2:-2, :] == 0)
def test_draw_points_on_image_with_image_of_ones(self):
# image of 1s
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_points_on_image(
np.ones((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=1.0, size=3,
raise_if_out_of_image=False
)
assert img.dtype.name == "uint8"
assert np.all(img[0:2, 0:2, 0] == 10)
assert np.all(img[0:2, 0:2, 1] == 200)
assert np.all(img[0:2, 0:2, 2] == 20)
assert np.all(img[0:2, -2:, 0] == 10)
assert np.all(img[0:2, -2:, 1] == 200)
assert np.all(img[0:2, -2:, 2] == 20)
assert np.all(img[:, 2:-2, :] == 1)
def test_draw_points_on_image_with_alpha_50_percent(self):
# alpha=0.5
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_points_on_image(
np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=0.5, size=3,
raise_if_out_of_image=False
)
assert np.all(img[0:2, 0:2, 0] == 5)
assert np.all(img[0:2, 0:2, 1] == 100)
assert np.all(img[0:2, 0:2, 2] == 10)
assert np.all(img[0:2, -2:, 0] == 5)
assert np.all(img[0:2, -2:, 1] == 100)
assert np.all(img[0:2, -2:, 2] == 10)
assert np.all(img[:, 2:-2, :] == 0)
def test_draw_points_on_image_with_size_one(self):
# size=1
ls = LineString([(0, 1), (9, 1)])
img = ls.draw_points_on_image(
np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=1.0, size=1,
raise_if_out_of_image=False
)
assert np.all(img[0, :, :] == 0)
assert np.all(img[2, :, :] == 0)
assert np.all(img[1, 0, 0] == 10)
assert np.all(img[1, 0, 1] == 200)
assert np.all(img[1, 0, 2] == 20)
assert np.all(img[1, -1, 0] == 10)
assert np.all(img[1, -1, 1] == 200)
assert np.all(img[1, -1, 2] == 20)
def test_draw_points_on_image_with_ls_ooi_and_raise_true(self):
with self.assertRaises(Exception) as context:
ls = LineString([(0-5, 1), (9+5, 1)])
_img = ls.draw_points_on_image(
np.zeros((3, 10, 3), dtype=np.uint8),
color=(10, 200, 20),
alpha=0.5, size=1,
raise_if_out_of_image=True
)
assert "Cannot draw keypoint " in str(context.exception)
def test_draw_on_image_with_mocking(self):
ls = LineString([(0, 1), (9, 1)])
module_name = "imgaug.augmentables.lines."
line_fname = "%sLineString.draw_lines_on_image" % (module_name,)
points_fname = "%sLineString.draw_points_on_image" % (module_name,)
with mock.patch(line_fname, return_value=1) as mock_line, \
mock.patch(points_fname, return_value=2) as mock_points:
_image = ls.draw_on_image(
np.zeros((10, 10, 3), dtype=np.uint8),
color=(1, 2, 3), color_lines=(4, 5, 6), color_points=(7, 8, 9),
alpha=1.0, alpha_lines=0.9, alpha_points=0.8,
size=1, size_lines=3, size_points=5,
antialiased=False,
raise_if_out_of_image=True)
assert mock_line.call_count == 1
assert mock_points.call_count == 1
assert mock_line.call_args_list[0][0][0].shape == (10, 10, 3)
assert mock_line.call_args_list[0][1]["color"][0] == 4
assert mock_line.call_args_list[0][1]["color"][1] == 5
assert mock_line.call_args_list[0][1]["color"][2] == 6
assert np.isclose(mock_line.call_args_list[0][1]["alpha"], 0.9)
assert mock_line.call_args_list[0][1]["size"] == 3
assert mock_line.call_args_list[0][1]["antialiased"] is False
assert mock_line.call_args_list[0][1]["raise_if_out_of_image"] \
is True
assert mock_points.call_args_list[0][0][0] == 1 # mock_line result
assert mock_points.call_args_list[0][1]["color"][0] == 7
assert mock_points.call_args_list[0][1]["color"][1] == 8
assert mock_points.call_args_list[0][1]["color"][2] == 9
assert np.isclose(mock_points.call_args_list[0][1]["alpha"], 0.8)
assert mock_points.call_args_list[0][1]["size"] == 5
assert mock_points.call_args_list[0][1]["raise_if_out_of_image"] \
is True
def test_draw_on_image_without_mocking(self):
ls = LineString([(0, 1), (5, 1), (5, 5)])
img = ls.draw_on_image(np.zeros((10, 10, 3), dtype=np.uint8),
color=(200, 120, 40), alpha=0.5, size=1)
assert np.all(img[1, 0:5, 0] == 100)
assert np.all(img[1, 0:5, 1] == 60)
assert np.all(img[1, 0:5, 2] == 20)
assert np.all(img[1:5, 5, 0] == 100)
assert np.all(img[1:5, 5, 1] == 60)
assert np.all(img[1:5, 5, 2] == 20)
assert np.all(img[0:2+1, 0:2, 0] >= 50) # color_points is 0.5*color
assert np.all(img[0:2+1, 0:2, 1] >= 30)
assert np.all(img[0:2+1, 0:2, 2] >= 10)
assert np.all(img[0:2+1, 4:6+1, 0] >= 50)
assert np.all(img[0:2+1, 4:6+1, 1] >= 30)
assert np.all(img[0:2+1, 4:6+1, 2] >= 10)
assert np.all(img[4:6+1, 4:6+1, 0] >= 50)
assert np.all(img[4:6+1, 4:6+1, 1] >= 30)
assert np.all(img[4:6+1, 4:6+1, 2] >= 10)
assert np.all(img[0, 3, :] == 0)
assert np.all(img[7:, :, :] == 0)
def test_draw_on_image_with_empty_line_string(self):
ls = LineString([])
img = ls.draw_on_image(np.zeros((10, 10, 3), dtype=np.uint8))
assert img.shape == (10, 10, 3)
assert np.sum(img) == 0
def test_extract_from_image_size_1_single_channel(self):
ls = LineString([(0, 5), (9, 5)])
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
extract = ls.extract_from_image(img, antialiased=False)
assert extract.shape == (1, 10, 1)
assert np.array_equal(extract, img[5:6, 0:10, :])
def test_extract_from_image_size_3_single_channel(self):
ls = LineString([(1, 5), (8, 5)])
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
extract = ls.extract_from_image(img, size=3, antialiased=False)
assert extract.shape == (3, 10, 1)
assert np.array_equal(extract, img[4:6+1, 0:10, :])
def test_extract_from_image_size_3_rgb(self):
# size=3, RGB image
ls = LineString([(1, 5), (8, 5)])
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
img_rgb = np.tile(img, (1, 1, 3))
img_rgb[..., 1] += 10
img_rgb[..., 2] += 20
extract = ls.extract_from_image(img_rgb, size=3, antialiased=False)
assert extract.shape == (3, 10, 3)
assert np.array_equal(extract, img_rgb[4:6+1, 0:10, :])
def test_extract_from_image_antialiased_true(self):
# weak antialiased=True test
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([(1, 1), (9, 9)])
extract_aa = ls.extract_from_image(img, size=3, antialiased=True)
extract = ls.extract_from_image(img, size=3, antialiased=False)
assert extract_aa.shape == extract.shape
assert np.sum(extract_aa) > np.sum(extract)
def test_extract_from_image_pad_false(self):
# pad=False
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([(-5, 5), (-3, 5)])
extract = ls.extract_from_image(img, size=1, antialiased=False,
pad=False, prevent_zero_size=True)
assert extract.shape == (1, 1, 1)
assert np.sum(extract) == 0
def test_extract_from_image_pad_false_and_prevent_zero_size_false(self):
# pad=False, prevent_zero_size=False
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([(-5, 5), (-3, 5)])
extract = ls.extract_from_image(img, size=1, antialiased=False,
pad=False, prevent_zero_size=False)
assert extract.shape == (0, 0, 1)
def test_extract_from_image_pad_max(self):
# pad_max=1
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([(-5, 5), (9, 5)])
extract = ls.extract_from_image(img, antialiased=False, pad=True,
pad_max=1)
assert extract.shape == (1, 11, 1)
assert np.array_equal(extract[:, 1:], img[5:6, 0:10, :])
assert np.all(extract[0, 0, :] == 0)
def test_extract_from_image_with_single_point_line_string(self):
# 1 coord
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([(1, 1)])
extract = ls.extract_from_image(img)
assert extract.shape == (1, 1, 1)
assert np.sum(extract) == img[1:2, 1:2, :]
def test_extract_from_image_with_single_point_ls_negative_coords(self):
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([(-10, -10)])
extract = ls.extract_from_image(img)
assert extract.shape == (1, 1, 1)
assert np.sum(extract) == 0
def test_extract_from_image_with_1_point_neg_coords_prevent_zero_size(self):
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([(-10, -10)])
extract = ls.extract_from_image(img, prevent_zero_size=True)
assert extract.shape == (1, 1, 1)
assert np.sum(extract) == 0
def test_extract_from_image_with_empty_line_string(self):
# 0 coords
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([])
extract = ls.extract_from_image(img)
assert extract.shape == (1, 1, 1)
assert np.sum(extract) == 0
def test_extract_from_image_with_empty_line_string_prevent_zero_size(self):
img = np.arange(10*10).reshape((10, 10, 1)).astype(np.uint8)
ls = LineString([])
extract = ls.extract_from_image(img, prevent_zero_size=False)
assert extract.shape == (0, 0, 1)
assert np.sum(extract) == 0
def test_concatenate_line_string_with_itself(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
ls_concat = ls.concatenate(ls)
assert ls_concat.coords_almost_equals([
(0, 0), (1, 0), (2, 1), (0, 0), (1, 0), (2, 1)
])
def test_concatenate_empty_line_string_with_itself(self):
ls = LineString([])
ls_concat = ls.concatenate(ls)
assert ls_concat.coords_almost_equals([])
def test_concatenate_empty_line_string_with_single_point_line_string(self):
ls = LineString([])
ls_concat = ls.concatenate(LineString([(0, 0)]))
assert ls_concat.coords_almost_equals([(0, 0)])
def test_concatenate_single_point_line_string_with_empty_line_string(self):
ls = LineString([(0, 0)])
ls_concat = ls.concatenate(LineString([]))
assert ls_concat.coords_almost_equals([(0, 0)])
def test_concatenate_empty_line_string_with_list_of_tuples(self):
ls = LineString([])
ls_concat = ls.concatenate([(0, 0)])
assert ls_concat.coords_almost_equals([(0, 0)])
def test_to_keypoints(self):
ls = LineString([(0, 0), (1, 0), (2, 1)])
observed = ls.to_keypoints()
assert all([isinstance(kp, ia.Keypoint) for kp in observed])
assert np.isclose(observed[0].x, 0)
assert np.isclose(observed[0].y, 0)
assert np.isclose(observed[1].x, 1)
assert np.isclose(observed[1].y, 0)
assert np.isclose(observed[2].x, 2)
assert np.isclose(observed[2].y, 1)
def test_to_keypoints_with_empty_line_string(self):
ls = LineString([])
assert len(ls.to_keypoints()) == 0
def test_to_bounding_box(self):
ls = LineString([(0, 0), (1, 0), (1, 1)])
observed = ls.to_bounding_box()
assert isinstance(observed, ia.BoundingBox)
assert np.isclose(observed.x1, 0)
assert np.isclose(observed.y1, 0)
assert np.isclose(observed.x2, 1)
assert np.isclose(observed.y2, 1)
def test_to_bounding_box_with_single_point_line_string(self):
ls = LineString([(0, 0)])
observed = ls.to_bounding_box()
assert isinstance(observed, ia.BoundingBox)
assert np.isclose(observed.x1, 0)
assert np.isclose(observed.y1, 0)
assert np.isclose(observed.x2, 0)
assert np.isclose(observed.y2, 0)
def test_to_bounding_box_with_empty_line_string(self):
ls = LineString([])
observed = ls.to_bounding_box()
assert observed is None
def test_to_polygon(self):
ls = LineString([(0, 0), (1, 0), (1, 1)])
observed = ls.to_polygon()
assert isinstance(observed, ia.Polygon)
assert np.allclose(observed.exterior, [(0, 0), (1, 0), (1, 1)])
def test_to_polygon_with_single_point_line_string(self):
ls = LineString([(0, 0)])
observed = ls.to_polygon()
assert isinstance(observed, ia.Polygon)
assert np.allclose(observed.exterior, [(0, 0)])
def test_to_polygon_with_empty_line_string(self):
ls = LineString([])
observed = ls.to_polygon()
assert isinstance(observed, ia.Polygon)
assert len(observed.exterior) == 0
# TODO add antialiased=True test
def test_to_heatmap(self):
ls = LineString([(0, 5), (5, 5)])
observed = ls.to_heatmap((10, 10), antialiased=False)
assert isinstance(observed, HeatmapsOnImage)
assert observed.shape == (10, 10)
assert observed.arr_0to1.shape == (10, 10, 1)
assert np.allclose(observed.arr_0to1[0:5, :, :], 0.0)
assert np.allclose(observed.arr_0to1[5, 0:5, :], 1.0)
assert np.allclose(observed.arr_0to1[6:, :, :], 0.0)
def test_to_heatmap_with_empty_line_string(self):
ls = LineString([])
observed = ls.to_heatmap((5, 5), antialiased=False)
assert observed.shape == (5, 5)
assert observed.arr_0to1.shape == (5, 5, 1)
assert np.allclose(observed.arr_0to1, 0.0)
# TODO change this after the segmap PR was merged
def test_segmentation_map(self):
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
ls = LineString([(0, 5), (5, 5)])
observed = ls.to_segmentation_map((10, 10))
assert isinstance(observed, SegmentationMapsOnImage)
assert observed.shape == (10, 10)
assert observed.arr.shape == (10, 10, 1)
assert np.all(observed.arr[0:5, :, :] == 0)
assert np.all(observed.arr[5, 0:5, :] == 1)
assert np.all(observed.arr[6:, :, :] == 0)
ls = LineString([])
observed = ls.to_segmentation_map((5, 5))
assert observed.shape == (5, 5)
assert observed.arr.shape == (5, 5, 1)
assert np.all(observed.arr == 0)
def test_coords_almost_equals_with_3_point_ls_90deg_angle(self):
ls = LineString([(0, 0), (1, 0), (1, 1)])
assert ls.coords_almost_equals(ls)
assert ls.coords_almost_equals([(0, 0), (1, 0), (1, 1)])
assert not ls.shift(y=1).coords_almost_equals(ls)
assert ls.shift(y=1).coords_almost_equals(ls, max_distance=1.01)
assert ls.coords_almost_equals([(0, 0), (0.5, 0), (1, 0), (1, 1)])
def test_coords_almost_equals_with_4_point_ls_90deg_angle(self):
ls = LineString([(0, 0), (0.5, 0), (1, 0), (1, 1)])
assert ls.coords_almost_equals([(0, 0), (1, 0), (1, 1)])
def test_coords_almost_equals_with_1_point_ls(self):
ls = LineString([(0, 0)])
assert ls.coords_almost_equals([(0, 0)])
assert not ls.coords_almost_equals([(0+1, 0)])
assert ls.coords_almost_equals([(0+1, 0)], max_distance=1.01)
assert not ls.coords_almost_equals([])
def test_coords_almost_equals_with_empty_line_string(self):
ls = LineString([])
assert ls.coords_almost_equals([])
assert not ls.coords_almost_equals([(0, 0)])
def test_coords_almost_equals_with_two_rectangular_line_strings(self):
# two rectangles around height=10, width=10 image,
# both LS closed, second one has more points around the left edge
ls_a = LineString([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)])
ls_b = LineString([(0, 0), (10, 0), (10, 10), (0, 10),
(0, 5.01), (0, 5.0), (0, 4.99), (0, 0)])
assert ls_a.coords_almost_equals(ls_b)
def test_coords_almost_equals_different_ls_but_overlapping_points(self):
# almost the same as in above test
# two rectangles around height=10, width=10 image,
# both LS closed, second one has more points around the left edge
# AND around left edge the second line string suddenly has a line
# up to the right edge, which immediately returns back to the left
# edge
# All points overlap between the lines, i.e. this test fails if naively
# checking for overlapping points.
ls_a = LineString([(0, 0), (10, 0), (10, 10), (0, 10), (0, 0)])
ls_b = LineString([(0, 0), (10, 0), (10, 10), (0, 10),
(0, 5.01), (10, 5.0), (0, 4.99), (0, 0)])
assert not ls_a.coords_almost_equals(ls_b)
def test_almost_equals_three_point_ls_without_label(self):
ls = LineString([(0, 0), (1, 0), (1, 1)])
ls_shifted = ls.shift(y=1)
assert ls.almost_equals(ls)
assert not ls.almost_equals(ls_shifted)
assert ls.almost_equals(LineString([(0, 0), (1, 0), (1, 1)],
label=None))
assert not ls.almost_equals(LineString([(0, 0), (1, 0), (1, 1)],
label="foo"))
def test_almost_equals_three_point_ls_with_label(self):
ls = LineString([(0, 0), (1, 0), (1, 1)], label="foo")
assert not ls.almost_equals(LineString([(0, 0), (1, 0), (1, 1)],
label=None))
assert ls.almost_equals(LineString([(0, 0), (1, 0), (1, 1)],
label="foo"))
def test_almost_equals_empty_line_string(self):
ls = LineString([])
assert ls.almost_equals(LineString([]))
assert not ls.almost_equals(LineString([], label="foo"))
def test_almost_equals_empty_line_string_with_label(self):
ls = LineString([], label="foo")
assert not ls.almost_equals(LineString([]))
assert ls.almost_equals(LineString([], label="foo"))
def test_copy_with_various_line_strings(self):
coords = [
[(0, 0), (1, 0), (1, 1)],
[(0, 0), (1.5, 0), (1.6, 1)],
[(0, 0)],
[],
[(0, 0), (1, 0), (1, 1)]
]
labels = [None, None, None, None, "foo"]
for coords_i, label in zip(coords, labels):
with self.subTest(coords=coords_i, label=label):
ls = LineString(coords_i, label=label)
observed = ls.copy()
assert observed is not ls
assert observed.coords is ls.coords
assert observed.label is ls.label
def test_copy_with_coords_arg_set(self):
ls = LineString([(0, 0), (1, 0), (1, 1)])
observed = ls.copy(coords=[(0, 0)])
assert observed.coords_almost_equals([(0, 0)])
assert observed.label is None
def test_copy_with_label_arg_set(self):
ls = LineString([(0, 0), (1, 0), (1, 1)])
observed = ls.copy(label="bar")
assert observed.coords is ls.coords
assert observed.label == "bar"
def test_deepcopy_with_various_line_strings(self):
coords = [
[(0, 0), (1, 0), (1, 1)],
[(0, 0), (1.5, 0), (1.6, 1)],
[(0, 0)],
[],
[(0, 0), (1, 0), (1, 1)]
]
labels = [None, None, None, None, "foo"]
for coords_i, label in zip(coords, labels):
with self.subTest(coords=coords_i, label=label):
ls = LineString(coords_i, label=label)
observed = ls.deepcopy()
assert observed is not ls
assert observed.coords is not ls.coords
assert observed.label == ls.label
def test_deepcopy_with_coords_arg_set(self):
ls = LineString([(0, 0), (1, 0), (1, 1)])
observed = ls.deepcopy(coords=[(0, 0)])
assert observed.coords_almost_equals([(0, 0)])
assert observed.label is None
def test_deepcopy_with_label_arg_set(self):
ls = LineString([(0, 0), (1, 0), (1, 1)])
observed = ls.deepcopy(label="bar")
assert observed.coords is not ls.coords
assert observed.label == "bar"
def test_string_conversion(self):
coords = [
[(0, 0), (1, 0), (1, 1)],
[(0, 0), (1.5, 0), (1.6, 1)],
[(0, 0)],
[],
[(0, 0), (1, 0), (1, 1)]
]
labels = [None, None, None, None, "foo"]
for coords_i, label in zip(coords, labels):
with self.subTest(coords=coords_i, label=label):
ls = LineString(coords_i, label=label)
# __str__() is tested more thoroughly in other tests
assert ls.__repr__() == ls.__str__()
def test___getitem__(self):
cba = ia.LineString([(0, 1), (2, 3)])
assert np.allclose(cba[0], (0, 1))
assert np.allclose(cba[1], (2, 3))
def test___getitem___slice(self):
cba = ia.LineString([(0, 1), (2, 3), (4, 5)])
assert np.allclose(cba[1:], [(2, 3), (4, 5)])
def test___iter___two_points(self):
cba = LineString([(1, 2), (3, 4)])
for i, xy in enumerate(cba):
assert i in [0, 1]
if i == 0:
assert np.allclose(xy, (1, 2))
elif i == 1:
assert np.allclose(xy, (3, 4))
assert i == 1
def test___iter___zero_points(self):
cba = LineString([])
i = 0
for xy in cba:
i += 1
assert i == 0
def test___str__(self):
coords = [
[(0, 0), (1, 0), (1, 1)],
[(0, 0), (1.5, 0), (1.6, 1)],
[(0, 0)],
[],
[(0, 0), (1, 0), (1, 1)]
]
labels = [None, None, None, None, "foo"]
expecteds = [
"LineString([(0.00, 0.00), (1.00, 0.00), (1.00, 1.00)], "
"label=None)",
"LineString([(0.00, 0.00), (1.50, 0.00), (1.60, 1.00)], "
"label=None)",
"LineString([(0.00, 0.00)], label=None)",
"LineString([], label=None)",
"LineString([(0.00, 0.00), (1.00, 0.00), (1.00, 1.00)], "
"label=foo)"
]
for coords_i, label, expected in zip(coords, labels, expecteds):
with self.subTest(coords=coords_i, label=label):
ls = LineString(coords_i, label=label)
observed = ls.__str__()
assert observed == expected
class TestLineStringsOnImage_items_setter(unittest.TestCase):
def test_with_list_of_line_strings(self):
ls = [ia.LineString([(0, 0), (1, 0)]),
ia.LineString([(1, 1), (10, 0)])]
lsoi = ia.LineStringsOnImage([], shape=(10, 20, 3))
lsoi.items = ls
assert np.all([
(np.allclose(ls_i.coords, ls_j.coords))
for ls_i, ls_j
in zip(lsoi.line_strings, ls)
])
class TestLineStringsOnImage_on_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, lsoi, to_shape):
return lsoi.on_(to_shape)
def test_on_image_with_same_shape(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
lsoi_proj = self._func(lsoi, (100, 100, 3))
assert np.all([ls_a.coords_almost_equals(ls_b)
for ls_a, ls_b
in zip(lsoi.line_strings, lsoi_proj.line_strings)])
assert lsoi_proj.shape == (100, 100, 3)
def test_on_image_with_2x_size(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
lsoi_proj = self._func(lsoi, (200, 200, 3))
assert lsoi_proj.line_strings[0].coords_almost_equals(
[(0, 0), (1*2, 0), (2*2, 1*2)]
)
assert lsoi_proj.line_strings[1].coords_almost_equals(
[(10*2, 10*2)]
)
assert lsoi_proj.shape == (200, 200, 3)
def test_on_image_with_2x_size_and_empty_list_of_line_strings(self):
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
lsoi_proj = self._func(lsoi, (200, 200, 3))
assert len(lsoi_proj.line_strings) == 0
assert lsoi_proj.shape == (200, 200, 3)
def test_inplaceness(self):
ls = ia.LineString([(0, 0), (1, 0)])
lsoi = LineStringsOnImage([ls], shape=(10, 10, 3))
lsoi2 = self._func(lsoi, (10, 10))
if self._is_inplace:
assert lsoi is lsoi2
else:
assert lsoi is not lsoi2
class TestLineStringsOnImage_on(TestLineStringsOnImage_on_):
@property
def _is_inplace(self):
return False
def _func(self, lsoi, to_shape):
return lsoi.on(to_shape)
class TestLineStringsOnImage_remove_out_of_image_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, lsoi, fully=True, partly=False):
return lsoi.remove_out_of_image_(fully=fully, partly=partly)
def test_remove_out_of_image_with_two_line_strings(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = self._func(lsoi)
assert len(observed.line_strings) == 2
assert observed.line_strings[0] is ls1
assert observed.line_strings[1] is ls2
def test_remove_out_of_image_with_empty_list_of_line_strings(self):
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = self._func(lsoi)
assert len(observed.line_strings) == 0
assert observed.shape == (100, 100, 3)
def test_remove_out_of_image_with_one_empty_line_string(self):
ls1 = LineString([])
lsoi = LineStringsOnImage([ls1], shape=(100, 100, 3))
observed = self._func(lsoi)
assert len(observed.line_strings) == 0
assert observed.shape == (100, 100, 3)
def test_remove_out_of_image_with_one_line_string(self):
ls1 = LineString([(-10, -10), (5, 5)])
lsoi = LineStringsOnImage([ls1], shape=(100, 100, 3))
observed = self._func(lsoi)
assert len(observed.line_strings) == 1
assert observed.line_strings[0] is ls1
assert observed.shape == (100, 100, 3)
def test_remove_out_of_image_remove_even_partial_oois(self):
ls1 = LineString([(-10, -10), (5, 5)])
lsoi = LineStringsOnImage([ls1], shape=(100, 100, 3))
observed = self._func(lsoi, partly=True, fully=True)
assert len(observed.line_strings) == 0
assert observed.shape == (100, 100, 3)
def test_remove_out_of_image_with_one_single_point_line_strings(self):
ls1 = LineString([(-10, -10)])
lsoi = LineStringsOnImage([ls1], shape=(100, 100, 3))
observed = self._func(lsoi)
assert len(observed.line_strings) == 0
assert observed.shape == (100, 100, 3)
def test_remove_out_of_image_partly_false_and_fully_false(self):
ls1 = LineString([(-10, -10)])
lsoi = LineStringsOnImage([ls1], shape=(100, 100, 3))
observed = self._func(lsoi, partly=False, fully=False)
assert len(observed.line_strings) == 1
assert observed.line_strings[0] is ls1
assert observed.shape == (100, 100, 3)
def test_inplaceness(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
lsoi2 = self._func(lsoi)
if self._is_inplace:
assert lsoi is lsoi2
else:
assert lsoi is not lsoi2
class TestLineStringsOnImage_remove_out_of_image(
TestLineStringsOnImage_remove_out_of_image_):
@property
def _is_inplace(self):
return False
def _func(self, lsoi, fully=True, partly=False):
return lsoi.remove_out_of_image(fully=fully, partly=partly)
class TestLineStringsOnImage_clip_out_of_image_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, lsoi):
return lsoi.clip_out_of_image_()
def test_clip_out_of_image_with_two_simple_line_strings(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = self._func(lsoi)
expected = []
expected.extend(ls1.clip_out_of_image((100, 100, 3)))
expected.extend(ls2.clip_out_of_image((100, 100, 3)))
assert len(lsoi.line_strings) == len(expected)
for ls_obs, ls_exp in zip(observed.line_strings, expected):
assert ls_obs.coords_almost_equals(ls_exp)
assert observed.shape == (100, 100, 3)
def test_clip_out_of_image_with_empty_list_of_line_strings(self):
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = self._func(lsoi)
assert len(observed.line_strings) == 0
assert observed.shape == (100, 100, 3)
def test_clip_out_of_image_with_one_empty_line_string(self):
ls1 = LineString([])
lsoi = LineStringsOnImage([ls1], shape=(100, 100, 3))
observed = self._func(lsoi)
assert len(observed.line_strings) == 0
assert observed.shape == (100, 100, 3)
def test_clip_out_of_image_with_single_point_ls_and_negative_coords(self):
ls1 = LineString([(-10, -10)])
lsoi = LineStringsOnImage([ls1], shape=(100, 100, 3))
observed = self._func(lsoi)
assert len(observed.line_strings) == 0
assert observed.shape == (100, 100, 3)
def test_inplaceness(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
lsoi2 = self._func(lsoi)
if self._is_inplace:
assert lsoi is lsoi2
else:
assert lsoi is not lsoi2
class TestLineStringsOnImage_clip_out_of_image(
TestLineStringsOnImage_clip_out_of_image_):
@property
def _is_inplace(self):
return False
def _func(self, lsoi):
return lsoi.clip_out_of_image()
class TestLineStringsOnImage_shift_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, lsoi, *args, **kwargs):
def _func_impl():
return lsoi.shift_(*args, **kwargs)
if len(lsoi.line_strings) == 0:
return _func_impl()
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_shift_two_simple_line_strings_along_xy(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = self._func(lsoi.deepcopy(), x=1, y=2)
assert observed.line_strings[0].coords_almost_equals(
ls1.shift(x=1, y=2)
)
assert observed.line_strings[1].coords_almost_equals(
ls2.shift(x=1, y=2)
)
assert observed.shape == (100, 100, 3)
def test_inplaceness(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
lsoi2 = self._func(lsoi, y=1)
if self._is_inplace:
assert lsoi is lsoi2
else:
assert lsoi is not lsoi2
class TestLineStringsOnImage_shift(TestLineStringsOnImage_shift_):
@property
def _is_inplace(self):
return False
def _func(self, lsoi, *args, **kwargs):
def _func_impl():
return lsoi.shift(*args, **kwargs)
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_shift_with_two_simple_line_strings(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = self._func(lsoi.deepcopy(), top=1, right=2, bottom=3, left=4)
assert observed.line_strings[0].coords_almost_equals([
(0 - 2 + 4, 0 + 1 - 3),
(1 - 2 + 4, 0 + 1 - 3),
(2 - 2 + 4, 1 + 1 - 3)
])
assert observed.line_strings[1].coords_almost_equals([
(10 - 2 + 4, 10 + 1 - 3)
])
assert observed.shape == (100, 100, 3)
def test_shift_with_empty_list_of_line_strings(self):
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = self._func(lsoi, top=1, right=2, bottom=3, left=4)
assert len(observed.line_strings) == 0
assert observed.shape == (100, 100, 3)
# TODO test to_keypoints_on_image()
# test invert_to_keypoints_on_image()
# test to_xy_array()
# test fill_from_xy_array_()
class TestLineStringsOnImage(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
lsoi = LineStringsOnImage([
LineString([]),
LineString([(0, 0), (5, 0)])
], shape=(10, 10, 3))
assert len(lsoi.line_strings) == 2
assert lsoi.shape == (10, 10, 3)
def test___init___shape_is_array(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
with assertWarns(self, ia.DeprecationWarning):
lsoi = LineStringsOnImage([
LineString([]),
LineString([(0, 0), (5, 0)])
], shape=image)
assert len(lsoi.line_strings) == 2
assert lsoi.shape == (10, 10, 3)
def test___init___with_empty_list(self):
lsoi = LineStringsOnImage([], shape=(10, 10))
assert lsoi.line_strings == []
assert lsoi.shape == (10, 10)
def test_items(self):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
items = lsoi.items
assert items == [ls1, ls2]
def test_items_empty(self):
kpsoi = LineStringsOnImage([], shape=(40, 50, 3))
items = kpsoi.items
assert items == []
def test_empty_with_empty_list(self):
lsoi = LineStringsOnImage([], shape=(10, 10, 3))
assert lsoi.empty
def test_empty_with_list_of_empty_line_string(self):
lsoi = LineStringsOnImage([LineString([])], shape=(10, 10, 3))
assert not lsoi.empty
def test_empty_with_list_of_single_point_line_string(self):
lsoi = LineStringsOnImage([LineString([(0, 0)])], shape=(10, 10, 3))
assert not lsoi.empty
def test_from_xy_arrays_single_input_array_with_two_line_strings(self):
arrs = np.float32([
[(0, 0), (10, 10), (5, 10)],
[(5, 5), (15, 15), (10, 15)]
])
lsoi = LineStringsOnImage.from_xy_arrays(arrs, shape=(100, 100, 3))
assert len(lsoi.line_strings) == 2
assert lsoi.line_strings[0].coords_almost_equals(arrs[0])
assert lsoi.line_strings[1].coords_almost_equals(arrs[1])
def test_from_xy_arrays_with_list_of_two_line_string_arrays(self):
arrs = [
np.float32([(0, 0), (10, 10), (5, 10)]),
np.float32([(5, 5), (15, 15), (10, 15), (25, 25)])
]
lsoi = LineStringsOnImage.from_xy_arrays(arrs, shape=(100, 100, 3))
assert len(lsoi.line_strings) == 2
assert lsoi.line_strings[0].coords_almost_equals(arrs[0])
assert lsoi.line_strings[1].coords_almost_equals(arrs[1])
def test_from_xy_arrays_with_empty_3d_array_and_0_points_per_ls(self):
arrs = np.zeros((0, 0, 2), dtype=np.float32)
lsoi = LineStringsOnImage.from_xy_arrays(arrs, shape=(100, 100, 3))
assert len(lsoi.line_strings) == 0
def test_from_xy_arrays_with_empty_3d_array_and_5_points_per_ls(self):
arrs = np.zeros((0, 5, 2), dtype=np.float32)
lsoi = LineStringsOnImage.from_xy_arrays(arrs, shape=(100, 100, 3))
assert len(lsoi.line_strings) == 0
def test_to_xy_arrays_with_two_line_strings_of_same_length(self):
ls1 = LineString([(0, 0), (10, 10), (5, 10)])
ls2 = LineString([(5, 5), (15, 15), (10, 15)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
arrs = lsoi.to_xy_arrays()
assert isinstance(arrs, list)
assert len(arrs) == 2
assert arrs[0].dtype.name == "float32"
assert arrs[1].dtype.name == "float32"
assert np.allclose(arrs, [
[(0, 0), (10, 10), (5, 10)],
[(5, 5), (15, 15), (10, 15)]
])
def test_to_xy_arrays_with_two_line_strings_of_different_lengths(self):
ls1 = LineString([(0, 0), (10, 10), (5, 10)])
ls2 = LineString([(5, 5), (15, 15), (10, 15), (25, 25)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
arrs = lsoi.to_xy_arrays()
assert isinstance(arrs, list)
assert len(arrs) == 2
assert arrs[0].dtype.name == "float32"
assert arrs[1].dtype.name == "float32"
assert np.allclose(arrs[0], [(0, 0), (10, 10), (5, 10)])
assert np.allclose(arrs[1], [(5, 5), (15, 15), (10, 15), (25, 25)])
def test_to_xy_arrays_with_two_line_strings_one_of_them_empty(self):
ls1 = LineString([])
ls2 = LineString([(5, 5), (15, 15), (10, 15), (25, 25)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
arrs = lsoi.to_xy_arrays()
assert isinstance(arrs, list)
assert len(arrs) == 2
assert arrs[0].dtype.name == "float32"
assert arrs[1].dtype.name == "float32"
assert arrs[0].shape == (0, 2)
assert np.allclose(arrs[1], [(5, 5), (15, 15), (10, 15), (25, 25)])
def test_to_xy_arrays_with_two_empty_list_of_line_strings(self):
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
arrs = lsoi.to_xy_arrays()
assert isinstance(arrs, list)
assert len(arrs) == 0
def test_draw_on_image_with_default_settings(self):
ls1 = LineString([(0, 0), (10, 10), (5, 10)])
ls2 = LineString([(5, 5), (15, 15), (10, 15), (25, 25)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
img = np.zeros((100, 100, 3), dtype=np.uint8) + 1
observed = lsoi.draw_on_image(img)
expected = np.copy(img)
for ls in [ls1, ls2]:
expected = ls.draw_on_image(expected)
assert np.array_equal(observed, expected)
def test_draw_on_image_with_custom_settings(self):
ls1 = LineString([(0, 0), (10, 10), (5, 10)])
ls2 = LineString([(5, 5), (15, 15), (10, 15), (25, 25)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
img = np.zeros((100, 100, 3), dtype=np.uint8) + 1
observed = lsoi.draw_on_image(img,
color_lines=(0, 0, 255),
color_points=(255, 0, 0),
alpha_lines=0.5,
alpha_points=0.6,
antialiased=False)
expected = np.copy(img)
for ls in [ls1, ls2]:
expected = ls.draw_on_image(expected,
color_lines=(0, 0, 255),
color_points=(255, 0, 0),
alpha_lines=0.5,
alpha_points=0.6,
antialiased=False)
assert np.array_equal(observed, expected)
def test_draw_on_image_with_default_settings_one_ls_empty(self):
ls1 = LineString([])
ls2 = LineString([(5, 5), (15, 15), (10, 15), (25, 25)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
img = np.zeros((100, 100, 3), dtype=np.uint8) + 1
observed = lsoi.draw_on_image(img)
expected = np.copy(img)
for ls in [ls1, ls2]:
expected = ls.draw_on_image(expected)
assert np.array_equal(observed, expected)
def test_draw_on_image_with_default_settings_and_empty_list_of_ls(self):
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
img = np.zeros((100, 100, 3), dtype=np.uint8) + 1
observed = lsoi.draw_on_image(img)
expected = np.copy(img)
assert np.array_equal(observed, expected)
def test_remove_out_of_image_fraction_(self):
item1 = ia.LineString([(5, 1), (9, 1)])
item2 = ia.LineString([(5, 1), (15, 1)])
item3 = ia.LineString([(15, 1), (25, 1)])
cbaoi = ia.LineStringsOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_reduced = cbaoi.remove_out_of_image_fraction_(0.6)
assert len(cbaoi_reduced.items) == 2
assert cbaoi_reduced.items == [item1, item2]
assert cbaoi_reduced is cbaoi
def test_remove_out_of_image_fraction(self):
item1 = ia.LineString([(5, 1), (9, 1)])
item2 = ia.LineString([(5, 1), (15, 1)])
item3 = ia.LineString([(15, 1), (25, 1)])
cbaoi = ia.LineStringsOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_reduced = cbaoi.remove_out_of_image_fraction(0.6)
assert len(cbaoi_reduced.items) == 2
assert cbaoi_reduced.items == [item1, item2]
assert cbaoi_reduced is not cbaoi
def test_to_xy_array(self):
lsoi = ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 2)]),
ia.LineString([(10, 20), (30, 40)])],
shape=(1, 2, 3))
xy_out = lsoi.to_xy_array()
expected = np.float32([
[0.0, 0.0],
[1.0, 2.0],
[10.0, 20.0],
[30.0, 40.0]
])
assert xy_out.shape == (4, 2)
assert np.allclose(xy_out, expected)
assert xy_out.dtype.name == "float32"
def test_to_xy_array__empty_object(self):
lsoi = ia.LineStringsOnImage(
[],
shape=(1, 2, 3))
xy_out = lsoi.to_xy_array()
assert xy_out.shape == (0, 2)
assert xy_out.dtype.name == "float32"
def test_fill_from_xy_array___empty_array(self):
xy = np.zeros((0, 2), dtype=np.float32)
lsoi = ia.LineStringsOnImage([], shape=(2, 2, 3))
lsoi = lsoi.fill_from_xy_array_(xy)
assert len(lsoi.line_strings) == 0
def test_fill_from_xy_array___empty_list(self):
xy = []
lsoi = ia.LineStringsOnImage([], shape=(2, 2, 3))
lsoi = lsoi.fill_from_xy_array_(xy)
assert len(lsoi.line_strings) == 0
def test_fill_from_xy_array___array_with_two_coords(self):
xy = np.array(
[(100, 101),
(102, 103),
(200, 201),
(202, 203)], dtype=np.float32)
lsoi = ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 2)]),
ia.LineString([(10, 20), (30, 40)])],
shape=(2, 2, 3))
lsoi = lsoi.fill_from_xy_array_(xy)
assert len(lsoi.line_strings) == 2
assert np.allclose(
lsoi.line_strings[0].coords,
[(100, 101), (102, 103)])
assert np.allclose(
lsoi.line_strings[1].coords,
[(200, 201), (202, 203)])
def test_fill_from_xy_array___list_with_two_coords(self):
xy = [(100, 101),
(102, 103),
(200, 201),
(202, 203)]
lsoi = ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 2)]),
ia.LineString([(10, 20), (30, 40)])],
shape=(2, 2, 3))
lsoi = lsoi.fill_from_xy_array_(xy)
assert len(lsoi.line_strings) == 2
assert np.allclose(
lsoi.line_strings[0].coords,
[(100, 101), (102, 103)])
assert np.allclose(
lsoi.line_strings[1].coords,
[(200, 201), (202, 203)])
def test_to_keypoints_on_image(self):
lsoi = ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 2)]),
ia.LineString([(10, 20), (30, 40)])],
shape=(1, 2, 3))
kpsoi = lsoi.to_keypoints_on_image()
assert len(kpsoi.keypoints) == 2*2
assert kpsoi.keypoints[0].x == 0
assert kpsoi.keypoints[0].y == 0
assert kpsoi.keypoints[1].x == 1
assert kpsoi.keypoints[1].y == 2
assert kpsoi.keypoints[2].x == 10
assert kpsoi.keypoints[2].y == 20
assert kpsoi.keypoints[3].x == 30
assert kpsoi.keypoints[3].y == 40
def test_to_keypoints_on_image__empty_instance(self):
lsoi = ia.LineStringsOnImage([], shape=(1, 2, 3))
kpsoi = lsoi.to_keypoints_on_image()
assert len(kpsoi.keypoints) == 0
def test_invert_to_keypoints_on_image_(self):
lsoi = ia.LineStringsOnImage(
[ia.LineString([(0, 0), (1, 2)]),
ia.LineString([(10, 20), (30, 40)])],
shape=(1, 2, 3))
kpsoi = ia.KeypointsOnImage(
[ia.Keypoint(100, 101), ia.Keypoint(102, 103),
ia.Keypoint(110, 120), ia.Keypoint(130, 140)],
shape=(10, 20, 30))
lsoi_inv = lsoi.invert_to_keypoints_on_image_(kpsoi)
assert len(lsoi_inv.line_strings) == 2
assert lsoi_inv.shape == (10, 20, 30)
assert np.allclose(
lsoi.line_strings[0].coords,
[(100, 101), (102, 103)])
assert np.allclose(
lsoi.line_strings[1].coords,
[(110, 120), (130, 140)])
def test_invert_to_keypoints_on_image___empty_instance(self):
lsoi = ia.LineStringsOnImage([], shape=(1, 2, 3))
kpsoi = ia.KeypointsOnImage([], shape=(10, 20, 30))
lsoi_inv = lsoi.invert_to_keypoints_on_image_(kpsoi)
assert len(lsoi_inv.line_strings) == 0
assert lsoi_inv.shape == (10, 20, 30)
def test_copy_with_two_line_strings(self):
# basic test, without labels
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = lsoi.copy()
assert observed.line_strings[0] is ls1
assert observed.line_strings[1] is ls2
assert observed.shape == (100, 100, 3)
def test_copy_with_two_line_strings_and_labels(self):
# basic test, with labels
ls1 = LineString([(0, 0), (1, 0), (2, 1)], label="foo")
ls2 = LineString([(10, 10)], label="bar")
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = lsoi.copy()
assert observed.line_strings[0] is ls1
assert observed.line_strings[1] is ls2
assert observed.line_strings[0].label == "foo"
assert observed.line_strings[1].label == "bar"
assert observed.shape == (100, 100, 3)
def test_copy_with_empty_list_of_line_strings(self):
# LSOI is empty
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = lsoi.copy()
assert observed.line_strings == []
assert observed.shape == (100, 100, 3)
def test_copy_and_replace_line_strings_attribute(self):
# provide line_strings
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = lsoi.copy(line_strings=[ls1, ls2], shape=(200, 201, 3))
assert observed.line_strings[0] is ls1
assert observed.line_strings[1] is ls2
assert observed.shape == (200, 201, 3)
def test_copy_and_replace_line_strings_attribute_with_labeled_ls(self):
# provide line_strings, with labels
ls1 = LineString([(0, 0), (1, 0), (2, 1)], label="foo")
ls2 = LineString([(10, 10)], label="bar")
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = lsoi.copy(line_strings=[ls1, ls2], shape=(200, 201, 3))
assert observed.line_strings[0] is ls1
assert observed.line_strings[1] is ls2
assert observed.line_strings[0].label == "foo"
assert observed.line_strings[1].label == "bar"
assert observed.shape == (200, 201, 3)
def test_copy_and_replace_line_strings_attribute_with_empty_list(self):
# provide empty list of line_strings
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = lsoi.copy(line_strings=[], shape=(200, 201, 3))
assert observed.line_strings == []
assert observed.shape == (200, 201, 3)
def test_deepcopy_with_two_line_strings(self):
# basic test, without labels
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = lsoi.deepcopy()
assert observed.line_strings[0] is not ls1
assert observed.line_strings[1] is not ls2
assert observed.line_strings[0].coords_almost_equals(ls1)
assert observed.line_strings[1].coords_almost_equals(ls2)
assert observed.shape == (100, 100, 3)
def test_deepcopy_with_two_line_strings_and_labels(self):
# basic test, with labels
ls1 = LineString([(0, 0), (1, 0), (2, 1)], label="foo")
ls2 = LineString([(10, 10)], label="bar")
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = lsoi.deepcopy()
assert observed.line_strings[0] is not ls1
assert observed.line_strings[1] is not ls2
assert observed.line_strings[0].coords_almost_equals(ls1)
assert observed.line_strings[1].coords_almost_equals(ls2)
assert observed.line_strings[0].label == "foo"
assert observed.line_strings[1].label == "bar"
assert observed.shape == (100, 100, 3)
def test_deepcopy_with_empty_list_of_line_strings(self):
# LSOI is empty
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = lsoi.deepcopy()
assert observed.line_strings == []
assert observed.shape == (100, 100, 3)
def test_deepcopy_and_replace_line_strings_attribute(self):
# provide line_strings
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = lsoi.deepcopy(line_strings=[ls1, ls2], shape=(200, 201, 3))
# line strings provided via line_strings are also deepcopied
assert observed.line_strings[0].coords_almost_equals(ls1)
assert observed.line_strings[1].coords_almost_equals(ls2)
assert observed.shape == (200, 201, 3)
def test_deepcopy_and_replace_line_strings_attribute_with_labeled_ls(self):
# provide line_strings, with labels
ls1 = LineString([(0, 0), (1, 0), (2, 1)], label="foo")
ls2 = LineString([(10, 10)], label="bar")
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = lsoi.deepcopy(line_strings=[ls1, ls2], shape=(200, 201, 3))
# line strings provided via line_strings are also deepcopied
assert observed.line_strings[0].coords_almost_equals(ls1)
assert observed.line_strings[1].coords_almost_equals(ls2)
assert observed.line_strings[0].label == "foo"
assert observed.line_strings[1].label == "bar"
assert observed.shape == (200, 201, 3)
def test_deepcopy_and_replace_line_strings_attribute_with_empty_list(self):
# provide empty list of line_strings
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = lsoi.deepcopy(line_strings=[], shape=(200, 201, 3))
assert observed.line_strings == []
assert observed.shape == (200, 201, 3)
def test___getitem__(self):
cbas = [
ia.LineString([(0, 0), (1, 0), (1, 1)]),
ia.LineString([(1, 1), (2, 1), (2, 2)])
]
cbasoi = ia.LineStringsOnImage(cbas, shape=(3, 4, 3))
assert cbasoi[0] is cbas[0]
assert cbasoi[1] is cbas[1]
assert cbasoi[0:2] == cbas
def test___iter__(self):
cbas = [ia.LineString([(0, 0), (1, 1)]),
ia.LineString([(1, 2), (3, 4)])]
cbasoi = ia.LineStringsOnImage(cbas, shape=(40, 50, 3))
for i, cba in enumerate(cbasoi):
assert cba is cbas[i]
def test___iter___empty(self):
cbasoi = ia.LineStringsOnImage([], shape=(40, 50, 3))
i = 0
for _cba in cbasoi:
i += 1
assert i == 0
def test___len__(self):
cbas = [ia.LineString([(0, 0), (1, 1)]),
ia.LineString([(1, 2), (3, 4)])]
cbasoi = ia.LineStringsOnImage(cbas, shape=(40, 50, 3))
assert len(cbasoi) == 2
def test___repr__(self):
def _func(obj):
return obj.__repr__()
self._test_str_repr(_func)
def test___str__(self):
def _func(obj):
return obj.__str__()
self._test_str_repr(_func)
def test___repr___empty_list_of_line_strings(self):
def _func(obj):
return obj.__repr__()
self._test_str_repr_empty_list_of_line_strings(_func)
def test___str___empty_list_of_line_strings(self):
def _func(obj):
return obj.__str__()
self._test_str_repr_empty_list_of_line_strings(_func)
@classmethod
def _test_str_repr(cls, func):
ls1 = LineString([(0, 0), (1, 0), (2, 1)])
ls2 = LineString([(10, 10)])
lsoi = LineStringsOnImage([ls1, ls2], shape=(100, 100, 3))
observed = func(lsoi)
expected = "LineStringsOnImage([%s, %s], shape=(100, 100, 3))" % (
func(ls1), func(ls2)
)
assert observed == expected
@classmethod
def _test_str_repr_empty_list_of_line_strings(cls, func):
lsoi = LineStringsOnImage([], shape=(100, 100, 3))
observed = func(lsoi)
expected = "LineStringsOnImage([], shape=(100, 100, 3))"
assert observed == expected
def _coords_eq(ls, other):
return ls.coords_almost_equals(other, max_distance=1e-2)
def _drawing_allclose(arr, v):
# draw_points_heatmaps_array() and draw_line_heatmap_array() are
# currently limited to 1/255 accuracy due to drawing in uint8
return np.allclose(arr, v, atol=(1.01/255), rtol=0)
================================================
FILE: test/augmentables/test_normalization.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
import imgaug as ia
import imgaug.augmentables.normalization as normalization
from imgaug.testutils import reseed
# TODO split up tests here
class TestNormalization(unittest.TestCase):
def setUp(self):
reseed()
def test_invert_normalize_images(self):
assert normalization.invert_normalize_images(None, None) is None
arr = np.zeros((1, 4, 4, 3), dtype=np.uint8)
arr_old = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = normalization.invert_normalize_images(arr, arr_old)
assert ia.is_np_array(observed)
assert observed.shape == (1, 4, 4, 3)
assert observed.dtype.name == "uint8"
arr = np.zeros((1, 4, 4, 1), dtype=np.uint8)
arr_old = np.zeros((4, 4), dtype=np.uint8)
observed = normalization.invert_normalize_images(arr, arr_old)
assert ia.is_np_array(observed)
assert observed.shape == (4, 4)
assert observed.dtype.name == "uint8"
arr = np.zeros((1, 4, 4, 1), dtype=np.uint8)
arr_old = np.zeros((1, 4, 4), dtype=np.uint8)
observed = normalization.invert_normalize_images(arr, arr_old)
assert ia.is_np_array(observed)
assert observed.shape == (1, 4, 4)
assert observed.dtype.name == "uint8"
images = []
images_old = []
observed = normalization.invert_normalize_images(images, images_old)
assert isinstance(observed, list)
assert len(observed) == 0
arr1 = np.zeros((4, 4, 1), dtype=np.uint8)
arr2 = np.zeros((5, 5, 3), dtype=np.uint8)
arr1_old = np.zeros((4, 4), dtype=np.uint8)
arr2_old = np.zeros((5, 5, 3), dtype=np.uint8)
observed = normalization.invert_normalize_images([arr1, arr2],
[arr1_old, arr2_old])
assert isinstance(observed, list)
assert len(observed) == 2
assert ia.is_np_array(observed[0])
assert ia.is_np_array(observed[1])
assert observed[0].shape == (4, 4)
assert observed[1].shape == (5, 5, 3)
assert observed[0].dtype.name == "uint8"
assert observed[1].dtype.name == "uint8"
# ---------
# images turned to list during augmentation
# ---------
# different shapes, each 3D
images = [np.zeros((3, 4, 1), dtype=np.uint8),
np.zeros((4, 3, 1), dtype=np.uint8)]
images_old = np.zeros((2, 4, 4, 1), dtype=np.uint8)
observed = normalization.invert_normalize_images(images, images_old)
assert isinstance(observed, list)
assert len(observed) == 2
assert observed[0] is images[0]
assert observed[1] is images[1]
# different shapes, each 2D
images = [np.zeros((3, 4, 1), dtype=np.uint8),
np.zeros((4, 3, 1), dtype=np.uint8)]
images_old = np.zeros((2, 4, 4), dtype=np.uint8)
observed = normalization.invert_normalize_images(images, images_old)
assert isinstance(observed, list)
assert len(observed) == 2
assert observed[0].shape == (3, 4)
assert observed[1].shape == (4, 3)
# same shapes, each 3D
images = [np.zeros((3, 4, 1), dtype=np.uint8),
np.zeros((3, 4, 1), dtype=np.uint8)]
images_old = np.zeros((2, 4, 4, 1), dtype=np.uint8)
observed = normalization.invert_normalize_images(images, images_old)
# assert ia.is_np_array(observed)
# assert observed.shape == (2, 3, 4, 1)
assert isinstance(observed, list)
assert len(observed) == 2
assert observed[0] is images[0]
assert observed[1] is images[1]
# same shapes, each 2D
images = [np.zeros((3, 4, 1), dtype=np.uint8),
np.zeros((3, 4, 1), dtype=np.uint8)]
images_old = np.zeros((2, 4, 4), dtype=np.uint8)
observed = normalization.invert_normalize_images(images, images_old)
# assert ia.is_np_array(observed)
# assert observed.shape == (2, 3, 4)
assert isinstance(observed, list)
assert len(observed) == 2
assert observed[0].shape == (3, 4)
assert observed[1].shape == (3, 4)
# single item in list
images = [np.zeros((3, 4, 1), dtype=np.uint8)]
images_old = np.zeros((1, 4, 4), dtype=np.uint8)
observed = normalization.invert_normalize_images(images, images_old)
# assert ia.is_np_array(observed)
# assert observed.shape == (1, 3, 4)
assert isinstance(observed, list)
assert len(observed) == 1
assert observed[0].shape == (3, 4)
# single item in list, original was 2D
images = [np.zeros((3, 4, 1), dtype=np.uint8)]
images_old = np.zeros((4, 4), dtype=np.uint8)
observed = normalization.invert_normalize_images(images, images_old)
# assert ia.is_np_array(observed)
# assert observed.shape == (3, 4)
assert isinstance(observed, list)
assert len(observed) == 1
assert observed[0].shape == (3, 4)
with self.assertRaises(ValueError):
normalization.invert_normalize_images(False, False)
def test_invert_normalize_heatmaps(self):
def _norm_and_invert(heatmaps, images):
return normalization.invert_normalize_heatmaps(
normalization.normalize_heatmaps(heatmaps, shapes=images),
heatmaps
)
# ----
# None
# ----
observed = normalization.invert_normalize_heatmaps(None, None)
assert observed is None
# ----
# array
# ----
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = np.zeros((1, 1, 1, 1), dtype=np.float32) + 0.1
after = _norm_and_invert(before, images=images)
assert ia.is_np_array(after)
assert after.shape == (1, 1, 1, 1)
assert after.dtype.name == "float32"
assert np.allclose(after, before)
# ----
# single HeatmapsOnImage
# ----
before = ia.HeatmapsOnImage(
np.zeros((1, 1, 1), dtype=np.float32) + 0.1,
shape=(1, 1, 3))
after = _norm_and_invert(before, images=None)
assert isinstance(after, ia.HeatmapsOnImage)
assert after.shape == before.shape
assert np.allclose(after.arr_0to1, before.arr_0to1)
# ----
# empty iterable
# ----
before = []
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert len(after) == 0
# ----
# iterable of arrays
# ----
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = [np.zeros((1, 1, 1), dtype=np.float32) + 0.1]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert after[0].shape == (1, 1, 1)
assert after[0].dtype.name == "float32"
assert np.allclose(after[0], before[0])
# ----
# iterable of HeatmapsOnImage
# ----
before = [ia.HeatmapsOnImage(
np.zeros((1, 1, 1), dtype=np.float32) + 0.1,
shape=(1, 1, 3))]
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert isinstance(after[0], ia.HeatmapsOnImage)
assert after[0].shape == before[0].shape
assert np.allclose(after[0].arr_0to1, before[0].arr_0to1)
def test_invert_normalize_segmentation_maps(self):
def _norm_and_invert(segmaps, images):
return normalization.invert_normalize_segmentation_maps(
normalization.normalize_segmentation_maps(
segmaps, shapes=images),
segmaps
)
# ----
# None
# ----
observed = normalization.invert_normalize_segmentation_maps(None, None)
assert observed is None
# ----
# array
# ----
for dt in [np.dtype("int32"), np.dtype("uint16"), np.dtype(bool)]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 3), dtype=np.uint8)]:
before = np.ones((1, 1, 1, 1), dtype=dt)
after = _norm_and_invert(before, images=images)
assert ia.is_np_array(after)
assert after.shape == (1, 1, 1, 1)
assert after.dtype.name == dt.name
assert np.array_equal(after, before)
# ----
# single SegmentationMapsOnImage
# ----
before = ia.SegmentationMapsOnImage(
np.zeros((1, 1, 1), dtype=np.int32) + 1,
shape=(1, 1, 3))
after = _norm_and_invert(before, images=None)
assert isinstance(after, ia.SegmentationMapsOnImage)
assert after.shape == before.shape
assert np.array_equal(after.arr, before.arr)
# ----
# empty iterable
# ----
before = []
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert len(after) == 0
# ----
# iterable of arrays
# ----
for dt in [np.dtype("int32"), np.dtype("uint16"), np.dtype(bool)]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = [np.ones((1, 1, 1), dtype=dt)]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert after[0].shape == (1, 1, 1)
assert after[0].dtype.name == dt.name
assert np.array_equal(after[0], before[0])
# ----
# iterable of SegmentationMapsOnImage
# ----
before = [ia.SegmentationMapsOnImage(
np.zeros((1, 1, 1), dtype=np.int32) + 1,
shape=(1, 1, 3))]
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert isinstance(after[0], ia.SegmentationMapsOnImage)
assert after[0].shape == before[0].shape
assert np.allclose(after[0].arr, before[0].arr)
def test_invert_normalize_keypoints(self):
def _norm_and_invert(kps, images):
return normalization.invert_normalize_keypoints(
normalization.normalize_keypoints(
kps, shapes=images),
kps
)
# ----
# None
# ----
observed = normalization.invert_normalize_keypoints(None, None)
assert observed is None
# ----
# array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = np.zeros((1, 1, 2), dtype=dt) + 1
after = _norm_and_invert(before, images=images)
assert ia.is_np_array(after)
assert after.shape == (1, 1, 2)
assert after.dtype.name == dt.name
assert np.allclose(after, 1)
# ----
# (x,y)
# ----
before = (1, 2)
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, tuple)
assert after == (1, 2)
# ----
# single Keypoint instance
# ----
before = ia.Keypoint(x=1, y=2)
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, ia.Keypoint)
assert after.x == 1
assert after.y == 2
# ----
# single KeypointsOnImage instance
# ----
before = ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)], shape=(1, 1, 3))
after = _norm_and_invert(before, images=None)
assert isinstance(after, ia.KeypointsOnImage)
assert len(after.keypoints) == 1
assert after.keypoints[0].x == 1
assert after.keypoints[0].y == 2
assert after.shape == (1, 1, 3)
# ----
# empty iterable
# ----
before = []
after = _norm_and_invert(before, images=None)
assert after == []
# ----
# iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = np.zeros((1, 1, 2), dtype=dt) + 1
after = _norm_and_invert(before, images=images)
assert ia.is_np_array(after)
assert after.shape == (1, 1, 2)
assert after.dtype.name == dt.name
assert np.allclose(after, 1)
# ----
# iterable of (x,y)
# ----
before = [(1, 2), (3, 4)]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert after == [(1, 2), (3, 4)]
# ----
# iterable of Keypoint
# ----
before = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert isinstance(after[0], ia.Keypoint)
assert isinstance(after[1], ia.Keypoint)
assert after[0].x == 1
assert after[0].y == 2
assert after[1].x == 3
assert after[1].y == 4
# ----
# iterable of KeypointsOnImage
# ----
before = [
ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)], shape=(1, 1, 3)),
ia.KeypointsOnImage([ia.Keypoint(x=3, y=4)], shape=(1, 1, 3)),
]
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert len(after) == 2
assert isinstance(after[0], ia.KeypointsOnImage)
assert isinstance(after[1], ia.KeypointsOnImage)
assert after[0].keypoints[0].x == 1
assert after[0].keypoints[0].y == 2
assert after[1].keypoints[0].x == 3
assert after[1].keypoints[0].y == 4
# ----
# iterable of empty interables
# ----
before = [[]]
after = _norm_and_invert(before, [np.zeros((1, 1, 3), dtype=np.uint8)])
assert after == [[]]
# ----
# iterable of iterable of (x,y)
# ----
before = [
[(1, 2), (3, 4)],
[(5, 6), (7, 8)]
]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert isinstance(after[0], list)
assert isinstance(after[1], list)
assert after[0][0][0] == 1
assert after[0][0][1] == 2
assert after[0][1][0] == 3
assert after[0][1][1] == 4
assert after[1][0][0] == 5
assert after[1][0][1] == 6
assert after[1][1][0] == 7
assert after[1][1][1] == 8
# ----
# iterable of iterable of Keypoint
# ----
before = [
[ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)],
[ia.Keypoint(x=5, y=6), ia.Keypoint(x=7, y=8)]
]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert isinstance(after[0], list)
assert isinstance(after[1], list)
assert after[0][0].x == 1
assert after[0][0].y == 2
assert after[0][1].x == 3
assert after[0][1].y == 4
assert after[1][0].x == 5
assert after[1][0].y == 6
assert after[1][1].x == 7
assert after[1][1].y == 8
def test_invert_normalize_bounding_boxes(self):
def _norm_and_invert(bbs, images):
return normalization.invert_normalize_bounding_boxes(
normalization.normalize_bounding_boxes(
bbs, shapes=images),
bbs
)
# ----
# None
# ----
observed = normalization.invert_normalize_bounding_boxes(None, None)
assert observed is None
# ----
# array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = np.zeros((1, 1, 4), dtype=dt) + 1
after = _norm_and_invert(before, images=images)
assert ia.is_np_array(after)
assert after.shape == (1, 1, 4)
assert after.dtype.name == dt.name
assert np.allclose(after, 1)
# ----
# (x1,y1,x2,y2)
# ----
before = (1, 2, 3, 4)
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, tuple)
assert after == (1, 2, 3, 4)
# ----
# single BoundingBox instance
# ----
before = ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, ia.BoundingBox)
assert after.x1 == 1
assert after.y1 == 2
assert after.x2 == 3
assert after.y2 == 4
# ----
# single BoundingBoxesOnImage instance
# ----
before = ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)],
shape=(1, 1, 3))
after = _norm_and_invert(before, images=None)
assert isinstance(after, ia.BoundingBoxesOnImage)
assert len(after.bounding_boxes) == 1
assert after.bounding_boxes[0].x1 == 1
assert after.bounding_boxes[0].y1 == 2
assert after.bounding_boxes[0].x2 == 3
assert after.bounding_boxes[0].y2 == 4
assert after.shape == (1, 1, 3)
# ----
# empty iterable
# ----
before = []
after = _norm_and_invert(before, images=None)
assert after == []
# ----
# iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = [np.zeros((1, 4), dtype=dt) + 1]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert ia.is_np_array(after[0])
assert after[0].shape == (1, 4)
assert after[0].dtype.name == dt.name
assert np.allclose(after[0], 1)
# ----
# iterable of (x1,y1,x2,y2)
# ----
before = [(1, 2, 3, 4), (5, 6, 7, 8)]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert after == [(1, 2, 3, 4), (5, 6, 7, 8)]
# ----
# iterable of BoundingBox
# ----
before = [
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)
]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert isinstance(after[0], ia.BoundingBox)
assert isinstance(after[1], ia.BoundingBox)
assert after[0].x1 == 1
assert after[0].y1 == 2
assert after[0].x2 == 3
assert after[0].y2 == 4
assert after[1].x1 == 5
assert after[1].y1 == 6
assert after[1].x2 == 7
assert after[1].y2 == 8
# ----
# iterable of BoundingBoxesOnImage
# ----
before = [
ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)],
shape=(1, 1, 3)),
ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)],
shape=(1, 1, 3))
]
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert len(after) == 2
assert isinstance(after[0], ia.BoundingBoxesOnImage)
assert isinstance(after[1], ia.BoundingBoxesOnImage)
assert isinstance(after[0].bounding_boxes[0], ia.BoundingBox)
assert isinstance(after[1].bounding_boxes[0], ia.BoundingBox)
assert after[0].bounding_boxes[0].x1 == 1
assert after[0].bounding_boxes[0].y1 == 2
assert after[0].bounding_boxes[0].x2 == 3
assert after[0].bounding_boxes[0].y2 == 4
assert after[1].bounding_boxes[0].x1 == 5
assert after[1].bounding_boxes[0].y1 == 6
assert after[1].bounding_boxes[0].x2 == 7
assert after[1].bounding_boxes[0].y2 == 8
assert after[0].shape == (1, 1, 3)
assert after[1].shape == (1, 1, 3)
# ----
# iterable of empty interables
# ----
before = [[]]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert after == [[]]
# ----
# iterable of iterable of (x1,y1,x2,y2)
# ----
before = [
[(1, 2, 3, 4)],
[(5, 6, 7, 8), (9, 10, 11, 12)]
]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert after == [
[(1, 2, 3, 4)],
[(5, 6, 7, 8), (9, 10, 11, 12)]
]
# ----
# iterable of iterable of Keypoint
# ----
before = [
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)],
[ia.BoundingBox(x1=9, y1=10, x2=11, y2=12),
ia.BoundingBox(x1=13, y1=14, x2=15, y2=16)]
]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert isinstance(after[0], list)
assert isinstance(after[1], list)
assert len(after[0]) == 2
assert len(after[1]) == 2
assert after[0][0].x1 == 1
assert after[0][0].y1 == 2
assert after[0][0].x2 == 3
assert after[0][0].y2 == 4
assert after[0][1].x1 == 5
assert after[0][1].y1 == 6
assert after[0][1].x2 == 7
assert after[0][1].y2 == 8
assert after[1][0].x1 == 9
assert after[1][0].y1 == 10
assert after[1][0].x2 == 11
assert after[1][0].y2 == 12
assert after[1][1].x1 == 13
assert after[1][1].y1 == 14
assert after[1][1].x2 == 15
assert after[1][1].y2 == 16
def test_invert_normalize_polygons(self):
def _norm_and_invert(polys, images):
return normalization.invert_normalize_polygons(
normalization.normalize_polygons(
polys, shapes=images),
polys
)
coords1 = [(0, 0), (10, 0), (10, 10)]
coords2 = [(5, 5), (15, 5), (15, 15)]
coords3 = [(0, 0), (10, 0), (10, 10), (0, 10)]
coords4 = [(5, 5), (15, 5), (15, 15), (5, 15)]
coords1_kps = [ia.Keypoint(x=x, y=y) for x, y in coords1]
coords2_kps = [ia.Keypoint(x=x, y=y) for x, y in coords2]
coords3_kps = [ia.Keypoint(x=x, y=y) for x, y in coords3]
coords4_kps = [ia.Keypoint(x=x, y=y) for x, y in coords4]
coords1_arr = np.float32(coords1)
coords2_arr = np.float32(coords2)
coords3_arr = np.float32(coords3)
coords4_arr = np.float32(coords4)
# ----
# None
# ----
observed = normalization.invert_normalize_polygons(None, None)
assert observed is None
# ----
# array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = coords1_arr[np.newaxis, np.newaxis, ...].astype(dt)
after = _norm_and_invert(before, images=images)
assert ia.is_np_array(after)
assert after.shape == (1, 1, 3, 2)
assert after.dtype.name == dt.name
assert np.allclose(after,
coords1_arr[np.newaxis, np.newaxis, ...])
before = np.tile(
coords1_arr[np.newaxis, np.newaxis, ...].astype(dt),
(1, 5, 1, 1)
)
after = _norm_and_invert(before, images=images)
assert ia.is_np_array(after)
assert after.shape == (1, 5, 3, 2)
assert after.dtype.name == dt.name
assert np.allclose(after[0],
coords1_arr[np.newaxis, ...])
# ----
# single Polygon instance
# ----
before = ia.Polygon(coords1)
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, ia.Polygon)
assert after.exterior_almost_equals(coords1)
# ----
# single PolygonsOnImage instance
# ----
before = ia.PolygonsOnImage([ia.Polygon(coords1)], shape=(1, 1, 3))
after = _norm_and_invert(before, images=None)
assert isinstance(after, ia.PolygonsOnImage)
assert len(after.polygons) == 1
assert after.polygons[0].exterior_almost_equals(coords1)
assert after.shape == (1, 1, 3)
# ----
# empty iterable
# ----
before = []
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert after == []
# ----
# iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = [coords1_arr[np.newaxis, ...].astype(dt)]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert ia.is_np_array(after[0])
assert after[0].shape == (1, 3, 2)
assert after[0].dtype.name == dt.name
assert np.allclose(after[0], coords1_arr[np.newaxis, ...])
before = [np.tile(
coords1_arr[np.newaxis, ...].astype(dt),
(5, 1, 1)
)]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert ia.is_np_array(after[0])
assert after[0].shape == (5, 3, 2)
assert after[0].dtype.name == dt.name
assert np.allclose(after[0][0], coords1_arr)
# ----
# iterable of (x,y)
# ----
before = coords1
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert after == coords1
# ----
# iterable of Keypoint
# ----
before = coords1_kps
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == len(coords1_kps)
assert all([kp_after.x == kp_before.x and kp_after.y == kp_before.y
for kp_after, kp_before in zip(after, coords1_kps)])
# ----
# iterable of Polygon
# ----
before = [ia.Polygon(coords1), ia.Polygon(coords2)]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert after[0].exterior_almost_equals(coords1)
assert after[1].exterior_almost_equals(coords2)
# ----
# iterable of PolygonsOnImage
# ----
before = [
ia.PolygonsOnImage([ia.Polygon(coords1)], shape=(1, 1, 3)),
ia.PolygonsOnImage([ia.Polygon(coords2)], shape=(2, 1, 3))
]
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert len(after) == 2
assert isinstance(after[0], ia.PolygonsOnImage)
assert isinstance(after[1], ia.PolygonsOnImage)
assert after[0].polygons[0].exterior_almost_equals(coords1)
assert after[1].polygons[0].exterior_almost_equals(coords2)
assert after[0].shape == (1, 1, 3)
assert after[1].shape == (2, 1, 3)
# ----
# iterable of empty interables
# ----
before = [[]]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert after == [[]]
# ----
# iterable of iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = [[coords1_arr.astype(dt)]]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert isinstance(after[0], list)
assert len(after[0]) == 1
assert ia.is_np_array(after[0][0])
assert after[0][0].shape == (3, 2)
assert after[0][0].dtype.name == dt.name
assert np.allclose(after[0][0], coords1_arr)
before = [[coords1_arr.astype(dt) for _ in sm.xrange(5)]]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert isinstance(after[0], list)
assert len(after[0]) == 5
assert ia.is_np_array(after[0][0])
assert after[0][0].shape == (3, 2)
assert after[0][0].dtype.name == dt.name
assert np.allclose(after[0][0], coords1_arr)
# ----
# iterable of iterable of (x,y)
# ----
before = [coords1, coords2]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert after[0] == coords1
assert after[1] == coords2
# ----
# iterable of iterable of Keypoint
# ----
before = [coords1_kps, coords2_kps]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert len(after[0]) == len(coords1_kps)
assert len(after[1]) == len(coords2_kps)
assert all([kp_after.x == kp_before.x and kp_after.y == kp_before.y
for kp_after, kp_before in zip(after[0], coords1_kps)])
assert all([kp_after.x == kp_before.x and kp_after.y == kp_before.y
for kp_after, kp_before in zip(after[1], coords2_kps)])
# ----
# iterable of iterable of Polygon
# ----
before = [
[ia.Polygon(coords1), ia.Polygon(coords2)],
[ia.Polygon(coords3), ia.Polygon(coords4)]
]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert isinstance(after[0], list)
assert isinstance(after[1], list)
assert len(after[0]) == 2
assert len(after[1]) == 2
assert after[0][0].exterior_almost_equals(coords1)
assert after[0][1].exterior_almost_equals(coords2)
assert after[1][0].exterior_almost_equals(coords3)
assert after[1][1].exterior_almost_equals(coords4)
# ----
# iterable of iterable of empty iterable
# ----
before = [[[]]]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert after == [[[]]]
# ----
# iterable of iterable of iterable of (x,y)
# ----
before = [[coords1, coords2], [coords3, coords4]]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert len(after[0]) == 2
assert len(after[1]) == 2
assert after[0][0] == coords1
assert after[0][1] == coords2
assert after[1][0] == coords3
assert after[1][1] == coords4
# ----
# iterable of iterable of iterable of Keypoint
# ----
before = [[coords1_kps, coords2_kps], [coords3_kps, coords4_kps]]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert len(after[0]) == 2
assert len(after[1]) == 2
assert all([kp_after.x == kp_before.x and kp_after.y == kp_before.y
for kp_after, kp_before in zip(after[0][0], coords1_kps)])
assert all([kp_after.x == kp_before.x and kp_after.y == kp_before.y
for kp_after, kp_before in zip(after[0][1], coords2_kps)])
assert all([kp_after.x == kp_before.x and kp_after.y == kp_before.y
for kp_after, kp_before in zip(after[1][0], coords3_kps)])
assert all([kp_after.x == kp_before.x and kp_after.y == kp_before.y
for kp_after, kp_before in zip(after[1][1], coords4_kps)])
# The underlying normalization functions are mostly identical for
# LineStrings and Polygons, hence we run only a few tests for LineStrings
# here. Most of the code was already tested for Polygons.
def test_invert_normalize_line_strings(self):
def _norm_and_invert(line_strings, images):
return normalization.invert_normalize_line_strings(
normalization.normalize_line_strings(
line_strings, shapes=images),
line_strings
)
coords1 = [(0, 0), (10, 0), (10, 10)]
coords2 = [(5, 5), (15, 5), (15, 15)]
coords3 = [(0, 0), (10, 0), (10, 10), (0, 10)]
coords4 = [(5, 5), (15, 5), (15, 15), (5, 15)]
coords1_arr = np.float32(coords1)
# ----
# None
# ----
observed = normalization.invert_normalize_line_strings(None, None)
assert observed is None
# ----
# single LineString instance
# ----
before = ia.LineString(coords1)
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, ia.LineString)
assert np.allclose(after.coords, coords1)
# ----
# single LineStringsOnImage instance
# ----
before = ia.LineStringsOnImage([ia.LineString(coords1)], shape=(1, 1, 3))
after = _norm_and_invert(before, images=None)
assert isinstance(after, ia.LineStringsOnImage)
assert len(after.line_strings) == 1
assert np.allclose(after.line_strings[0].coords, coords1)
assert after.shape == (1, 1, 3)
# ----
# iterable of LineStringsOnImage
# ----
before = [
ia.LineStringsOnImage([ia.LineString(coords1)], shape=(1, 1, 3)),
ia.LineStringsOnImage([ia.LineString(coords2)], shape=(2, 1, 3))
]
after = _norm_and_invert(before, images=None)
assert isinstance(after, list)
assert len(after) == 2
assert isinstance(after[0], ia.LineStringsOnImage)
assert isinstance(after[1], ia.LineStringsOnImage)
assert np.allclose(after[0].line_strings[0].coords, coords1)
assert np.allclose(after[1].line_strings[0].coords, coords2)
assert after[0].shape == (1, 1, 3)
assert after[1].shape == (2, 1, 3)
# ----
# iterable of iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
for images in [[np.zeros((1, 1, 3), dtype=np.uint8)],
np.zeros((1, 1, 1, 3), dtype=np.uint8)]:
before = [[coords1_arr.astype(dt)]]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert isinstance(after[0], list)
assert len(after[0]) == 1
assert ia.is_np_array(after[0][0])
assert after[0][0].shape == (3, 2)
assert after[0][0].dtype.name == dt.name
assert np.allclose(after[0][0], coords1_arr)
before = [[coords1_arr.astype(dt) for _ in sm.xrange(5)]]
after = _norm_and_invert(before, images=images)
assert isinstance(after, list)
assert len(after) == 1
assert isinstance(after[0], list)
assert len(after[0]) == 5
assert ia.is_np_array(after[0][0])
assert after[0][0].shape == (3, 2)
assert after[0][0].dtype.name == dt.name
assert np.allclose(after[0][0], coords1_arr)
# ----
# iterable of iterable of LineString
# ----
before = [
[ia.LineString(coords1), ia.LineString(coords2)],
[ia.LineString(coords3), ia.LineString(coords4)]
]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert isinstance(after[0], list)
assert isinstance(after[1], list)
assert len(after[0]) == 2
assert len(after[1]) == 2
assert np.allclose(after[0][0].coords, coords1)
assert np.allclose(after[0][1].coords, coords2)
assert np.allclose(after[1][0].coords, coords3)
assert np.allclose(after[1][1].coords, coords4)
# ----
# iterable of iterable of iterable of (x,y)
# ----
before = [[coords1, coords2], [coords3, coords4]]
after = _norm_and_invert(before,
images=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)])
assert isinstance(after, list)
assert len(after) == 2
assert len(after[0]) == 2
assert len(after[1]) == 2
assert after[0][0] == coords1
assert after[0][1] == coords2
assert after[1][0] == coords3
assert after[1][1] == coords4
def test_normalize_images(self):
assert normalization.normalize_images(None) is None
arr = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = normalization.normalize_images(arr)
assert ia.is_np_array(observed)
assert observed.shape == (1, 4, 4, 3)
assert observed.dtype.name == "uint8"
arr = np.zeros((1, 4, 4), dtype=np.uint8)
observed = normalization.normalize_images(arr)
assert ia.is_np_array(observed)
assert observed.shape == (1, 4, 4, 1)
assert observed.dtype.name == "uint8"
arr = np.zeros((4, 4), dtype=np.uint8)
observed = normalization.normalize_images(arr)
assert ia.is_np_array(observed)
assert observed.shape == (1, 4, 4, 1)
assert observed.dtype.name == "uint8"
observed = normalization.normalize_images([])
assert isinstance(observed, list)
assert len(observed) == 0
arr1 = np.zeros((4, 4), dtype=np.uint8)
arr2 = np.zeros((5, 5, 3), dtype=np.uint8)
observed = normalization.normalize_images([arr1, arr2])
assert isinstance(observed, list)
assert len(observed) == 2
assert ia.is_np_array(observed[0])
assert ia.is_np_array(observed[1])
assert observed[0].shape == (4, 4, 1)
assert observed[1].shape == (5, 5, 3)
assert observed[0].dtype.name == "uint8"
assert observed[1].dtype.name == "uint8"
with self.assertRaises(ValueError):
normalization.normalize_images(False)
def test_normalize_heatmaps(self):
# ----
# None
# ----
heatmaps_norm = normalization.normalize_heatmaps(None)
assert heatmaps_norm is None
# ----
# array
# ----
heatmaps_norm = normalization.normalize_heatmaps(
np.zeros((1, 1, 1, 1), dtype=np.float32) + 0.1,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(heatmaps_norm, list)
assert isinstance(heatmaps_norm[0], ia.HeatmapsOnImage)
assert np.allclose(heatmaps_norm[0].arr_0to1, 0 + 0.1)
heatmaps_norm = normalization.normalize_heatmaps(
np.zeros((1, 1, 1, 1), dtype=np.float32) + 0.1,
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(heatmaps_norm, list)
assert isinstance(heatmaps_norm[0], ia.HeatmapsOnImage)
assert np.allclose(heatmaps_norm[0].arr_0to1, 0 + 0.1)
# --> heatmaps for too many images
with self.assertRaises(ValueError):
_heatmaps_norm = normalization.normalize_heatmaps(
np.zeros((2, 1, 1, 1), dtype=np.float32) + 0.1,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few heatmaps
with self.assertRaises(ValueError):
_heatmaps_norm = normalization.normalize_heatmaps(
np.zeros((1, 1, 1, 1), dtype=np.float32) + 0.1,
np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> wrong channel number
with self.assertRaises(ValueError):
_heatmaps_norm = normalization.normalize_heatmaps(
np.zeros((1, 1, 1), dtype=np.float32) + 0.1,
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
# --> images None
with self.assertRaises(ValueError):
_heatmaps_norm = normalization.normalize_heatmaps(
np.zeros((1, 1, 1, 1), dtype=np.float32) + 0.1,
shapes=None
)
# ----
# single HeatmapsOnImage
# ----
heatmaps_norm = normalization.normalize_heatmaps(
ia.HeatmapsOnImage(
np.zeros((1, 1, 1), dtype=np.float32) + 0.1,
shape=(1, 1, 3)),
shapes=None
)
assert isinstance(heatmaps_norm, list)
assert isinstance(heatmaps_norm[0], ia.HeatmapsOnImage)
assert np.allclose(heatmaps_norm[0].arr_0to1, 0 + 0.1)
# ----
# empty iterable
# ----
heatmaps_norm = normalization.normalize_heatmaps(
[],
shapes=None
)
assert heatmaps_norm is None
# ----
# iterable of arrays
# ----
heatmaps_norm = normalization.normalize_heatmaps(
[np.zeros((1, 1, 1), dtype=np.float32) + 0.1],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(heatmaps_norm, list)
assert isinstance(heatmaps_norm[0], ia.HeatmapsOnImage)
assert np.allclose(heatmaps_norm[0].arr_0to1, 0 + 0.1)
heatmaps_norm = normalization.normalize_heatmaps(
[np.zeros((1, 1, 1), dtype=np.float32) + 0.1],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(heatmaps_norm, list)
assert isinstance(heatmaps_norm[0], ia.HeatmapsOnImage)
assert np.allclose(heatmaps_norm[0].arr_0to1, 0 + 0.1)
# --> heatmaps for too many images
with self.assertRaises(ValueError):
_heatmaps_norm = normalization.normalize_heatmaps(
[
np.zeros((1, 1, 1), dtype=np.float32) + 0.1,
np.zeros((1, 1, 1), dtype=np.float32) + 0.1
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few heatmaps
with self.assertRaises(ValueError):
_heatmaps_norm = normalization.normalize_heatmaps(
[np.zeros((1, 1, 1), dtype=np.float32) + 0.1],
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> images None
with self.assertRaises(ValueError):
_heatmaps_norm = normalization.normalize_heatmaps(
[np.zeros((1, 1, 1), dtype=np.float32) + 0.1],
shapes=None,
)
# --> wrong number of dimensions
with self.assertRaises(ValueError):
_heatmaps_norm = normalization.normalize_heatmaps(
[np.zeros((1, 1, 1, 1), dtype=np.float32) + 0.1],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
# ----
# iterable of HeatmapsOnImage
# ----
heatmaps_norm = normalization.normalize_heatmaps(
[ia.HeatmapsOnImage(
np.zeros((1, 1, 1), dtype=np.float32) + 0.1,
shape=(1, 1, 3))],
shapes=None
)
assert isinstance(heatmaps_norm, list)
assert isinstance(heatmaps_norm[0], ia.HeatmapsOnImage)
assert np.allclose(heatmaps_norm[0].arr_0to1, 0 + 0.1)
def test_normalize_segmentation_maps(self):
# ----
# None
# ----
segmaps_norm = normalization.normalize_segmentation_maps(None)
assert segmaps_norm is None
# ----
# array
# ----
for dt in [np.dtype("int32"), np.dtype("uint16"), np.dtype(bool)]:
# NOTE: use np.full(shape, 1, dtype=dt) here and below instead of
# np.zeros(shape, dtype=dt) + 1, because the latter one converts
# dtype bool_ to int64.
segmaps_norm = normalization.normalize_segmentation_maps(
np.full((1, 1, 1, 1), 1, dtype=dt),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(segmaps_norm, list)
assert isinstance(segmaps_norm[0], ia.SegmentationMapsOnImage)
assert np.allclose(segmaps_norm[0].arr[..., 0], 1)
segmaps_norm = normalization.normalize_segmentation_maps(
np.full((1, 1, 1, 1), 1, dtype=dt),
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(segmaps_norm, list)
assert isinstance(segmaps_norm[0], ia.SegmentationMapsOnImage)
assert np.allclose(segmaps_norm[0].arr[..., 0], 1)
# --> segmaps for too many images
with self.assertRaises(ValueError):
_segmaps_norm = normalization.normalize_segmentation_maps(
np.full((2, 1, 1), 1, dtype=dt),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few segmaps
with self.assertRaises(ValueError):
_segmaps_norm = normalization.normalize_segmentation_maps(
np.full((1, 1, 1), 1, dtype=dt),
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> images None
with self.assertRaises(ValueError):
_segmaps_norm = normalization.normalize_segmentation_maps(
np.full((1, 1, 1), 1, dtype=dt),
shapes=None
)
# ----
# single SegmentationMapsOnImage
# ----
segmaps_norm = normalization.normalize_segmentation_maps(
ia.SegmentationMapsOnImage(
np.full((1, 1, 1), 1, dtype=np.int32),
shape=(1, 1, 3)),
shapes=None
)
assert isinstance(segmaps_norm, list)
assert isinstance(segmaps_norm[0], ia.SegmentationMapsOnImage)
assert np.allclose(segmaps_norm[0].arr[..., 0], 0 + 1)
# ----
# empty iterable
# ----
segmaps_norm = normalization.normalize_segmentation_maps(
[], shapes=None
)
assert segmaps_norm is None
# ----
# iterable of arrays
# ----
for dt in [np.dtype("int32"), np.dtype("uint16"), np.dtype(bool)]:
segmaps_norm = normalization.normalize_segmentation_maps(
[np.full((1, 1, 1), 1, dtype=dt)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(segmaps_norm, list)
assert isinstance(segmaps_norm[0], ia.SegmentationMapsOnImage)
assert np.allclose(segmaps_norm[0].arr[..., 0], 1)
segmaps_norm = normalization.normalize_segmentation_maps(
[np.full((1, 1, 1), 1, dtype=dt)],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(segmaps_norm, list)
assert isinstance(segmaps_norm[0], ia.SegmentationMapsOnImage)
assert np.allclose(segmaps_norm[0].arr[..., 0], 1)
# --> segmaps for too many images
with self.assertRaises(ValueError):
_segmaps_norm = normalization.normalize_segmentation_maps(
[
np.full((1, 1, 1), 1, dtype=np.int32),
np.full((1, 1, 1), 1, dtype=np.int32)
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few segmaps
with self.assertRaises(ValueError):
_segmaps_norm = normalization.normalize_segmentation_maps(
[np.full((1, 1, 1), 1, dtype=np.int32)],
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> images None
with self.assertRaises(ValueError):
_segmaps_norm = normalization.normalize_segmentation_maps(
[np.full((1, 1, 1), 1, dtype=np.int32)],
shapes=None
)
# --> wrong number of dimensions
with self.assertRaises(ValueError):
_segmaps_norm = normalization.normalize_segmentation_maps(
[np.full((1, 1, 1, 1), 1, dtype=np.int32)],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
# ----
# iterable of SegmentationMapsOnImage
# ----
segmaps_norm = normalization.normalize_segmentation_maps(
[ia.SegmentationMapsOnImage(
np.full((1, 1, 1), 1, dtype=np.int32),
shape=(1, 1, 3))],
shapes=None
)
assert isinstance(segmaps_norm, list)
assert isinstance(segmaps_norm[0], ia.SegmentationMapsOnImage)
assert np.allclose(segmaps_norm[0].arr[..., 0], 1)
def test_normalize_keypoints(self):
def _assert_single_image_expected(inputs):
# --> images None
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
inputs, None)
# --> too many images
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
inputs,
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> too many images
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
inputs,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# None
# ----
keypoints_norm = normalization.normalize_keypoints(None)
assert keypoints_norm is None
# ----
# array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
keypoints_norm = normalization.normalize_keypoints(
np.zeros((1, 1, 2), dtype=dt) + 1,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 1
assert np.allclose(keypoints_norm[0].to_xy_array(), 1)
keypoints_norm = normalization.normalize_keypoints(
np.zeros((1, 5, 2), dtype=dt) + 1,
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 5
assert np.allclose(keypoints_norm[0].to_xy_array(), 1)
# --> keypoints for too many images
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
np.zeros((2, 1, 2), dtype=dt) + 1,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few keypoints
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
np.zeros((1, 1, 2), dtype=dt) + 1,
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> wrong keypoints shape
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
np.zeros((1, 1, 100), dtype=dt) + 1,
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
_assert_single_image_expected(np.zeros((1, 1, 2), dtype=dt) + 1)
# ----
# (x,y)
# ----
keypoints_norm = normalization.normalize_keypoints(
(1, 2),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 1
assert keypoints_norm[0].keypoints[0].x == 1
assert keypoints_norm[0].keypoints[0].y == 2
_assert_single_image_expected((1, 2))
# ----
# single Keypoint instance
# ----
keypoints_norm = normalization.normalize_keypoints(
ia.Keypoint(x=1, y=2),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 1
assert keypoints_norm[0].keypoints[0].x == 1
assert keypoints_norm[0].keypoints[0].y == 2
_assert_single_image_expected(ia.Keypoint(x=1, y=2))
# ----
# single KeypointsOnImage instance
# ----
keypoints_norm = normalization.normalize_keypoints(
ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)], shape=(1, 1, 3)),
shapes=None
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 1
assert keypoints_norm[0].keypoints[0].x == 1
assert keypoints_norm[0].keypoints[0].y == 2
# ----
# empty iterable
# ----
keypoints_norm = normalization.normalize_keypoints(
[], shapes=None
)
assert keypoints_norm is None
# ----
# iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
keypoints_norm = normalization.normalize_keypoints(
[np.zeros((1, 2), dtype=dt) + 1],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 1
assert np.allclose(keypoints_norm[0].to_xy_array(), 1)
keypoints_norm = normalization.normalize_keypoints(
[np.zeros((5, 2), dtype=dt) + 1],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 5
assert np.allclose(keypoints_norm[0].to_xy_array(), 1)
# --> keypoints for too many images
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[
np.zeros((1, 2), dtype=dt) + 1,
np.zeros((1, 2), dtype=dt) + 1
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few keypoints
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[np.zeros((1, 2), dtype=dt) + 1],
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> images None
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[np.zeros((1, 2), dtype=dt) + 1],
shapes=None
)
# --> wrong shape
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[np.zeros((1, 100), dtype=dt) + 1],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
# ----
# iterable of (x,y)
# ----
keypoints_norm = normalization.normalize_keypoints(
[(1, 2), (3, 4)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 2
assert keypoints_norm[0].keypoints[0].x == 1
assert keypoints_norm[0].keypoints[0].y == 2
assert keypoints_norm[0].keypoints[1].x == 3
assert keypoints_norm[0].keypoints[1].y == 4
# may only be used for single images
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[(1, 2)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of Keypoint
# ----
keypoints_norm = normalization.normalize_keypoints(
[ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 2
assert keypoints_norm[0].keypoints[0].x == 1
assert keypoints_norm[0].keypoints[0].y == 2
assert keypoints_norm[0].keypoints[1].x == 3
assert keypoints_norm[0].keypoints[1].y == 4
# may only be used for single images
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[ia.Keypoint(x=1, y=2)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of KeypointsOnImage
# ----
keypoints_norm = normalization.normalize_keypoints(
[
ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)], shape=(1, 1, 3)),
ia.KeypointsOnImage([ia.Keypoint(x=3, y=4)], shape=(1, 1, 3)),
],
shapes=None
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 1
assert keypoints_norm[0].keypoints[0].x == 1
assert keypoints_norm[0].keypoints[0].y == 2
assert isinstance(keypoints_norm[1], ia.KeypointsOnImage)
assert len(keypoints_norm[1].keypoints) == 1
assert keypoints_norm[1].keypoints[0].x == 3
assert keypoints_norm[1].keypoints[0].y == 4
# ----
# iterable of empty interables
# ----
keypoints_norm = normalization.normalize_keypoints(
[[]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert keypoints_norm is None
# ----
# iterable of iterable of (x,y)
# ----
keypoints_norm = normalization.normalize_keypoints(
[
[(1, 2), (3, 4)],
[(5, 6), (7, 8)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 2
assert keypoints_norm[0].keypoints[0].x == 1
assert keypoints_norm[0].keypoints[0].y == 2
assert keypoints_norm[0].keypoints[1].x == 3
assert keypoints_norm[0].keypoints[1].y == 4
assert len(keypoints_norm[1].keypoints) == 2
assert keypoints_norm[1].keypoints[0].x == 5
assert keypoints_norm[1].keypoints[0].y == 6
assert keypoints_norm[1].keypoints[1].x == 7
assert keypoints_norm[1].keypoints[1].y == 8
# --> images None
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[
[(1, 2), (3, 4)],
[(5, 6), (7, 8)]
],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[
[(1, 2), (3, 4)],
[(5, 6), (7, 8)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of iterable of Keypoint
# ----
keypoints_norm = normalization.normalize_keypoints(
[
[ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)],
[ia.Keypoint(x=5, y=6), ia.Keypoint(x=7, y=8)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(keypoints_norm, list)
assert isinstance(keypoints_norm[0], ia.KeypointsOnImage)
assert len(keypoints_norm[0].keypoints) == 2
assert keypoints_norm[0].keypoints[0].x == 1
assert keypoints_norm[0].keypoints[0].y == 2
assert keypoints_norm[0].keypoints[1].x == 3
assert keypoints_norm[0].keypoints[1].y == 4
assert len(keypoints_norm[1].keypoints) == 2
assert keypoints_norm[1].keypoints[0].x == 5
assert keypoints_norm[1].keypoints[0].y == 6
assert keypoints_norm[1].keypoints[1].x == 7
assert keypoints_norm[1].keypoints[1].y == 8
# --> images None
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[
[ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)],
[ia.Keypoint(x=5, y=6), ia.Keypoint(x=7, y=8)]
],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_keypoints_norm = normalization.normalize_keypoints(
[
[ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=4)],
[ia.Keypoint(x=5, y=6), ia.Keypoint(x=7, y=8)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
def test_normalize_bounding_boxes(self):
def _assert_single_image_expected(inputs):
# --> images None
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
inputs,
shapes=None
)
# --> too many images
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
inputs,
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> too many images
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
inputs,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# None
# ----
bbs_norm = normalization.normalize_bounding_boxes(None)
assert bbs_norm is None
# ----
# array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
bbs_norm = normalization.normalize_bounding_boxes(
np.zeros((1, 1, 4), dtype=dt) + 1,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 1
assert np.allclose(bbs_norm[0].to_xyxy_array(), 1)
bbs_norm = normalization.normalize_bounding_boxes(
np.zeros((1, 5, 4), dtype=dt) + 1,
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 5
assert np.allclose(bbs_norm[0].to_xyxy_array(), 1)
# --> bounding boxes for too many images
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
np.zeros((2, 1, 4), dtype=dt) + 1,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few bounding boxes
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
np.zeros((1, 1, 4), dtype=dt) + 1,
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> wrong keypoints shape
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
np.zeros((1, 1, 100), dtype=dt) + 1,
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
_assert_single_image_expected(np.zeros((1, 1, 4), dtype=dt) + 1)
# ----
# (x1,y1,x2,y2)
# ----
bbs_norm = normalization.normalize_bounding_boxes(
(1, 2, 3, 4),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 1
assert bbs_norm[0].bounding_boxes[0].x1 == 1
assert bbs_norm[0].bounding_boxes[0].y1 == 2
assert bbs_norm[0].bounding_boxes[0].x2 == 3
assert bbs_norm[0].bounding_boxes[0].y2 == 4
_assert_single_image_expected((1, 2, 3, 4))
# ----
# single BoundingBox instance
# ----
bbs_norm = normalization.normalize_bounding_boxes(
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 1
assert bbs_norm[0].bounding_boxes[0].x1 == 1
assert bbs_norm[0].bounding_boxes[0].y1 == 2
assert bbs_norm[0].bounding_boxes[0].x2 == 3
assert bbs_norm[0].bounding_boxes[0].y2 == 4
_assert_single_image_expected(ia.BoundingBox(x1=1, y1=2, x2=3, y2=4))
# ----
# single BoundingBoxesOnImage instance
# ----
bbs_norm = normalization.normalize_bounding_boxes(
ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)],
shape=(1, 1, 3)),
shapes=None
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 1
assert bbs_norm[0].bounding_boxes[0].x1 == 1
assert bbs_norm[0].bounding_boxes[0].y1 == 2
assert bbs_norm[0].bounding_boxes[0].x2 == 3
assert bbs_norm[0].bounding_boxes[0].y2 == 4
# ----
# empty iterable
# ----
bbs_norm = normalization.normalize_bounding_boxes([], shapes=None)
assert bbs_norm is None
# ----
# iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
bbs_norm = normalization.normalize_bounding_boxes(
[np.zeros((1, 4), dtype=dt) + 1],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 1
assert np.allclose(bbs_norm[0].to_xyxy_array(), 1)
bbs_norm = normalization.normalize_bounding_boxes(
[np.zeros((5, 4), dtype=dt) + 1],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 5
assert np.allclose(bbs_norm[0].to_xyxy_array(), 1)
# --> bounding boxes for too many images
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[
np.zeros((1, 4), dtype=dt) + 1,
np.zeros((1, 4), dtype=dt) + 1
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few bounding boxes
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[np.zeros((1, 4), dtype=dt) + 1],
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> images None
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[np.zeros((1, 4), dtype=dt) + 1],
shapes=None
)
# --> wrong shape
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[np.zeros((1, 100), dtype=dt) + 1],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
# ----
# iterable of (x1,y1,x2,y2)
# ----
bbs_norm = normalization.normalize_bounding_boxes(
[(1, 2, 3, 4), (5, 6, 7, 8)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 2
assert bbs_norm[0].bounding_boxes[0].x1 == 1
assert bbs_norm[0].bounding_boxes[0].y1 == 2
assert bbs_norm[0].bounding_boxes[0].x2 == 3
assert bbs_norm[0].bounding_boxes[0].y2 == 4
assert bbs_norm[0].bounding_boxes[1].x1 == 5
assert bbs_norm[0].bounding_boxes[1].y1 == 6
assert bbs_norm[0].bounding_boxes[1].x2 == 7
assert bbs_norm[0].bounding_boxes[1].y2 == 8
# may only be used for single images
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[(1, 2, 3, 4)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of BoundingBox
# ----
bbs_norm = normalization.normalize_bounding_boxes(
[
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 2
assert bbs_norm[0].bounding_boxes[0].x1 == 1
assert bbs_norm[0].bounding_boxes[0].y1 == 2
assert bbs_norm[0].bounding_boxes[0].x2 == 3
assert bbs_norm[0].bounding_boxes[0].y2 == 4
assert bbs_norm[0].bounding_boxes[1].x1 == 5
assert bbs_norm[0].bounding_boxes[1].y1 == 6
assert bbs_norm[0].bounding_boxes[1].x2 == 7
assert bbs_norm[0].bounding_boxes[1].y2 == 8
# may only be used for single images
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of BoundingBoxesOnImage
# ----
bbs_norm = normalization.normalize_bounding_boxes(
[
ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)],
shape=(1, 1, 3)),
ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)],
shape=(1, 1, 3))
],
shapes=None
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 1
assert bbs_norm[0].bounding_boxes[0].x1 == 1
assert bbs_norm[0].bounding_boxes[0].y1 == 2
assert bbs_norm[0].bounding_boxes[0].x2 == 3
assert bbs_norm[0].bounding_boxes[0].y2 == 4
assert isinstance(bbs_norm[1], ia.BoundingBoxesOnImage)
assert len(bbs_norm[1].bounding_boxes) == 1
assert bbs_norm[1].bounding_boxes[0].x1 == 5
assert bbs_norm[1].bounding_boxes[0].y1 == 6
assert bbs_norm[1].bounding_boxes[0].x2 == 7
assert bbs_norm[1].bounding_boxes[0].y2 == 8
# ----
# iterable of empty interables
# ----
bbs_norm = normalization.normalize_bounding_boxes(
[[]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert bbs_norm is None
# ----
# iterable of iterable of (x1,y1,x2,y2)
# ----
bbs_norm = normalization.normalize_bounding_boxes(
[
[(1, 2, 3, 4)],
[(5, 6, 7, 8), (9, 10, 11, 12)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 1
assert bbs_norm[0].bounding_boxes[0].x1 == 1
assert bbs_norm[0].bounding_boxes[0].y1 == 2
assert bbs_norm[0].bounding_boxes[0].x2 == 3
assert bbs_norm[0].bounding_boxes[0].y2 == 4
assert len(bbs_norm[1].bounding_boxes) == 2
assert bbs_norm[1].bounding_boxes[0].x1 == 5
assert bbs_norm[1].bounding_boxes[0].y1 == 6
assert bbs_norm[1].bounding_boxes[0].x2 == 7
assert bbs_norm[1].bounding_boxes[0].y2 == 8
assert bbs_norm[1].bounding_boxes[1].x1 == 9
assert bbs_norm[1].bounding_boxes[1].y1 == 10
assert bbs_norm[1].bounding_boxes[1].x2 == 11
assert bbs_norm[1].bounding_boxes[1].y2 == 12
# --> images None
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[
[(1, 2, 3, 4), (3, 4, 5, 6)],
[(5, 6, 7, 8), (7, 8, 9, 10)]
],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[
[(1, 2, 3, 4)],
[(5, 6, 7, 8)]
],
[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of iterable of Keypoint
# ----
bbs_norm = normalization.normalize_bounding_boxes(
[
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)],
[ia.BoundingBox(x1=9, y1=10, x2=11, y2=12),
ia.BoundingBox(x1=13, y1=14, x2=15, y2=16)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(bbs_norm, list)
assert isinstance(bbs_norm[0], ia.BoundingBoxesOnImage)
assert len(bbs_norm[0].bounding_boxes) == 2
assert bbs_norm[0].bounding_boxes[0].x1 == 1
assert bbs_norm[0].bounding_boxes[0].y1 == 2
assert bbs_norm[0].bounding_boxes[0].x2 == 3
assert bbs_norm[0].bounding_boxes[0].y2 == 4
assert bbs_norm[0].bounding_boxes[1].x1 == 5
assert bbs_norm[0].bounding_boxes[1].y1 == 6
assert bbs_norm[0].bounding_boxes[1].x2 == 7
assert bbs_norm[0].bounding_boxes[1].y2 == 8
assert len(bbs_norm[1].bounding_boxes) == 2
assert bbs_norm[1].bounding_boxes[0].x1 == 9
assert bbs_norm[1].bounding_boxes[0].y1 == 10
assert bbs_norm[1].bounding_boxes[0].x2 == 11
assert bbs_norm[1].bounding_boxes[0].y2 == 12
assert bbs_norm[1].bounding_boxes[1].x1 == 13
assert bbs_norm[1].bounding_boxes[1].y1 == 14
assert bbs_norm[1].bounding_boxes[1].x2 == 15
assert bbs_norm[1].bounding_boxes[1].y2 == 16
# --> images None
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)],
[ia.BoundingBox(x1=9, y1=10, x2=11, y2=12),
ia.BoundingBox(x1=13, y1=14, x2=15, y2=16)]
],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_bbs_norm = normalization.normalize_bounding_boxes(
[
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)],
[ia.BoundingBox(x1=9, y1=10, x2=11, y2=12),
ia.BoundingBox(x1=13, y1=14, x2=15, y2=16)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
def test_normalize_polygons(self):
def _assert_single_image_expected(inputs):
# --> images None
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
inputs, shapes=None)
# --> too many images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
inputs,
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8))
# --> too many images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
inputs,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
coords1 = [(0, 0), (10, 0), (10, 10)]
coords2 = [(5, 5), (15, 5), (15, 15)]
coords3 = [(0, 0), (10, 0), (10, 10), (0, 10)]
coords4 = [(5, 5), (15, 5), (15, 15), (5, 15)]
coords1_kps = [ia.Keypoint(x=x, y=y) for x, y in coords1]
coords2_kps = [ia.Keypoint(x=x, y=y) for x, y in coords2]
coords3_kps = [ia.Keypoint(x=x, y=y) for x, y in coords3]
coords4_kps = [ia.Keypoint(x=x, y=y) for x, y in coords4]
coords1_arr = np.float32(coords1)
coords2_arr = np.float32(coords2)
coords3_arr = np.float32(coords3)
coords4_arr = np.float32(coords4)
# ----
# None
# ----
polygons_norm = normalization.normalize_polygons(None)
assert polygons_norm is None
# ----
# array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
polygons_norm = normalization.normalize_polygons(
coords1_arr[np.newaxis, np.newaxis, ...].astype(dt),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 1
assert np.allclose(polygons_norm[0].polygons[0].exterior,
coords1_arr)
polygons_norm = normalization.normalize_polygons(
np.tile(
coords1_arr[np.newaxis, np.newaxis, ...].astype(dt),
(1, 5, 1, 1)
),
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 5
assert np.allclose(polygons_norm[0].polygons[0].exterior,
coords1_arr)
# --> polygons for too many images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
np.tile(
coords1_arr[np.newaxis, np.newaxis, ...].astype(dt),
(2, 1, 1, 1)
),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few polygons
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
np.tile(
coords1_arr[np.newaxis, np.newaxis, ...].astype(dt),
(1, 1, 1, 1)
),
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> wrong polygons shape
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
np.tile(
coords1_arr[np.newaxis, np.newaxis, ...].astype(dt),
(1, 1, 1, 10)
),
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
_assert_single_image_expected(
coords1_arr[np.newaxis, np.newaxis, ...].astype(dt))
# ----
# single Polygon instance
# ----
polygons_norm = normalization.normalize_polygons(
ia.Polygon(coords1),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 1
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
_assert_single_image_expected(ia.Polygon(coords1))
# ----
# single PolygonsOnImage instance
# ----
polygons_norm = normalization.normalize_polygons(
ia.PolygonsOnImage([ia.Polygon(coords1)], shape=(1, 1, 3)),
shapes=None
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 1
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
# ----
# empty iterable
# ----
polygons_norm = normalization.normalize_polygons(
[], shapes=None
)
assert polygons_norm is None
# ----
# iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
polygons_norm = normalization.normalize_polygons(
[coords1_arr[np.newaxis, ...].astype(dt)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 1
assert np.allclose(polygons_norm[0].polygons[0].exterior,
coords1_arr)
polygons_norm = normalization.normalize_polygons(
[np.tile(
coords1_arr[np.newaxis, ...].astype(dt),
(5, 1, 1)
)],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 5
assert np.allclose(polygons_norm[0].polygons[0].exterior,
coords1_arr)
# --> polygons for too many images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[coords1_arr[np.newaxis, ...].astype(dt),
coords2_arr[np.newaxis, ...].astype(dt)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few polygons
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[coords1_arr[np.newaxis, ...].astype(dt)],
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> wrong polygons shape
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[np.tile(
coords1_arr[np.newaxis, ...].astype(dt),
(1, 1, 10)
)],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
_assert_single_image_expected(
[coords1_arr[np.newaxis, ...].astype(dt)]
)
# ----
# iterable of (x,y)
# ----
polygons_norm = normalization.normalize_polygons(
coords1,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 1
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
# may only be used for single images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
coords1,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of Keypoint
# ----
polygons_norm = normalization.normalize_polygons(
coords1_kps,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 1
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
# may only be used for single images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
coords1_kps,
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of Polygon
# ----
polygons_norm = normalization.normalize_polygons(
[ia.Polygon(coords1), ia.Polygon(coords2)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 2
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
assert polygons_norm[0].polygons[1].exterior_almost_equals(coords2)
# may only be used for single images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[ia.Polygon(coords1)],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of PolygonsOnImage
# ----
polygons_norm = normalization.normalize_polygons(
[
ia.PolygonsOnImage([ia.Polygon(coords1)], shape=(1, 1, 3)),
ia.PolygonsOnImage([ia.Polygon(coords2)], shape=(1, 1, 3))
],
shapes=None
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 1
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
assert isinstance(polygons_norm[1], ia.PolygonsOnImage)
assert len(polygons_norm[1].polygons) == 1
assert polygons_norm[1].polygons[0].exterior_almost_equals(coords2)
# ----
# iterable of empty iterables
# ----
polygons_norm = normalization.normalize_polygons(
[[]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert polygons_norm is None
# ----
# iterable of iterable of array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
polygons_norm = normalization.normalize_polygons(
[[coords1_arr.astype(dt)]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 1
assert np.allclose(polygons_norm[0].polygons[0].exterior,
coords1_arr)
polygons_norm = normalization.normalize_polygons(
[[
np.copy(coords1_arr).astype(dt) for _ in sm.xrange(5)
]],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 5
assert np.allclose(polygons_norm[0].polygons[0].exterior,
coords1_arr)
# --> polygons for too many images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[[coords1_arr.astype(dt)],
[coords2_arr.astype(dt)]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
# --> too few polygons
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[[coords1_arr.astype(dt)]],
shapes=np.zeros((2, 1, 1, 3), dtype=np.uint8)
)
# --> wrong polygons shape
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[[np.tile(
coords1_arr.astype(dt),
(1, 1, 10)
)]],
shapes=np.zeros((1, 1, 1, 3), dtype=np.uint8)
)
_assert_single_image_expected(
[[coords1_arr.astype(dt)]]
)
# ----
# iterable of iterable of (x,y)
# ----
polygons_norm = normalization.normalize_polygons(
[coords1, coords2],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 2
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
assert polygons_norm[0].polygons[1].exterior_almost_equals(coords2)
# --> images None
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[coords1, coords2],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[coords1, coords2],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of iterable of Keypoint
# ----
polygons_norm = normalization.normalize_polygons(
[coords1_kps, coords2_kps],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 2
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
assert polygons_norm[0].polygons[1].exterior_almost_equals(coords2)
# --> images None
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[coords1_kps, coords2_kps],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[coords1_kps, coords2_kps],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of iterable of Polygon
# ----
polygons_norm = normalization.normalize_polygons(
[
[ia.Polygon(coords1), ia.Polygon(coords2)],
[ia.Polygon(coords3), ia.Polygon(coords4)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert isinstance(polygons_norm[1], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 2
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
assert polygons_norm[0].polygons[1].exterior_almost_equals(coords2)
assert len(polygons_norm[1].polygons) == 2
assert polygons_norm[1].polygons[0].exterior_almost_equals(coords3)
assert polygons_norm[1].polygons[1].exterior_almost_equals(coords4)
# --> images None
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[
[ia.Polygon(coords1), ia.Polygon(coords2)],
[ia.Polygon(coords3), ia.Polygon(coords4)]
],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[
[ia.Polygon(coords1), ia.Polygon(coords2)],
[ia.Polygon(coords3), ia.Polygon(coords4)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of iterable of empty iterable
# ----
polygons_norm = normalization.normalize_polygons(
[[[]]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert polygons_norm is None
# ----
# iterable of iterable of iterable of (x,y)
# ----
polygons_norm = normalization.normalize_polygons(
[[coords1, coords2], [coords3, coords4]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 2
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
assert polygons_norm[0].polygons[1].exterior_almost_equals(coords2)
assert len(polygons_norm[0].polygons) == 2
assert polygons_norm[1].polygons[0].exterior_almost_equals(coords3)
assert polygons_norm[1].polygons[1].exterior_almost_equals(coords4)
# --> images None
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[[coords1, coords2]],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[[coords1, coords2], [coords3]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# ----
# iterable of iterable of iterable of Keypoint
# ----
polygons_norm = normalization.normalize_polygons(
[[coords1_kps, coords2_kps], [coords3_kps, coords4_kps]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(polygons_norm, list)
assert isinstance(polygons_norm[0], ia.PolygonsOnImage)
assert len(polygons_norm[0].polygons) == 2
assert polygons_norm[0].polygons[0].exterior_almost_equals(coords1)
assert polygons_norm[0].polygons[1].exterior_almost_equals(coords2)
assert len(polygons_norm[0].polygons) == 2
assert polygons_norm[1].polygons[0].exterior_almost_equals(coords3)
assert polygons_norm[1].polygons[1].exterior_almost_equals(coords4)
# --> images None
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[[coords1_kps, coords2_kps]],
shapes=None
)
# --> different number of images
with self.assertRaises(ValueError):
_polygons_norm = normalization.normalize_polygons(
[[coords1_kps, coords2_kps], [coords3_kps]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
# essentially already tested via polygons, as they are based on the
# same methods, hence a short test here
def test_normalize_line_strings(self):
coords1 = [(0, 0), (10, 0), (10, 10)]
coords2 = [(5, 5), (15, 5), (15, 15)]
coords3 = [(0, 0), (10, 0), (10, 10), (0, 10)]
coords4 = [(5, 5), (15, 5), (15, 15), (5, 15)]
coords1_arr = np.float32(coords1)
# ----
# None
# ----
lss_norm = normalization.normalize_line_strings(None)
assert lss_norm is None
# ----
# array
# ----
for dt in [np.dtype("float32"), np.dtype("int16"), np.dtype("uint16")]:
lss_norm = normalization.normalize_line_strings(
coords1_arr[np.newaxis, np.newaxis, ...].astype(dt),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(lss_norm, list)
assert isinstance(lss_norm[0], ia.LineStringsOnImage)
assert len(lss_norm[0].line_strings) == 1
assert np.allclose(lss_norm[0].line_strings[0].coords, coords1_arr)
# ----
# single LineString instance
# ----
lss_norm = normalization.normalize_line_strings(
ia.LineString(coords1),
shapes=[np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(lss_norm, list)
assert isinstance(lss_norm[0], ia.LineStringsOnImage)
assert len(lss_norm[0].line_strings) == 1
assert np.allclose(lss_norm[0].line_strings[0].coords, coords1)
# ----
# single LineStringOnImage instance
# ----
lss_norm = normalization.normalize_line_strings(
ia.LineStringsOnImage([ia.LineString(coords1)], shape=(1, 1, 3)),
shapes=None
)
assert isinstance(lss_norm, list)
assert isinstance(lss_norm[0], ia.LineStringsOnImage)
assert len(lss_norm[0].line_strings) == 1
assert np.allclose(lss_norm[0].line_strings[0].coords, coords1)
# ----
# empty iterable
# ----
lss_norm = normalization.normalize_line_strings(
[], shapes=None
)
assert lss_norm is None
# ----
# iterable of LineStringOnImage
# ----
lss_norm = normalization.normalize_line_strings(
[
ia.LineStringsOnImage(
[ia.LineString(coords1)], shape=(1, 1, 3)),
ia.LineStringsOnImage(
[ia.LineString(coords2)], shape=(1, 1, 3))
],
shapes=None
)
assert isinstance(lss_norm, list)
assert isinstance(lss_norm[0], ia.LineStringsOnImage)
assert len(lss_norm[0].line_strings) == 1
assert np.allclose(lss_norm[0].line_strings[0].coords, coords1)
assert isinstance(lss_norm[1], ia.LineStringsOnImage)
assert len(lss_norm[1].line_strings) == 1
assert np.allclose(lss_norm[1].line_strings[0].coords, coords2)
# ----
# iterable of iterable of LineString
# ----
lss_norm = normalization.normalize_line_strings(
[
[ia.LineString(coords1), ia.LineString(coords2)],
[ia.LineString(coords3), ia.LineString(coords4)]
],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(lss_norm, list)
assert isinstance(lss_norm[0], ia.LineStringsOnImage)
assert isinstance(lss_norm[1], ia.LineStringsOnImage)
assert len(lss_norm[0].line_strings) == 2
assert np.allclose(lss_norm[0].line_strings[0].coords, coords1)
assert np.allclose(lss_norm[0].line_strings[1].coords, coords2)
assert len(lss_norm[1].line_strings) == 2
assert np.allclose(lss_norm[1].line_strings[0].coords, coords3)
assert np.allclose(lss_norm[1].line_strings[1].coords, coords4)
# ----
# iterable of iterable of iterable of (x,y)
# ----
lss_norm = normalization.normalize_line_strings(
[[coords1, coords2], [coords3, coords4]],
shapes=[np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((1, 1, 3), dtype=np.uint8)]
)
assert isinstance(lss_norm, list)
assert isinstance(lss_norm[0], ia.LineStringsOnImage)
assert len(lss_norm[0].line_strings) == 2
assert np.allclose(lss_norm[0].line_strings[0].coords, coords1)
assert np.allclose(lss_norm[0].line_strings[1].coords, coords2)
assert len(lss_norm[0].line_strings) == 2
assert np.allclose(lss_norm[1].line_strings[0].coords, coords3)
assert np.allclose(lss_norm[1].line_strings[1].coords, coords4)
def test__find_first_nonempty(self):
# None
observed = normalization.find_first_nonempty(None)
assert observed[0] is None
assert observed[1] is True
assert len(observed[2]) == 0
# None with parents
observed = normalization.find_first_nonempty(None, parents=["foo"])
assert observed[0] is None
assert observed[1] is True
assert len(observed[2]) == 1
assert observed[2][0] == "foo"
# array
observed = normalization.find_first_nonempty(np.zeros((4, 4, 3)))
assert ia.is_np_array(observed[0])
assert observed[0].shape == (4, 4, 3)
assert observed[1] is True
assert len(observed[2]) == 0
# int
observed = normalization.find_first_nonempty(0)
assert observed[0] == 0
assert observed[1] is True
assert len(observed[2]) == 0
# str
observed = normalization.find_first_nonempty("foo")
assert observed[0] == "foo"
assert observed[1] is True
assert len(observed[2]) == 0
# empty list
observed = normalization.find_first_nonempty([])
assert observed[0] is None
assert observed[1] is False
assert len(observed[2]) == 0
# empty list of empty lists
observed = normalization.find_first_nonempty([[], [], []])
assert observed[0] is None
assert observed[1] is False
assert len(observed[2]) == 1
# empty list of empty lists of empty lists
observed = normalization.find_first_nonempty([[], [[]], []])
assert observed[0] is None
assert observed[1] is False
assert len(observed[2]) == 2
# list of None
observed = normalization.find_first_nonempty([None, None])
assert observed[0] is None
assert observed[1] is True
assert len(observed[2]) == 1
# list of array
observed = normalization.find_first_nonempty([
np.zeros((4, 4, 3)), np.zeros((5, 5, 3))])
assert ia.is_np_array(observed[0])
assert observed[0].shape == (4, 4, 3)
assert observed[1] is True
assert len(observed[2]) == 1
# list of list of array
observed = normalization.find_first_nonempty(
[[np.zeros((4, 4, 3))], [np.zeros((5, 5, 3))]]
)
assert ia.is_np_array(observed[0])
assert observed[0].shape == (4, 4, 3)
assert observed[1] is True
assert len(observed[2]) == 2
# list of tuple of array
observed = normalization.find_first_nonempty(
[
(
np.zeros((4, 4, 3)), np.zeros((5, 5, 3))
), (
np.zeros((6, 6, 3)), np.zeros((7, 7, 3))
)
]
)
assert ia.is_np_array(observed[0])
assert observed[0].shape == (4, 4, 3)
assert observed[1] is True
assert len(observed[2]) == 2
def test__nonempty_info_to_type_str(self):
ntype = normalization._nonempty_info_to_type_str(
None, True, [])
assert ntype == "None"
ntype = normalization._nonempty_info_to_type_str(
None, False, [])
assert ntype == "iterable[empty]"
ntype = normalization._nonempty_info_to_type_str(
None, False, [[]])
assert ntype == "iterable-iterable[empty]"
ntype = normalization._nonempty_info_to_type_str(
None, False, [[], []])
assert ntype == "iterable-iterable-iterable[empty]"
ntype = normalization._nonempty_info_to_type_str(
None, False, [tuple(), []])
assert ntype == "iterable-iterable-iterable[empty]"
ntype = normalization._nonempty_info_to_type_str(
1, True, [tuple([1, 2])])
assert ntype == "tuple[number,size=2]"
ntype = normalization._nonempty_info_to_type_str(
1, True, [[], tuple([1, 2])])
assert ntype == "iterable-tuple[number,size=2]"
ntype = normalization._nonempty_info_to_type_str(
1, True, [tuple([1, 2, 3, 4])])
assert ntype == "tuple[number,size=4]"
ntype = normalization._nonempty_info_to_type_str(
1, True, [[], tuple([1, 2, 3, 4])])
assert ntype == "iterable-tuple[number,size=4]"
with self.assertRaises(AssertionError):
ntype = normalization._nonempty_info_to_type_str(
1, True, [tuple([1, 2, 3])])
assert ntype == "tuple[number,size=4]"
ntype = normalization._nonempty_info_to_type_str(
np.zeros((4, 4, 3), dtype=np.uint8), True, [])
assert ntype == "array[uint]"
ntype = normalization._nonempty_info_to_type_str(
np.zeros((4, 4, 3), dtype=np.float32), True, [])
assert ntype == "array[float]"
ntype = normalization._nonempty_info_to_type_str(
np.zeros((4, 4, 3), dtype=np.int32), True, [])
assert ntype == "array[int]"
ntype = normalization._nonempty_info_to_type_str(
np.zeros((4, 4, 3), dtype=bool), True, [])
assert ntype == "array[bool]"
ntype = normalization._nonempty_info_to_type_str(
np.zeros((4, 4, 3), dtype=np.dtype("complex")), True, [])
assert ntype == "array[c]"
ntype = normalization._nonempty_info_to_type_str(
np.zeros((4, 4, 3), dtype=np.uint8), True, [[]])
assert ntype == "iterable-array[uint]"
ntype = normalization._nonempty_info_to_type_str(
np.zeros((4, 4, 3), dtype=np.uint8), True, [[], []])
assert ntype == "iterable-iterable-array[uint]"
cls_names = ["Keypoint", "KeypointsOnImage",
"BoundingBox", "BoundingBoxesOnImage",
"Polygon", "PolygonsOnImage",
"HeatmapsOnImage", "SegmentationMapsOnImage"]
clss = [
ia.Keypoint(x=1, y=1),
ia.KeypointsOnImage([], shape=(1, 1, 3)),
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4),
ia.BoundingBoxesOnImage([], shape=(1, 1, 3)),
ia.Polygon([(1, 1), (1, 2), (2, 2)]),
ia.PolygonsOnImage([], shape=(1,)),
ia.HeatmapsOnImage(np.zeros((1, 1, 1), dtype=np.float32),
shape=(1, 1, 3)),
ia.SegmentationMapsOnImage(np.zeros((1, 1, 1), dtype=np.int32),
shape=(1, 1, 3))
]
for cls_name, cls in zip(cls_names, clss):
ntype = normalization._nonempty_info_to_type_str(
cls, True, [])
assert ntype == cls_name
ntype = normalization._nonempty_info_to_type_str(
cls, True, [[]])
assert ntype == "iterable-%s" % (cls_name,)
ntype = normalization._nonempty_info_to_type_str(
cls, True, [[], tuple()])
assert ntype == "iterable-iterable-%s" % (cls_name,)
def test_estimate_heatmaps_norm_type(self):
ntype = normalization.estimate_heatmaps_norm_type(None)
assert ntype == "None"
ntype = normalization.estimate_heatmaps_norm_type(
np.zeros((1, 1, 1, 1), dtype=np.float32))
assert ntype == "array[float]"
ntype = normalization.estimate_heatmaps_norm_type(
ia.HeatmapsOnImage(
np.zeros((1, 1, 1), dtype=np.float32),
shape=(1, 1, 1)
)
)
assert ntype == "HeatmapsOnImage"
ntype = normalization.estimate_heatmaps_norm_type([])
assert ntype == "iterable[empty]"
ntype = normalization.estimate_heatmaps_norm_type(
[np.zeros((1, 1, 1), dtype=np.float32)])
assert ntype == "iterable-array[float]"
ntype = normalization.estimate_heatmaps_norm_type([
ia.HeatmapsOnImage(np.zeros((1, 1, 1), dtype=np.float32),
shape=(1, 1, 1))
])
assert ntype == "iterable-HeatmapsOnImage"
# --
# error cases
# --
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_heatmaps_norm_type(1)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_heatmaps_norm_type("foo")
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_heatmaps_norm_type(
np.zeros((1, 1, 1), dtype=np.int32))
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_heatmaps_norm_type([1])
# wrong class
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_heatmaps_norm_type(
ia.KeypointsOnImage([], shape=(1, 1, 1)))
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_heatmaps_norm_type([[]])
# list of list of Heatmaps, only list of Heatmaps is max
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_heatmaps_norm_type([
[ia.HeatmapsOnImage(np.zeros((1, 1, 1), dtype=np.float32),
shape=(1, 1, 1))]
])
def test_estimate_segmaps_norm_type(self):
ntype = normalization.estimate_segmaps_norm_type(None)
assert ntype == "None"
for name, dt in zip(["int", "uint", "bool"],
[np.int32, np.uint16, bool]):
ntype = normalization.estimate_segmaps_norm_type(
np.zeros((1, 1, 1, 1), dtype=dt))
assert ntype == "array[%s]" % (name,)
ntype = normalization.estimate_segmaps_norm_type(
ia.SegmentationMapsOnImage(
np.zeros((1, 1, 1), dtype=np.int32),
shape=(1, 1, 1)
)
)
assert ntype == "SegmentationMapsOnImage"
ntype = normalization.estimate_segmaps_norm_type([])
assert ntype == "iterable[empty]"
ntype = normalization.estimate_segmaps_norm_type(
[np.zeros((1, 1, 1), dtype=np.int32)])
assert ntype == "iterable-array[int]"
ntype = normalization.estimate_segmaps_norm_type([
ia.SegmentationMapsOnImage(np.zeros((1, 1, 1), dtype=np.int32),
shape=(1, 1, 1))
])
assert ntype == "iterable-SegmentationMapsOnImage"
# --
# error cases
# --
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_segmaps_norm_type(1)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_segmaps_norm_type("foo")
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_segmaps_norm_type([1])
# wrong class
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_segmaps_norm_type(
ia.KeypointsOnImage([], shape=(1, 1, 1)))
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_segmaps_norm_type([[]])
# list of list of SegMap, only list of SegMap is max
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_segmaps_norm_type([
[ia.SegmentationMapsOnImage(
np.zeros((1, 1, 1, 1), dtype=np.int32),
shape=(1, 1, 1))]
])
def test_estimate_keypoints_norm_type(self):
ntype = normalization.estimate_keypoints_norm_type(None)
assert ntype == "None"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_keypoints_norm_type(
np.zeros((1, 5, 2), dtype=dt))
assert ntype == "array[%s]" % (name,)
ntype = normalization.estimate_keypoints_norm_type((1, 2))
assert ntype == "tuple[number,size=2]"
ntype = normalization.estimate_keypoints_norm_type(
ia.Keypoint(x=1, y=2))
assert ntype == "Keypoint"
ntype = normalization.estimate_keypoints_norm_type(
ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)], shape=(1, 1, 3)))
assert ntype == "KeypointsOnImage"
ntype = normalization.estimate_keypoints_norm_type([])
assert ntype == "iterable[empty]"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_keypoints_norm_type(
[np.zeros((5, 2), dtype=dt)])
assert ntype == "iterable-array[%s]" % (name,)
ntype = normalization.estimate_keypoints_norm_type([(1, 2)])
assert ntype == "iterable-tuple[number,size=2]"
ntype = normalization.estimate_keypoints_norm_type(
[ia.Keypoint(x=1, y=2)])
assert ntype == "iterable-Keypoint"
ntype = normalization.estimate_keypoints_norm_type([
ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)], shape=(1, 1, 3))])
assert ntype == "iterable-KeypointsOnImage"
ntype = normalization.estimate_keypoints_norm_type([[]])
assert ntype == "iterable-iterable[empty]"
ntype = normalization.estimate_keypoints_norm_type([[(1, 2)]])
assert ntype == "iterable-iterable-tuple[number,size=2]"
ntype = normalization.estimate_keypoints_norm_type(
[[ia.Keypoint(x=1, y=2)]])
assert ntype == "iterable-iterable-Keypoint"
# --
# error cases
# --
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_keypoints_norm_type(1)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_keypoints_norm_type("foo")
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_keypoints_norm_type([1])
# wrong class
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_keypoints_norm_type(
ia.HeatmapsOnImage(np.zeros((1, 1, 1), dtype=np.float32),
shape=(1, 1, 1)))
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_keypoints_norm_type([[[]]])
# list of list of list of keypoints,
# only list of list of keypoints is max
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_keypoints_norm_type(
[[[ia.Keypoint(x=1, y=2)]]])
def test_estimate_bounding_boxes_norm_type(self):
ntype = normalization.estimate_bounding_boxes_norm_type(None)
assert ntype == "None"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_bounding_boxes_norm_type(
np.zeros((1, 5, 4), dtype=dt))
assert ntype == "array[%s]" % (name,)
ntype = normalization.estimate_bounding_boxes_norm_type((1, 2, 3, 4))
assert ntype == "tuple[number,size=4]"
ntype = normalization.estimate_bounding_boxes_norm_type(
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4))
assert ntype == "BoundingBox"
ntype = normalization.estimate_bounding_boxes_norm_type(
ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)], shape=(1, 1, 3)))
assert ntype == "BoundingBoxesOnImage"
ntype = normalization.estimate_bounding_boxes_norm_type([])
assert ntype == "iterable[empty]"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_bounding_boxes_norm_type(
[np.zeros((5, 4), dtype=dt)])
assert ntype == "iterable-array[%s]" % (name,)
ntype = normalization.estimate_bounding_boxes_norm_type([(1, 2, 3, 4)])
assert ntype == "iterable-tuple[number,size=4]"
ntype = normalization.estimate_bounding_boxes_norm_type([
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)])
assert ntype == "iterable-BoundingBox"
ntype = normalization.estimate_bounding_boxes_norm_type([
ia.BoundingBoxesOnImage([ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)],
shape=(1, 1, 3))])
assert ntype == "iterable-BoundingBoxesOnImage"
ntype = normalization.estimate_bounding_boxes_norm_type([[]])
assert ntype == "iterable-iterable[empty]"
ntype = normalization.estimate_bounding_boxes_norm_type(
[[(1, 2, 3, 4)]])
assert ntype == "iterable-iterable-tuple[number,size=4]"
ntype = normalization.estimate_bounding_boxes_norm_type(
[[ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]])
assert ntype == "iterable-iterable-BoundingBox"
# --
# error cases
# --
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_bounding_boxes_norm_type(1)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_bounding_boxes_norm_type("foo")
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_bounding_boxes_norm_type([1])
# wrong class
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_bounding_boxes_norm_type(
ia.HeatmapsOnImage(
np.zeros((1, 1, 1), dtype=np.float32),
shape=(1, 1, 1))
)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_bounding_boxes_norm_type([[[]]])
# list of list of list of bounding boxes,
# only list of list of bounding boxes is max
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_bounding_boxes_norm_type([[[
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]]])
def test_estimate_polygons_norm_type(self):
points = [(0, 0), (10, 0), (10, 10)]
ntype = normalization.estimate_polygons_norm_type(None)
assert ntype == "None"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_polygons_norm_type(
np.zeros((1, 2, 5, 2), dtype=dt)
)
assert ntype == "array[%s]" % (name,)
ntype = normalization.estimate_polygons_norm_type(
ia.Polygon(points)
)
assert ntype == "Polygon"
ntype = normalization.estimate_polygons_norm_type(
ia.PolygonsOnImage(
[ia.Polygon(points)], shape=(1, 1, 3))
)
assert ntype == "PolygonsOnImage"
ntype = normalization.estimate_polygons_norm_type([])
assert ntype == "iterable[empty]"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_polygons_norm_type(
[np.zeros((5, 4), dtype=dt)]
)
assert ntype == "iterable-array[%s]" % (name,)
ntype = normalization.estimate_polygons_norm_type(points)
assert ntype == "iterable-tuple[number,size=2]"
ntype = normalization.estimate_polygons_norm_type(
[ia.Keypoint(x=x, y=y) for x, y in points]
)
assert ntype == "iterable-Keypoint"
ntype = normalization.estimate_polygons_norm_type([ia.Polygon(points)])
assert ntype == "iterable-Polygon"
ntype = normalization.estimate_polygons_norm_type(
[ia.PolygonsOnImage([ia.Polygon(points)],
shape=(1, 1, 3))]
)
assert ntype == "iterable-PolygonsOnImage"
ntype = normalization.estimate_polygons_norm_type([[]])
assert ntype == "iterable-iterable[empty]"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_polygons_norm_type(
[[np.zeros((5, 4), dtype=dt)]]
)
assert ntype == "iterable-iterable-array[%s]" % (name,)
ntype = normalization.estimate_polygons_norm_type([points])
assert ntype == "iterable-iterable-tuple[number,size=2]"
ntype = normalization.estimate_polygons_norm_type([[
ia.Keypoint(x=x, y=y) for x, y in points
]])
assert ntype == "iterable-iterable-Keypoint"
ntype = normalization.estimate_polygons_norm_type(
[[ia.Polygon(points)]]
)
assert ntype == "iterable-iterable-Polygon"
ntype = normalization.estimate_polygons_norm_type([[[]]])
assert ntype == "iterable-iterable-iterable[empty]"
ntype = normalization.estimate_polygons_norm_type([[points]])
assert ntype == "iterable-iterable-iterable-tuple[number,size=2]"
ntype = normalization.estimate_polygons_norm_type(
[[[ia.Keypoint(x=x, y=y) for x, y in points]]]
)
assert ntype == "iterable-iterable-iterable-Keypoint"
# --
# error cases
# --
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_polygons_norm_type(1)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_polygons_norm_type("foo")
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_polygons_norm_type([1])
# wrong class
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_polygons_norm_type(
ia.HeatmapsOnImage(
np.zeros((1, 1, 1), dtype=np.float32),
shape=(1, 1, 1))
)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_polygons_norm_type([[[[]]]])
# list of list of list of polygons,
# only list of list of polygons is max
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_polygons_norm_type([[[
ia.Polygon(points)]]]
)
def test_estimate_line_strings_norm_type(self):
points = [(0, 0), (10, 0), (10, 10)]
ntype = normalization.estimate_line_strings_norm_type(None)
assert ntype == "None"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_line_strings_norm_type(
np.zeros((1, 2, 5, 2), dtype=dt)
)
assert ntype == "array[%s]" % (name,)
ntype = normalization.estimate_line_strings_norm_type(
ia.LineString(points)
)
assert ntype == "LineString"
ntype = normalization.estimate_line_strings_norm_type(
ia.LineStringsOnImage(
[ia.LineString(points)], shape=(1, 1, 3))
)
assert ntype == "LineStringsOnImage"
ntype = normalization.estimate_line_strings_norm_type([])
assert ntype == "iterable[empty]"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_line_strings_norm_type(
[np.zeros((5, 4), dtype=dt)]
)
assert ntype == "iterable-array[%s]" % (name,)
ntype = normalization.estimate_line_strings_norm_type(points)
assert ntype == "iterable-tuple[number,size=2]"
ntype = normalization.estimate_line_strings_norm_type(
[ia.Keypoint(x=x, y=y) for x, y in points]
)
assert ntype == "iterable-Keypoint"
ntype = normalization.estimate_line_strings_norm_type(
[ia.LineString(points)])
assert ntype == "iterable-LineString"
ntype = normalization.estimate_line_strings_norm_type(
[ia.LineStringsOnImage([ia.LineString(points)],
shape=(1, 1, 3))]
)
assert ntype == "iterable-LineStringsOnImage"
ntype = normalization.estimate_line_strings_norm_type([[]])
assert ntype == "iterable-iterable[empty]"
for name, dt in zip(["float", "int", "uint"],
[np.float32, np.int32, np.uint16]):
ntype = normalization.estimate_line_strings_norm_type(
[[np.zeros((5, 4), dtype=dt)]]
)
assert ntype == "iterable-iterable-array[%s]" % (name,)
ntype = normalization.estimate_line_strings_norm_type([points])
assert ntype == "iterable-iterable-tuple[number,size=2]"
ntype = normalization.estimate_line_strings_norm_type([[
ia.Keypoint(x=x, y=y) for x, y in points
]])
assert ntype == "iterable-iterable-Keypoint"
ntype = normalization.estimate_line_strings_norm_type(
[[ia.LineString(points)]]
)
assert ntype == "iterable-iterable-LineString"
ntype = normalization.estimate_line_strings_norm_type([[[]]])
assert ntype == "iterable-iterable-iterable[empty]"
ntype = normalization.estimate_line_strings_norm_type([[points]])
assert ntype == "iterable-iterable-iterable-tuple[number,size=2]"
ntype = normalization.estimate_line_strings_norm_type(
[[[ia.Keypoint(x=x, y=y) for x, y in points]]]
)
assert ntype == "iterable-iterable-iterable-Keypoint"
# --
# error cases
# --
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_line_strings_norm_type(1)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_line_strings_norm_type("foo")
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_line_strings_norm_type([1])
# wrong class
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_line_strings_norm_type(
ia.HeatmapsOnImage(
np.zeros((1, 1, 1), dtype=np.float32),
shape=(1, 1, 1))
)
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_line_strings_norm_type([[[[]]]])
# list of list of list of LineStrings,
# only list of list of LineStrings is max
with self.assertRaises(AssertionError):
_ntype = normalization.estimate_line_strings_norm_type([[[
ia.LineString(points)]]]
)
================================================
FILE: test/augmentables/test_polys.py
================================================
from __future__ import print_function, division, absolute_import
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
import shapely
import shapely.geometry
import imgaug as ia
import imgaug.random as iarandom
from imgaug.testutils import reseed, wrap_shift_deprecation, assertWarns
from imgaug.augmentables.polys import _ConcavePolygonRecoverer
class TestPolygon___init__(unittest.TestCase):
def test_exterior_is_list_of_keypoints(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=0.5, y=2.5)]
poly = ia.Polygon(kps)
assert poly.exterior.dtype.name == "float32"
assert np.allclose(
poly.exterior,
np.float32([
[0.0, 0.0],
[1.0, 1.0],
[0.5, 2.5]
])
)
def test_exterior_is_list_of_tuples_of_floats(self):
poly = ia.Polygon([(0.0, 0.0), (1.0, 1.0), (0.5, 2.5)])
assert poly.exterior.dtype.name == "float32"
assert np.allclose(
poly.exterior,
np.float32([
[0.0, 0.0],
[1.0, 1.0],
[0.5, 2.5]
])
)
def test_exterior_is_list_of_tuples_of_ints(self):
poly = ia.Polygon([(0, 0), (1, 1), (1, 3)])
assert poly.exterior.dtype.name == "float32"
assert np.allclose(
poly.exterior,
np.float32([
[0.0, 0.0],
[1.0, 1.0],
[1.0, 3.0]
])
)
def test_exterior_is_float32_array(self):
poly = ia.Polygon(
np.float32([
[0.0, 0.0],
[1.0, 1.0],
[0.5, 2.5]
])
)
assert poly.exterior.dtype.name == "float32"
assert np.allclose(
poly.exterior,
np.float32([
[0.0, 0.0],
[1.0, 1.0],
[0.5, 2.5]
])
)
def test_exterior_is_float64_array(self):
poly = ia.Polygon(
np.float64([
[0.0, 0.0],
[1.0, 1.0],
[0.5, 2.5]
])
)
assert poly.exterior.dtype.name == "float32"
assert np.allclose(
poly.exterior,
np.float32([
[0.0, 0.0],
[1.0, 1.0],
[0.5, 2.5]
])
)
def test_exterior_is_empty_list(self):
poly = ia.Polygon([])
assert poly.exterior.dtype.name == "float32"
assert poly.exterior.shape == (0, 2)
def test_exterior_is_empty_array(self):
poly = ia.Polygon(np.zeros((0, 2), dtype=np.float32))
assert poly.exterior.dtype.name == "float32"
assert poly.exterior.shape == (0, 2)
def test_fails_if_exterior_is_array_with_wrong_shape(self):
with self.assertRaises(AssertionError):
_ = ia.Polygon(np.zeros((8,), dtype=np.float32))
def test_label_is_none(self):
poly = ia.Polygon([(0, 0)])
assert poly.label is None
def test_label_is_string(self):
poly = ia.Polygon([(0, 0)], label="test")
assert poly.label == "test"
class TestPolygon_coords(unittest.TestCase):
def test_with_three_points(self):
poly = ia.Polygon([(0, 0), (1, 0.5), (1.5, 2.0)])
assert poly.coords is poly.exterior
class TestPolygon_xx(unittest.TestCase):
def test_filled_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1.5, 0), (4.1, 1), (2.9, 2.0)])
assert poly.xx.dtype.name == "float32"
assert np.allclose(poly.xx, np.float32([0.0, 1.0, 1.5, 4.1, 2.9]))
def test_empty_polygon(self):
poly = ia.Polygon([])
assert poly.xx.dtype.name == "float32"
assert poly.xx.shape == (0,)
class TestPolygon_yy(unittest.TestCase):
def test_filled_polygon(self):
poly = ia.Polygon([(0, 0), (0, 1), (0, 1.5), (1, 4.1), (2.0, 2.9)])
assert poly.yy.dtype.name == "float32"
assert np.allclose(poly.yy, np.float32([0.0, 1.0, 1.5, 4.1, 2.9]))
def test_empty_polygon(self):
poly = ia.Polygon([])
assert poly.yy.dtype.name == "float32"
assert poly.yy.shape == (0,)
class TestPolygon_xx_int(unittest.TestCase):
def test_filled_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1.5, 0), (4.1, 1), (2.9, 2.0)])
assert poly.xx_int.dtype.name == "int32"
assert np.allclose(poly.xx_int, np.int32([0, 1, 2, 4, 3]))
def test_empty_polygon(self):
poly = ia.Polygon([])
assert poly.xx_int.dtype.name == "int32"
assert poly.xx_int.shape == (0,)
class TestPolygon_yy_int(unittest.TestCase):
def test_filled_polygon(self):
poly = ia.Polygon([(0, 0), (0, 1), (0, 1.5), (1, 4.1), (2.0, 2.9)])
assert poly.yy_int.dtype.name == "int32"
assert np.allclose(poly.yy_int, np.int32([0, 1, 2, 4, 3]))
def test_empty_polygon(self):
poly = ia.Polygon([])
assert poly.yy_int.dtype.name == "int32"
assert poly.yy_int.shape == (0,)
class TestPolygon_is_valid(unittest.TestCase):
def test_filled_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert poly.is_valid
def test_empty_polygon(self):
poly = ia.Polygon([])
assert not poly.is_valid
def test_polygon_with_one_point(self):
poly = ia.Polygon([(0, 0)])
assert not poly.is_valid
def test_polygon_with_two_points(self):
poly = ia.Polygon([(0, 0), (1, 0)])
assert not poly.is_valid
def test_polygon_with_self_intersection(self):
# self intersection around the line segment from (0, 1) to (0, 0)
poly = ia.Polygon([(0, 0), (1, 0), (-1, 0.5), (1, 1), (0, 1)])
assert not poly.is_valid
def test_polygon_with_consecutive_identical_points(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 0), (1, 1), (0, 1)])
assert poly.is_valid
class TestPolygon_area(unittest.TestCase):
def test_square_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert 1.0 - 1e-8 < poly.area < 1.0 + 1e-8
def test_rectangular_polygon(self):
poly = ia.Polygon([(0, 0), (2, 0), (2, 1), (0, 1)])
assert 2.0 - 1e-8 < poly.area < 2.0 + 1e-8
def test_triangular_polygon(self):
poly = ia.Polygon([(0, 0), (1, 1), (0, 1)])
assert 1/2 - 1e-8 < poly.area < 1/2 + 1e-8
def test_polygon_with_two_points(self):
poly = ia.Polygon([(0, 0), (1, 1)])
assert np.isclose(poly.area, 0.0)
def test_polygon_with_one_point(self):
poly = ia.Polygon([(0, 0)])
assert np.isclose(poly.area, 0.0)
def test_polygon_with_zero_points(self):
poly = ia.Polygon([])
assert np.isclose(poly.area, 0.0)
class TestPolygon_height(unittest.TestCase):
def test_square_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert np.allclose(poly.height, 1.0, atol=1e-8, rtol=0)
def test_rectangular_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 2), (0, 2)])
assert np.allclose(poly.height, 2.0, atol=1e-8, rtol=0)
def test_triangular_polygon(self):
poly = ia.Polygon([(0, 0), (1, 1), (0, 1)])
assert np.allclose(poly.height, 1.0, atol=1e-8, rtol=0)
def test_polygon_with_two_points(self):
poly = ia.Polygon([(0, 0), (1, 1)])
assert np.allclose(poly.height, 1.0, atol=1e-8, rtol=0)
def test_polygon_with_one_point(self):
poly = ia.Polygon([(0, 0)])
assert np.allclose(poly.height, 0.0, atol=1e-8, rtol=0)
class TestPolygon_width(unittest.TestCase):
def test_square_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert np.allclose(poly.width, 1.0, atol=1e-8, rtol=0)
def test_rectangular_polygon(self):
poly = ia.Polygon([(0, 0), (2, 0), (2, 1), (0, 1)])
assert np.allclose(poly.width, 2.0, atol=1e-8, rtol=0)
def test_triangular_polygon(self):
poly = ia.Polygon([(0, 0), (1, 1), (0, 1)])
assert np.allclose(poly.width, 1.0, atol=1e-8, rtol=0)
def test_polygon_with_two_points(self):
poly = ia.Polygon([(0, 0), (1, 1)])
assert np.allclose(poly.width, 1.0, atol=1e-8, rtol=0)
def test_polygon_with_one_point(self):
poly = ia.Polygon([(0, 0)])
assert np.allclose(poly.width, 0.0, atol=1e-8, rtol=0)
class TestPolygon_project_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, poly, from_shape, to_shape):
return poly.project_(from_shape, to_shape)
def test_project_square_to_image_of_identical_shape(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_proj = self._func(poly, (1, 1), (1, 1))
assert poly_proj.exterior.dtype.name == "float32"
assert poly_proj.exterior.shape == (4, 2)
assert np.allclose(
poly_proj.exterior,
np.float32([
[0, 0],
[1, 0],
[1, 1],
[0, 1]
])
)
def test_project_square_to_image_with_twice_the_height_and_width(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_proj = self._func(poly, (1, 1), (2, 2))
assert poly_proj.exterior.dtype.name == "float32"
assert poly_proj.exterior.shape == (4, 2)
assert np.allclose(
poly_proj.exterior,
np.float32([
[0, 0],
[2, 0],
[2, 2],
[0, 2]
])
)
def test_project_square_to_image_with_twice_the_height_but_same_width(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_proj = self._func(poly, (1, 1), (2, 1))
assert poly_proj.exterior.dtype.name == "float32"
assert poly_proj.exterior.shape == (4, 2)
assert np.allclose(
poly_proj.exterior,
np.float32([
[0, 0],
[1, 0],
[1, 2],
[0, 2]
])
)
def test_project_empty_exterior(self):
poly = ia.Polygon([])
poly_proj = self._func(poly, (1, 1), (2, 2))
assert poly_proj.exterior.dtype.name == "float32"
assert poly_proj.exterior.shape == (0, 2)
def test_inplaceness(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly2 = self._func(poly, (1, 1), (1, 1))
if self._is_inplace:
assert poly is poly2
else:
assert poly is not poly2
class TestPolygon_project(TestPolygon_project_):
@property
def _is_inplace(self):
return False
def _func(self, poly, from_shape, to_shape):
return poly.project(from_shape, to_shape)
class TestPolygon_find_closest_point_idx(unittest.TestCase):
def test_without_return_distance(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
coords = [(0, 0), (1, 0), (1.0001, -0.001), (0.2, 0.2)]
expected_indices = [0, 1, 1, 0]
for (x, y), expected_index in zip(coords, expected_indices):
with self.subTest(x=x, y=0):
closest_idx = poly.find_closest_point_index(x=x, y=y)
assert closest_idx == expected_index
def test_with_return_distance(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
coords = [(0, 0), (0.1, 0.15), (0.9, 0.15)]
expected_indices = [0, 0, 1]
expected_distances = [
0.0,
np.sqrt((0.1**2) + (0.15**2)),
np.sqrt(((1.0-0.9)**2) + (0.15**2))
]
gen = zip(coords, expected_indices, expected_distances)
for (x, y), expected_index, expected_dist in gen:
with self.subTest(x=x, y=y):
closest_idx, distance = poly.find_closest_point_index(
x=x, y=y, return_distance=True)
assert closest_idx == expected_index
assert np.allclose(distance, expected_dist)
def test_fails_for_empty_exterior(self):
poly = ia.Polygon([])
with self.assertRaises(AssertionError):
_ = poly.find_closest_point_index(x=0, y=0)
class TestPolygon_compute_out_of_image_area(unittest.TestCase):
def test_fully_inside_image_plane(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
image_shape = (10, 20, 3)
area_ooi = poly.compute_out_of_image_area(image_shape)
assert np.isclose(area_ooi, 0.0)
def test_partially_outside_of_image_plane(self):
poly = ia.Polygon([(-1, 0), (1, 0), (1, 2), (-1, 2)])
image_shape = (10, 20, 3)
area_ooi = poly.compute_out_of_image_area(image_shape)
assert np.isclose(area_ooi, 2.0)
def test_fully_outside_of_image_plane(self):
poly = ia.Polygon([(-1, 0), (0, 0), (0, 1), (-1, 1)])
image_shape = (10, 20, 3)
area_ooi = poly.compute_out_of_image_area(image_shape)
assert np.isclose(area_ooi, 1.0)
def test_multiple_polygons_after_clip(self):
# two polygons inside the image area remain after clipping
# result is (area - poly1 - poly2) or here the part of the polygon
# that is left of the y-axis (x=0.0)
poly = ia.Polygon([(-10, 0), (5, 0), (5, 5), (-5, 5),
(-5, 10), (5, 10),
(5, 15), (-10, 15)])
image_shape = (15, 10, 3)
area_ooi = poly.compute_out_of_image_area(image_shape)
# the part left of the y-axis is not exactly square, but has a hole
# on its right (vertically centered), hence we have to subtract 5*5
assert np.isclose(area_ooi, 10*15 - 5*5)
class TestPolygon_compute_out_of_image_fraction(unittest.TestCase):
def test_polygon_with_zero_points(self):
poly = ia.Polygon([])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_polygon_with_one_point(self):
poly = ia.Polygon([(1.0, 1.0)])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_polygon_with_one_point_ooi(self):
poly = ia.Polygon([(-1.0, 1.0)])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 1.0)
def test_polygon_with_two_points(self):
poly = ia.Polygon([(1.0, 1.0), (2.0, 1.0)])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_polygon_with_two_points_one_ooi(self):
poly = ia.Polygon([(9.0, 1.0), (11.0, 1.0)])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.5, atol=1e-3)
def test_polygon_with_three_points_as_line(self):
poly = ia.Polygon([(9.0, 1.0), (10.0, 1.0), (11.0, 1.0)])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.5, atol=1e-3)
def test_standard_polygon_not_ooi(self):
poly = ia.Polygon([(1.0, 1.0), (2.0, 1.0), (2.0, 2.0)])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.0)
def test_standard_polygon_partially_ooi(self):
poly = ia.Polygon([(9.0, 1.0), (11.0, 1.0), (11.0, 3.0), (9.0, 3.0)])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 0.5, atol=1e-3)
def test_standard_polygon_fully_ooi(self):
poly = ia.Polygon([(11.0, 1.0), (13.0, 1.0), (13.0, 3.0), (11.0, 3.0)])
image_shape = (10, 10, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 1.0)
def test_zero_sized_image_axes(self):
poly = ia.Polygon([(1.0, 1.0), (2.0, 1.0), (2.0, 2.0)])
image_shape = (0, 0, 3)
factor = poly.compute_out_of_image_fraction(image_shape)
assert np.isclose(factor, 1.0)
class TestPolygon_is_fully_within_image(unittest.TestCase):
def test_barely_within_image__shape_as_3d_tuple(self):
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
assert poly.is_fully_within_image((1, 1, 3))
def test_barely_within_image__shape_as_2d_tuple(self):
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
assert poly.is_fully_within_image((1, 1))
def test_barely_within_image__shape_as_ndarray(self):
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
assert poly.is_fully_within_image(
np.zeros((1, 1, 3), dtype=np.uint8)
)
def test_right_and_bottom_sides_overlap__shape_as_3d_tuple(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert not poly.is_fully_within_image((1, 1, 3))
def test_right_and_bottom_sides_overlap__shape_as_2d_tuple(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert not poly.is_fully_within_image((1, 1))
def test_right_and_bottom_sides_overlap__shape_as_ndarray(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert not poly.is_fully_within_image(
np.zeros((1, 1, 3), dtype=np.uint8)
)
def test_far_outside_of_image__shape_as_3d_tuple(self):
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
assert not poly.is_fully_within_image((1, 1, 3))
def test_exterior_empty_fails(self):
poly = ia.Polygon([])
with self.assertRaises(Exception):
_ = poly.is_fully_within_image((1, 1, 3))
class TestPolygon_is_partly_within_image(unittest.TestCase):
def test_barely_within_image__shape_as_3d_tuple(self):
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
assert poly.is_partly_within_image((1, 1, 3))
def test_barely_within_image__shape_as_2d_tuple(self):
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
assert poly.is_partly_within_image((1, 1))
def test_barely_within_image__shape_as_ndarray(self):
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
assert poly.is_partly_within_image(
np.zeros((1, 1, 3), dtype=np.uint8)
)
def test_right_and_bottom_sides_overlap__shape_as_3d_tuple(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert poly.is_partly_within_image((1, 1, 3))
def test_right_and_bottom_sides_overlap__shape_as_2d_tuple(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert poly.is_partly_within_image((1, 1))
def test_right_and_bottom_sides_overlap__shape_as_ndarray(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert poly.is_partly_within_image(np.zeros((1, 1, 3), dtype=np.uint8))
def test_far_outside_of_image__shape_as_3d_tuple(self):
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
assert not poly.is_partly_within_image((1, 1, 3))
def test_far_outside_of_image__shape_as_2d_tuple(self):
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
assert not poly.is_partly_within_image((1, 1))
def test_far_outside_of_image__shape_as_ndarray(self):
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
assert not poly.is_partly_within_image(
np.zeros((1, 1, 3), dtype=np.uint8)
)
def test_exterior_empty_fails(self):
poly = ia.Polygon([])
with self.assertRaises(Exception):
_ = poly.is_partly_within_image((1, 1, 3))
class TestPolygon_is_out_of_image(unittest.TestCase):
def test_barely_within_image(self):
shapes = [(1, 1, 3), (1, 1), np.zeros((1, 1, 3), dtype=np.uint8)]
for shape in shapes:
shape_str = (
str(shape) if isinstance(shape, tuple) else str(shape.shape)
)
with self.subTest(shape=shape_str):
poly = ia.Polygon([(0, 0), (0.999, 0), (0.999, 0.999), (0, 0.999)])
is_ooi = poly.is_out_of_image
assert not is_ooi(shape, partly=False, fully=False)
assert not is_ooi(shape, partly=True, fully=False)
assert not is_ooi(shape, partly=False, fully=True)
assert not is_ooi(shape, partly=True, fully=True)
def test_right_and_bottom_sides_overlap__shape_as_ndarray(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
shape = np.zeros((1, 1, 3), dtype=np.uint8)
assert not poly.is_out_of_image(shape, partly=False, fully=False)
assert poly.is_out_of_image(shape, partly=True, fully=False)
assert not poly.is_out_of_image(shape, partly=False, fully=True)
assert poly.is_out_of_image(shape, partly=True, fully=True)
def test_far_outside_of_image__shape_as_3d_tuple(self):
poly = ia.Polygon([(100, 100), (101, 100), (101, 101), (100, 101)])
shape = (1, 1, 3)
assert not poly.is_out_of_image(shape, partly=False, fully=False)
assert not poly.is_out_of_image(shape, partly=True, fully=False)
assert poly.is_out_of_image(shape, partly=False, fully=True)
assert poly.is_out_of_image(shape, partly=True, fully=True)
def test_triangle_partially_outside_of_image(self):
poly = ia.Polygon([(8, 11), (11, 8), (11, 11)])
assert not poly.is_out_of_image((100, 100, 3), fully=True, partly=True)
assert not poly.is_out_of_image((10, 10, 3), fully=True, partly=False)
assert poly.is_out_of_image((10, 10, 3), fully=False, partly=True)
def test_rectangle_with_all_corners_outside_of_the_image(self):
poly = ia.Polygon([(-1.0, -1.0), (2.0, -1.0), (2.0, 2.0), (-1.0, 2.0)])
assert not poly.is_out_of_image((100, 100, 3), fully=True, partly=False)
assert poly.is_out_of_image((100, 100, 3), fully=False, partly=True)
assert not poly.is_out_of_image((1, 1, 3), fully=True, partly=False)
assert poly.is_out_of_image((1, 1, 3), fully=False, partly=True)
assert poly.is_out_of_image((1, 1, 3), fully=True, partly=True)
def test_polygon_with_two_points(self):
poly = ia.Polygon([(2.0, 2.0), (10.0, 2.0)])
assert not poly.is_out_of_image((100, 100, 3), fully=True, partly=False)
assert not poly.is_out_of_image((100, 100, 3), fully=False, partly=True)
assert not poly.is_out_of_image((3, 3, 3), fully=True, partly=False)
assert poly.is_out_of_image((3, 3, 3), fully=False, partly=True)
assert poly.is_out_of_image((1, 1, 3), fully=True, partly=False)
assert not poly.is_out_of_image((1, 1, 3), fully=False, partly=True)
def test_polygon_with_one_point(self):
poly = ia.Polygon([(2.0, 2.0)])
assert not poly.is_out_of_image((100, 100, 3), fully=True, partly=False)
assert not poly.is_out_of_image((100, 100, 3), fully=False, partly=True)
assert poly.is_out_of_image((1, 1, 3), fully=True, partly=False)
assert not poly.is_out_of_image((1, 1, 3), fully=False, partly=True)
def test_polygon_with_zero_points_fails(self):
poly = ia.Polygon([])
got_exception = False
try:
poly.is_out_of_image((1, 1, 3))
except Exception as exc:
assert (
"Cannot determine whether the polygon is inside the "
"image" in str(exc))
got_exception = True
assert got_exception
class TestPolygon_cut_out_of_image(unittest.TestCase):
@mock.patch("imgaug.augmentables.polys.Polygon.clip_out_of_image")
def test_warns_of_deprecation(self, mock_clip):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
polygon = ia.Polygon([(0, 0), (1, 0), (1, 1)])
shape = (1, 1)
_ = polygon.cut_out_of_image(shape)
mock_clip.assert_called_once_with(shape)
assert "is deprecated" in str(caught_warnings[0].message)
class TestPolygon_clip_out_of_image(unittest.TestCase):
def test_polygon_inside_of_image(self):
# poly inside image
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label=None)
image = np.zeros((1, 1, 3), dtype=np.uint8)
multipoly_clipped = poly.clip_out_of_image(image)
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(poly.exterior)
assert multipoly_clipped[0].label is None
def test_polygon_half_outside_of_image(self):
# square poly shifted by x=0.5, y=0.5 => half out of image
poly = ia.Polygon([(0.5, 0.5), (1.5, 0.5), (1.5, 1.5), (0.5, 1.5)],
label="test")
image = np.zeros((1, 1, 3), dtype=np.uint8)
multipoly_clipped = poly.clip_out_of_image(image)
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[0.5, 0.5],
[1.0, 0.5],
[1.0, 1.0],
[0.5, 1.0]
]))
assert multipoly_clipped[0].label == "test"
def test_single_edge_intersecting_with_image_edge(self):
# square poly with a single edge intersecting the image (issue #310)
poly = ia.Polygon([(-1.0, 0.0), (0.0, 0.0), (0.0, 1.0), (-1.0, 1.0)])
image = np.zeros((1, 1, 3), dtype=np.uint8)
multipoly_clipped = poly.clip_out_of_image(image)
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 0
def test_tiny_area_around_image_edge_intersecting(self):
# square poly with a tiny area on the left image edge intersecting with
# the image
offset = 1e-4
poly = ia.Polygon([(-1.0, 0.0), (0.0+offset, 0.0),
(0.0+offset, 1.0), (-1.0, 1.0)])
image = np.zeros((1, 1, 3), dtype=np.uint8)
multipoly_clipped = poly.clip_out_of_image(image)
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[0.0, 0.0],
[0.0+offset, 0.0],
[0.0+offset, 1.0],
[0.0, 1.0]
]))
def test_single_point_intersecting_with_image(self):
# square poly with a single point intersecting the image (issue #310)
poly = ia.Polygon([(-1.0, -1.0), (0.0, -1.0), (0.0, 0.0), (-1.0, 0.0)])
image = np.zeros((1, 1, 3), dtype=np.uint8)
multipoly_clipped = poly.clip_out_of_image(image)
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 0
def test_tiny_area_around_image_corner_point_intersecting_with_image(self):
# square poly with a tiny area around the top left image corner
# intersecting with the the image
offset = 1e-4
poly = ia.Polygon([(-1.0, -1.0), (0.0, -1.0),
(0.0+offset, 0.0+offset), (-1.0, 0.0)])
image = np.zeros((1, 1, 3), dtype=np.uint8)
multipoly_clipped = poly.clip_out_of_image(image)
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[0.0, 0.0],
[0.0+offset, 0.0],
[0.0+offset, 0.0+offset],
[0.0, 0.0+offset]
]))
def test_polygon_clipped_to_two_separate_polygons(self):
# non-square poly, with one rectangle on the left side of the image
# and one on the right side, both sides are connected by a thin strip
# below the image after clipping it should become two rectangles
poly = ia.Polygon([(-0.1, 0.0), (0.4, 0.0), (0.4, 1.1), (0.6, 1.1),
(0.6, 0.0), (1.1, 0.0), (1.1, 1.2), (-0.1, 1.2)],
label="test")
image = np.zeros((1, 1, 3), dtype=np.uint8)
multipoly_clipped = poly.clip_out_of_image(image)
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 2
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[0.0, 0.0],
[0.4, 0.0],
[0.4, 1.0],
[0.0, 1.0]
]))
assert multipoly_clipped[0].label == "test"
assert multipoly_clipped[1].exterior_almost_equals(np.float32([
[0.6, 0.0],
[1.0, 0.0],
[1.0, 1.0],
[0.6, 1.0]
]))
assert multipoly_clipped[0].label == "test"
def test_polygon_fully_outside_of_the_image(self):
# poly outside of image
poly = ia.Polygon([(10.0, 10.0), (11,.0, 10.0), (11.0, 11.0)])
multipoly_clipped = poly.clip_out_of_image((5, 5, 3))
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 0
def test_small_intersection_with_image_one_poly_point_inside_image(self):
# poly area partially inside image
# and one point is inside the image
poly = ia.Polygon([(50, 50), (110, 50), (110, 110), (50, 110)])
multipoly_clipped = poly.clip_out_of_image((100, 100, 3))
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[50, 50],
[100, 50],
[100, 100],
[50, 100]
]))
def test_small_intersection_with_image_no_poly_point_inside_image(self):
# poly area partially inside image,
# but not a single point is inside the image
poly = ia.Polygon([(100+0.5*100, 0),
(100+0.5*100, 100+0.5*100),
(0, 100+0.5*100)])
multipoly_clipped = poly.clip_out_of_image((100, 100, 3))
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[100, 0.5*100],
[100, 100],
[0.5*100, 100]
]))
def test_polygon_with_two_points_that_is_not_clipped(self):
# polygon with two points
poly = ia.Polygon([(2.0, 2.0), (10.0, 2.0)])
multipoly_clipped = poly.clip_out_of_image((100, 100, 3))
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[2.0, 2.0],
[10.0, 2.0]
]))
def test_polygon_with_two_points_that_is_clipped(self):
poly = ia.Polygon([(2.0, 2.0), (10.0, 2.0)])
multipoly_clipped = poly.clip_out_of_image((3, 3, 3))
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[2.0, 2.0],
[3.0, 2.0]
]), max_distance=1e-3)
def test_polygon_with_one_point_that_is_not_clipped(self):
# polygon with a single point
poly = ia.Polygon([(2.0, 2.0)])
multipoly_clipped = poly.clip_out_of_image((3, 3, 3))
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 1
assert multipoly_clipped[0].exterior_almost_equals(np.float32([
[2.0, 2.0]
]))
def test_polygon_with_one_point_that_is_clipped(self):
poly = ia.Polygon([(2.0, 2.0)])
multipoly_clipped = poly.clip_out_of_image((1, 1, 3))
assert isinstance(multipoly_clipped, list)
assert len(multipoly_clipped) == 0
class TestPolygon_shift_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, poly, *args, **kwargs):
def _func_impl():
return poly.shift_(*args, **kwargs)
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
@property
def poly(self):
return ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
def test_shift_along_xy(self):
poly_shifted = self._func(self.poly, x=1, y=2)
assert np.allclose(poly_shifted.exterior, np.float32([
[0 + 1, 0 + 2],
[1 + 1, 0 + 2],
[1 + 1, 1 + 2],
[0 + 1, 1 + 2]
]))
assert poly_shifted.label == "test"
def test_inplaceness(self):
poly = self.poly
poly2 = self._func(poly, y=1)
if self._is_inplace:
assert poly is poly2
else:
assert poly is not poly2
class TestPolygon_shift(TestPolygon_shift_):
@property
def _is_inplace(self):
return False
def _func(self, poly, *args, **kwargs):
def _func_impl():
return poly.shift(*args, **kwargs)
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_shift_does_not_work_inplace(self):
poly = self.poly
poly_shifted = self._func(poly, top=1)
assert np.allclose(poly.exterior, np.float32([
[0, 0],
[1, 0],
[1, 1],
[0, 1]
]))
assert np.allclose(poly_shifted.exterior, np.float32([
[0, 1],
[1, 1],
[1, 2],
[0, 2]
]))
def test_shift_from_top(self):
for v in [1, 0, -1, 0.5]:
with self.subTest(top=v):
poly_shifted = self._func(self.poly, top=v)
assert np.allclose(poly_shifted.exterior, np.float32([
[0, 0 + v],
[1, 0 + v],
[1, 1 + v],
[0, 1 + v]
]))
assert poly_shifted.label == "test"
def test_shift_from_bottom(self):
for v in [1, 0, -1, 0.5]:
with self.subTest(bottom=v):
poly_shifted = self._func(self.poly, bottom=v)
assert np.allclose(poly_shifted.exterior, np.float32([
[0, 0 - v],
[1, 0 - v],
[1, 1 - v],
[0, 1 - v]
]))
assert poly_shifted.label == "test"
def test_shift_from_top_and_bottom(self):
for v in [1, 0, -1, 0.5]:
with self.subTest(top=v, bottom=-v):
poly_shifted = self._func(self.poly, top=v, bottom=-v)
assert np.allclose(poly_shifted.exterior, np.float32([
[0, 0 + 2*v],
[1, 0 + 2*v],
[1, 1 + 2*v],
[0, 1 + 2*v]
]))
assert poly_shifted.label == "test"
def test_shift_from_left(self):
for v in [1, 0, -1, 0.5]:
with self.subTest(left=v):
poly_shifted = self._func(self.poly, left=v)
assert np.allclose(poly_shifted.exterior, np.float32([
[0 + v, 0],
[1 + v, 0],
[1 + v, 1],
[0 + v, 1]
]))
assert poly_shifted.label == "test"
def test_shift_from_right(self):
for v in [1, 0, -1, 0.5]:
with self.subTest(right=v):
poly_shifted = self._func(self.poly, right=v)
assert np.allclose(poly_shifted.exterior, np.float32([
[0 - v, 0],
[1 - v, 0],
[1 - v, 1],
[0 - v, 1]
]))
assert poly_shifted.label == "test"
def test_shift_from_left_and_right(self):
for v in [1, 0, -1, 0.5]:
with self.subTest(left=v, right=-v):
poly_shifted = self._func(self.poly, left=v, right=-v)
assert np.allclose(poly_shifted.exterior, np.float32([
[0 + 2 * v, 0],
[1 + 2 * v, 0],
[1 + 2 * v, 1],
[0 + 2 * v, 1]
]))
assert poly_shifted.label == "test"
class TestPolygon_draw_on_image(unittest.TestCase):
@property
def image(self):
return np.tile(
np.arange(100).reshape((10, 10, 1)),
(1, 1, 3)
).astype(np.uint8)
def test_square_polygon(self):
# simple drawing of square
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
image = self.image
image_poly = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=1.0,
alpha_lines=1.0,
alpha_points=0.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.uint8
assert image_poly.shape == (10, 10, 3)
# draw did not change original image (copy=True)
assert np.sum(image) == 3 * np.sum(np.arange(100))
for c_idx, value in enumerate([0, 255, 0]):
# left boundary
assert np.all(image_poly[2:9, 2:3, c_idx]
== np.zeros((7, 1), dtype=np.uint8) + value)
# right boundary
assert np.all(image_poly[2:9, 8:9, c_idx]
== np.zeros((7, 1), dtype=np.uint8) + value)
# top boundary
assert np.all(image_poly[2:3, 2:9, c_idx]
== np.zeros((1, 7), dtype=np.uint8) + value)
# bottom boundary
assert np.all(image_poly[8:9, 2:9, c_idx]
== np.zeros((1, 7), dtype=np.uint8) + value)
expected = np.tile(
np.uint8([32, 128, 32]).reshape((1, 1, 3)),
(5, 5, 1)
)
assert np.all(image_poly[3:8, 3:8, :] == expected)
def test_square_polygon_use_no_color_subargs(self):
# simple drawing of square, use only "color" arg
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
image = self.image
image_poly = poly.draw_on_image(image,
color=[0, 255, 0],
alpha=1.0,
alpha_face=1.0,
alpha_lines=1.0,
alpha_points=0.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.uint8
assert image_poly.shape == (10, 10, 3)
# draw did not change original image (copy=True)
assert np.sum(image) == 3 * np.sum(np.arange(100))
for c_idx, value in enumerate([0, 0.5*255, 0]):
value = int(np.round(value))
# left boundary
assert np.all(image_poly[2:9, 2:3, c_idx]
== np.zeros((7, 1), dtype=np.uint8) + value)
# right boundary
assert np.all(image_poly[2:9, 8:9, c_idx]
== np.zeros((7, 1), dtype=np.uint8) + value)
# top boundary
assert np.all(image_poly[2:3, 2:9, c_idx]
== np.zeros((1, 7), dtype=np.uint8) + value)
# bottom boundary
assert np.all(image_poly[8:9, 2:9, c_idx]
== np.zeros((1, 7), dtype=np.uint8) + value)
expected = np.tile(
np.uint8([0, 255, 0]).reshape((1, 1, 3)),
(5, 5, 1)
)
assert np.all(image_poly[3:8, 3:8, :] == expected)
def test_square_polygon_on_float32_image(self):
# simple drawing of square with float32 input
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
image = self.image
image_poly = poly.draw_on_image(image.astype(np.float32),
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=1.0,
alpha_lines=1.0,
alpha_points=0.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.float32
assert image_poly.shape == (10, 10, 3)
for c_idx, value in enumerate([0, 255, 0]):
# left boundary
assert np.allclose(image_poly[2:9, 2:3, c_idx],
np.zeros((7, 1), dtype=np.float32) + value)
# right boundary
assert np.allclose(image_poly[2:9, 8:9, c_idx],
np.zeros((7, 1), dtype=np.float32) + value)
# top boundary
assert np.allclose(image_poly[2:3, 2:9, c_idx],
np.zeros((1, 7), dtype=np.float32) + value)
# bottom boundary
assert np.allclose(image_poly[8:9, 2:9, c_idx],
np.zeros((1, 7), dtype=np.float32) + value)
expected = np.tile(
np.float32([32, 128, 32]).reshape((1, 1, 3)),
(5, 5, 1)
)
assert np.allclose(image_poly[3:8, 3:8, :], expected)
def test_square_polygon_half_outside_of_image(self):
# drawing of poly that is half out of image
poly = ia.Polygon([(2, 2+5), (8, 2+5), (8, 8+5), (2, 8+5)])
image = self.image
image_poly = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=1.0,
alpha_lines=1.0,
alpha_points=0.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.uint8
assert image_poly.shape == (10, 10, 3)
# draw did not change original image (copy=True)
assert np.sum(image) == 3 * np.sum(np.arange(100))
for c_idx, value in enumerate([0, 255, 0]):
# left boundary
assert np.all(image_poly[2+5:, 2:3, c_idx]
== np.zeros((3, 1), dtype=np.uint8) + value)
# right boundary
assert np.all(image_poly[2+5:, 8:9, c_idx]
== np.zeros((3, 1), dtype=np.uint8) + value)
# top boundary
assert np.all(image_poly[2+5:3+5, 2:9, c_idx]
== np.zeros((1, 7), dtype=np.uint8) + value)
expected = np.tile(
np.uint8([32, 128, 32]).reshape((1, 1, 3)),
(2, 5, 1)
)
assert np.all(image_poly[3+5:, 3:8, :] == expected)
def test_square_polygon_half_outside_of_image_with_raise_if_ooi(self):
# drawing of poly that is half out of image, with
# raise_if_out_of_image=True
poly = ia.Polygon([(2, 2+5), (8, 2+5), (8, 8+5), (0, 8+5)])
image = self.image
got_exception = False
try:
_ = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=1.0,
alpha_lines=1.0,
alpha_points=0.0,
raise_if_out_of_image=True)
except Exception as exc:
assert "Cannot draw polygon" in str(exc)
got_exception = True
# only polygons fully outside of the image plane lead to exceptions
assert not got_exception
def test_polygon_fully_outside_of_image(self):
# drawing of poly that is fully out of image
poly = ia.Polygon([(100, 100), (100+10, 100), (100+10, 100+10),
(100, 100+10)])
image = self.image
image_poly = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=1.0,
alpha_lines=1.0,
alpha_points=0.0,
raise_if_out_of_image=False)
assert np.array_equal(image_poly, image)
def test_polygon_fully_outside_of_image_with_raise_if_ooi(self):
# drawing of poly that is fully out of image,
# with raise_if_out_of_image=True
poly = ia.Polygon([(100, 100), (100+10, 100), (100+10, 100+10),
(100, 100+10)])
image = self.image
got_exception = False
try:
_ = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=1.0,
alpha_lines=1.0,
alpha_points=0.0,
raise_if_out_of_image=True)
except Exception as exc:
assert "Cannot draw polygon" in str(exc)
got_exception = True
assert got_exception
def test_only_lines_visible(self):
# face+points invisible via alpha
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
image = self.image
image_poly = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=0.0,
alpha_lines=1.0,
alpha_points=0.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.uint8
assert image_poly.shape == (10, 10, 3)
# draw did not change original image (copy=True)
assert np.sum(image) == 3 * np.sum(np.arange(100))
for c_idx, value in enumerate([0, 255, 0]):
# left boundary
assert np.all(image_poly[2:9, 2:3, c_idx]
== np.zeros((7, 1), dtype=np.uint8) + value)
assert np.all(image_poly[3:8, 3:8, :] == image[3:8, 3:8, :])
def test_only_face_visible(self):
# boundary+points invisible via alpha
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
image = self.image
image_poly = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=1.0,
alpha_lines=0.0,
alpha_points=0.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.uint8
assert image_poly.shape == (10, 10, 3)
# draw did not change original image (copy=True)
assert np.sum(image) == 3 * np.sum(np.arange(100))
expected = np.tile(
np.uint8([32, 128, 32]).reshape((1, 1, 3)), (6, 6, 1)
)
assert np.all(image_poly[2:8, 2:8, :] == expected)
def test_alpha_is_080(self):
# alpha=0.8
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
image = self.image
image_poly = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=0.8,
alpha_points=0.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.uint8
assert image_poly.shape == (10, 10, 3)
for c_idx, value in enumerate([0, 255, 0]):
expected = np.round(
(1-0.8)*image[2:9, 8:9, c_idx]
+ np.full((7, 1), 0.8*value, dtype=np.float32)
).astype(np.uint8)
# right boundary
assert np.all(image_poly[2:9, 8:9, c_idx] == expected)
expected = (0.8 * 0.5) * np.tile(
np.uint8([32, 128, 32]).reshape((1, 1, 3)), (5, 5, 1)
) + (1 - (0.8 * 0.5)) * image[3:8, 3:8, :]
assert np.all(image_poly[3:8, 3:8, :]
== np.round(expected).astype(np.uint8))
def test_face_and_lines_at_half_visibility(self):
# alpha of fill and perimeter 0.5
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
image = self.image
image_poly = poly.draw_on_image(image,
color=[32, 128, 32],
color_face=[32, 128, 32],
color_lines=[0, 255, 0],
color_points=[0, 255, 0],
alpha=1.0,
alpha_face=0.5,
alpha_lines=0.5,
alpha_points=0.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.uint8
assert image_poly.shape == (10, 10, 3)
for c_idx, value in enumerate([0, 255, 0]):
expected = np.round(
0.5*image[2:9, 8:9, c_idx]
+ np.full((7, 1), 0.5*value, dtype=np.float32)
).astype(np.uint8)
# right boundary
assert np.all(image_poly[2:9, 8:9, c_idx] == expected)
expected = 0.5 * np.tile(
np.uint8([32, 128, 32]).reshape((1, 1, 3)), (5, 5, 1)
) + 0.5 * image[3:8, 3:8, :]
assert np.all(image_poly[3:8, 3:8, :]
== np.round(expected).astype(np.uint8))
# copy=False
# test deactivated as the function currently does not offer a copy
# argument
"""
image_cp = np.copy(image)
poly = ia.Polygon([(2, 2), (8, 2), (8, 8), (2, 8)])
image_poly = poly.draw_on_image(image_cp,
color_face=[32, 128, 32],
color_boundary=[0, 255, 0],
alpha_face=1.0,
alpha_boundary=1.0,
raise_if_out_of_image=False)
assert image_poly.dtype.type == np.uint8
assert image_poly.shape == (10, 10, 3)
assert np.all(image_cp == image_poly)
assert not np.all(image_cp == image)
for c_idx, value in enumerate([0, 255, 0]):
# left boundary
assert np.all(image_poly[2:9, 2:3, c_idx]
== np.zeros((6, 1, 3), dtype=np.uint8) + value)
# left boundary
assert np.all(image_cp[2:9, 2:3, c_idx]
== np.zeros((6, 1, 3), dtype=np.uint8) + value)
expected = np.tile(
np.uint8([32, 128, 32]).reshape((1, 1, 3)),
(5, 5, 1)
)
assert np.all(image_poly[3:8, 3:8, :] == expected)
assert np.all(image_cp[3:8, 3:8, :] == expected)
"""
class TestPolygon_extract_from_image(unittest.TestCase):
@property
def image(self):
return np.arange(20*20*2).reshape((20, 20, 2)).astype(np.int32)
def test_polygon_is_identical_with_image_shape(self):
# inside image and completely covers it
poly = ia.Polygon([(0, 0), (10, 0), (10, 10), (0, 10)])
subimage = poly.extract_from_image(self.image)
assert np.array_equal(subimage, self.image[0:10, 0:10, :])
def test_polygon_is_subpart_of_image(self):
# inside image, subpart of it (not all of the image has to be
# extracted)
poly = ia.Polygon([(1, 1), (9, 1), (9, 9), (1, 9)])
subimage = poly.extract_from_image(self.image)
assert np.array_equal(subimage, self.image[1:9, 1:9, :])
def test_polygon_fully_inside_image__no_rectangular_shape(self):
# inside image, two image areas that don't belong to the polygon but
# have to be extracted
poly = ia.Polygon([(0, 0), (10, 0), (10, 5), (20, 5),
(20, 20), (10, 20), (10, 5), (0, 5)])
subimage = poly.extract_from_image(self.image)
expected = np.copy(self.image)
expected[:5, 10:, :] = 0 # top right block
expected[5:, :10, :] = 0 # left bottom block
assert np.array_equal(subimage, expected)
def test_polygon_is_partially_outside_of_image(self):
# partially out of image
poly = ia.Polygon([(-5, 0), (5, 0), (5, 10), (-5, 10)])
subimage = poly.extract_from_image(self.image)
expected = np.zeros((10, 10, 2), dtype=np.int32)
expected[0:10, 5:10, :] = self.image[0:10, 0:5, :]
assert np.array_equal(subimage, expected)
def test_polygon_is_fully_outside_of_image(self):
# fully out of image
poly = ia.Polygon([(30, 0), (40, 0), (40, 10), (30, 10)])
subimage = poly.extract_from_image(self.image)
expected = np.zeros((10, 10, 2), dtype=np.int32)
assert np.array_equal(subimage, expected)
def test_polygon_coords_after_rounding_are_identical_with_img_shape(self):
# inside image, subpart of it
# float coordinates, rounded so that the whole image will be extracted
poly = ia.Polygon([(0.4, 0.4), (9.6, 0.4), (9.6, 9.6), (0.4, 9.6)])
subimage = poly.extract_from_image(self.image)
assert np.array_equal(subimage, self.image[0:10, 0:10, :])
def test_polygon_coords_after_rounding_are_subpart_of_image(self):
# inside image, subpart of it
# float coordinates, rounded so that x/y 0<=i<9 will be extracted
# (instead of 0<=i<10)
poly = ia.Polygon([(0.5, 0.5), (9.4, 0.5), (9.4, 9.4), (0.5, 9.4)])
subimage = poly.extract_from_image(self.image)
assert np.array_equal(subimage, self.image[0:9, 0:9, :])
def test_polygon_coords_after_rounding_are_subpart_of_image2(self):
# inside image, subpart of it
# float coordinates, rounded so that x/y 1<=i<9 will be extracted
# (instead of 0<=i<10)
poly = ia.Polygon([(0.51, 0.51), (9.4, 0.51), (9.4, 9.4), (0.51, 9.4)])
subimage = poly.extract_from_image(self.image)
assert np.array_equal(subimage, self.image[1:9, 1:9, :])
def test_polygon_without_area_fails(self):
# error for invalid polygons
got_exception = False
poly = ia.Polygon([(0.51, 0.51), (9.4, 0.51)])
try:
_ = poly.extract_from_image(self.image)
except Exception as exc:
assert "Polygon must be made up" in str(exc)
got_exception = True
assert got_exception
class TestPolygon_change_first_point_by_coords(unittest.TestCase):
def test_change_to_first_point_in_exterior(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_reordered = poly.change_first_point_by_coords(x=0, y=0)
assert np.allclose(poly.exterior, poly_reordered.exterior)
def test_change_to_first_second_in_exterior(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_reordered = poly.change_first_point_by_coords(x=1, y=0)
# make sure that it does not reorder inplace
assert np.allclose(poly.exterior, np.float32([[0, 0], [1, 0], [1, 1]]))
assert np.allclose(poly_reordered.exterior,
np.float32([[1, 0], [1, 1], [0, 0]]))
def test_change_to_third_point_in_exterior(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_reordered = poly.change_first_point_by_coords(x=1, y=1)
assert np.allclose(poly_reordered.exterior,
np.float32([[1, 1], [0, 0], [1, 0]]))
def test_coords_slightly_off_from_target_point_limited_max_distance(self):
# inaccurate point, but close enough
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_reordered = poly.change_first_point_by_coords(x=1.0, y=0.01,
max_distance=0.1)
assert np.allclose(poly_reordered.exterior,
np.float32([[1, 0], [1, 1], [0, 0]]))
def test_coords_slightly_off_from_target_point_infinite_max_distance(self):
# inaccurate point, but close enough (infinite max distance)
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_reordered = poly.change_first_point_by_coords(x=1.0, y=0.01,
max_distance=None)
assert np.allclose(poly_reordered.exterior,
np.float32([[1, 0], [1, 1], [0, 0]]))
def test_closest_point_to_coords_exceeds_max_distance(self):
# point too far away
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
got_exception = False
try:
_ = poly.change_first_point_by_coords(x=1.0, y=0.01, max_distance=0.001)
except Exception as exc:
assert "Closest found point " in str(exc)
got_exception = True
assert got_exception
def test_polygon_with_two_points(self):
# reorder with two points
poly = ia.Polygon([(0, 0), (1, 0)])
poly_reordered = poly.change_first_point_by_coords(x=1, y=0)
assert np.allclose(poly_reordered.exterior,
np.float32([[1, 0], [0, 0]]))
def test_polygon_with_one_point(self):
# reorder with one point
poly = ia.Polygon([(0, 0)])
poly_reordered = poly.change_first_point_by_coords(x=0, y=0)
assert np.allclose(poly_reordered.exterior, np.float32([[0, 0]]))
def test_polygon_with_zero_points_fails(self):
# invalid polygon
got_exception = False
poly = ia.Polygon([])
try:
_ = poly.change_first_point_by_coords(x=0, y=0)
except Exception as exc:
assert "Cannot reorder polygon points" in str(exc)
got_exception = True
assert got_exception
class TestPolygon_change_first_point_by_index(unittest.TestCase):
def test_change_to_point_with_index_0(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_reordered = poly.change_first_point_by_index(0)
assert np.allclose(poly.exterior, poly_reordered.exterior)
def test_change_to_point_with_index_1(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_reordered = poly.change_first_point_by_index(1)
# make sure that it does not reorder inplace
assert np.allclose(poly.exterior,
np.float32([[0, 0], [1, 0], [1, 1]]))
assert np.allclose(poly_reordered.exterior,
np.float32([[1, 0], [1, 1], [0, 0]]))
def test_change_to_point_with_index_2(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_reordered = poly.change_first_point_by_index(2)
assert np.allclose(poly_reordered.exterior,
np.float32([[1, 1], [0, 0], [1, 0]]))
def test_polygon_with_two_points(self):
# reorder with two points
poly = ia.Polygon([(0, 0), (1, 0)])
poly_reordered = poly.change_first_point_by_index(1)
assert np.allclose(poly_reordered.exterior,
np.float32([[1, 0], [0, 0]]))
def test_polygon_with_one_point(self):
# reorder with one point
poly = ia.Polygon([(0, 0)])
poly_reordered = poly.change_first_point_by_index(0)
assert np.allclose(poly_reordered.exterior, np.float32([[0, 0]]))
def test_polygon_with_zero_points_fails(self):
poly = ia.Polygon([])
got_exception = False
try:
_ = poly.change_first_point_by_index(0)
except AssertionError:
got_exception = True
assert got_exception
def test_index_beyond_max_index_fails(self):
# idx out of bounds
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
got_exception = False
try:
_ = poly.change_first_point_by_index(3)
except AssertionError:
got_exception = True
assert got_exception
def test_index_beyond_max_index_fails__single_point_polygon(self):
poly = ia.Polygon([(0, 0)])
got_exception = False
try:
_ = poly.change_first_point_by_index(1)
except AssertionError:
got_exception = True
assert got_exception
def test_index_below_zero_fails(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
got_exception = False
try:
_ = poly.change_first_point_by_index(-1)
except AssertionError:
got_exception = True
assert got_exception
class TestPolygon_subdivide_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, poly, points_per_edge):
return poly.subdivide_(points_per_edge)
def test_zero_points(self):
poly = ia.Polygon([])
poly_sub = self._func(poly, 1)
assert len(poly_sub.exterior) == 0
def test_one_point(self):
poly = ia.Polygon([(1, 1)])
poly_sub = self._func(poly, 1)
assert len(poly_sub.exterior) == 1
def test_two_points(self):
poly = ia.Polygon([(1, 2), (2, 4)])
poly_sub = self._func(poly, 1)
assert len(poly_sub.exterior) == 4
assert poly_sub.coords_almost_equals([
(1, 2),
(1.5, 3.0),
(2, 4),
(1.5, 3.0)
])
def test_three_points(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_sub = self._func(poly, 1)
assert len(poly_sub.exterior) == 6
assert poly_sub.coords_almost_equals([
(0, 0),
(0.5, 0.0),
(1, 0),
(1, 0.5),
(1, 1),
(0.5, 0.5)
])
def test_three_points__n_points_0(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_sub = self._func(poly, 0)
assert len(poly_sub.exterior) == 3
assert poly_sub.coords_almost_equals([
(0, 0),
(1, 0),
(1, 1),
])
def test_three_points__n_points_2(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_sub = self._func(poly, 2)
assert len(poly_sub.exterior) == 3+2*3
assert poly_sub.coords_almost_equals([
(0, 0),
(1/3, 0),
(2/3, 0),
(1, 0),
(1, 1/3),
(1, 2/3),
(1, 1),
(2/3, 2/3),
(1/3, 1/3)
])
def test_label_none_is_preserved(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly_sub = self._func(poly, 1)
assert poly_sub.label is None
def test_label_str_is_preserved(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)], label="foo")
poly_sub = self._func(poly, 1)
assert poly_sub.label == "foo"
def test_inplaceness(self):
poly = ia.Polygon([(1, 2), (2, 4)])
poly2 = self._func(poly, 1)
if self._is_inplace:
assert poly is poly2
else:
assert poly is not poly2
class TestPolygon_subdivide(TestPolygon_subdivide_):
@property
def _is_inplace(self):
return False
def _func(self, poly, points_per_edge):
return poly.subdivide(points_per_edge)
class TestPolygon_to_shapely_line_string(unittest.TestCase):
def test_three_point_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
ls = poly.to_shapely_line_string()
assert np.allclose(ls.coords, np.float32([[0, 0], [1, 0], [1, 1]]))
def test_two_point_polygon(self):
# two point polygon
poly = ia.Polygon([(0, 0), (1, 0)])
ls = poly.to_shapely_line_string()
assert np.allclose(ls.coords, np.float32([[0, 0], [1, 0]]))
def test_one_point_polygon_fails(self):
# one point polygon
poly = ia.Polygon([(0, 0)])
got_exception = False
try:
_ = poly.to_shapely_line_string()
except Exception as exc:
assert (
"Conversion to shapely line string requires at least two "
"points" in str(exc))
got_exception = True
assert got_exception
def test_zero_point_polygon_fails(self):
# zero point polygon
poly = ia.Polygon([])
got_exception = False
try:
_ = poly.to_shapely_line_string()
except Exception as exc:
assert (
"Conversion to shapely line string requires at least two "
"points" in str(exc))
got_exception = True
assert got_exception
def test_closed_is_true(self):
# closed line string
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
ls = poly.to_shapely_line_string(closed=True)
assert np.allclose(ls.coords,
np.float32([[0, 0], [1, 0], [1, 1], [0, 0]]))
def test_interpolate_is_1(self):
# interpolation
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
ls = poly.to_shapely_line_string(interpolate=1)
assert np.allclose(
ls.coords,
np.float32([[0, 0], [0.5, 0], [1, 0], [1, 0.5],
[1, 1], [0.5, 0.5]]))
def test_interpolate_is_2(self):
# interpolation with 2 steps
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
ls = poly.to_shapely_line_string(interpolate=2)
assert np.allclose(ls.coords, np.float32([
[0, 0], [1/3, 0], [2/3, 0],
[1, 0], [1, 1/3], [1, 2/3],
[1, 1], [2/3, 2/3], [1/3, 1/3]
]))
def test_closed_is_true_and_interpolate_is_1(self):
# interpolation with closed=True
poly = ia.Polygon([(0, 0), (1, 0), (1, 1)])
ls = poly.to_shapely_line_string(closed=True, interpolate=1)
assert np.allclose(
ls.coords,
np.float32([[0, 0], [0.5, 0], [1, 0], [1, 0.5], [1, 1],
[0.5, 0.5], [0, 0]]))
class TestPolygon_to_shapely_polygon(unittest.TestCase):
def test_square_polygon(self):
exterior = [(0, 0), (1, 0), (1, 1), (0, 1)]
poly = ia.Polygon(exterior)
poly_shapely = poly.to_shapely_polygon()
gen = zip(exterior, poly_shapely.exterior.coords)
for (x_exp, y_exp), (x_obs, y_obs) in gen:
assert np.isclose(x_obs, x_exp, rtol=0, atol=1e-8)
assert np.isclose(y_obs, y_exp, rtol=0, atol=1e-8)
class TestPolygon_to_bounding_box(unittest.TestCase):
def test_square_polygon(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
bb = poly.to_bounding_box()
assert 0 - 1e-8 < bb.x1 < 0 + 1e-8
assert 0 - 1e-8 < bb.y1 < 0 + 1e-8
assert 1 - 1e-8 < bb.x2 < 1 + 1e-8
assert 1 - 1e-8 < bb.y2 < 1 + 1e-8
def test_triangular_polygon(self):
poly = ia.Polygon([(0.5, 0), (1, 1), (0, 1)])
bb = poly.to_bounding_box()
assert 0 - 1e-8 < bb.x1 < 0 + 1e-8
assert 0 - 1e-8 < bb.y1 < 0 + 1e-8
assert 1 - 1e-8 < bb.x2 < 1 + 1e-8
assert 1 - 1e-8 < bb.y2 < 1 + 1e-8
def test_triangular_polygon2(self):
poly = ia.Polygon([(0.5, 0.5), (2, 0.1), (1, 1)])
bb = poly.to_bounding_box()
assert 0.5 - 1e-8 < bb.x1 < 0.5 + 1e-8
assert 0.1 - 1e-8 < bb.y1 < 0.1 + 1e-8
assert 2.0 - 1e-8 < bb.x2 < 2.0 + 1e-8
assert 1.0 - 1e-8 < bb.y2 < 1.0 + 1e-8
class TestPolygon_to_line_string(unittest.TestCase):
def test_polygon_with_zero_points(self):
poly = ia.Polygon([])
ls = poly.to_line_string(closed=False)
assert len(ls.coords) == 0
assert ls.label is None
def test_polygon_with_zero_points__closed_is_true(self):
poly = ia.Polygon([])
ls = poly.to_line_string(closed=True)
assert len(ls.coords) == 0
assert ls.label is None
def test_polygon_with_zero_points__label_set(self):
poly = ia.Polygon([], label="foo")
ls = poly.to_line_string(closed=False)
assert len(ls.coords) == 0
assert ls.label == "foo"
def test_polygon_with_one_point(self):
poly = ia.Polygon([(0, 0)])
ls = poly.to_line_string(closed=False)
assert len(ls.coords) == 1
assert ls.label is None
def test_polygon_with_one_point__closed_is_true(self):
poly = ia.Polygon([(0, 0)])
ls = poly.to_line_string(closed=True)
assert len(ls.coords) == 1
assert ls.coords_almost_equals([(0, 0)])
assert ls.label is None
def test_polygon_with_two_points(self):
poly = ia.Polygon([(0, 0), (1, 1)])
ls = poly.to_line_string(closed=False)
assert len(ls.coords) == 2
assert ls.coords_almost_equals([(0, 0), (1, 1)])
assert ls.label is None
def test_polygon_with_two_point__closed_is_true(self):
poly = ia.Polygon([(0, 0), (1, 1)])
ls = poly.to_line_string(closed=True)
assert len(ls.coords) == 3
assert ls.coords_almost_equals([(0, 0), (1, 1), (0, 0)])
assert ls.label is None
def test_polygon_with_two_points__label_is_set(self):
poly = ia.Polygon([(0, 0), (1, 1)], label="foo")
ls = poly.to_line_string()
assert len(ls.coords) == 3
assert ls.coords_almost_equals([(0, 0), (1, 1), (0, 0)])
assert ls.label == "foo"
def test_polygon_with_two_point__closed_is_true_label_is_set(self):
poly = ia.Polygon([(0, 0), (1, 1)], label="foo")
ls = poly.to_line_string(closed=True)
assert len(ls.coords) == 3
assert ls.coords_almost_equals([(0, 0), (1, 1), (0, 0)])
assert ls.label == "foo"
class TestPolygon_from_shapely(unittest.TestCase):
def test_square_polygon(self):
exterior = [(0, 0), (1, 0), (1, 1), (0, 1)]
poly_shapely = shapely.geometry.Polygon(exterior)
poly = ia.Polygon.from_shapely(poly_shapely)
# shapely messes up the point ordering, so we try to correct it here
start_idx = 0
for i, (x, y) in enumerate(poly.exterior):
dist = np.sqrt((exterior[0][0] - x) ** 2
+ (exterior[0][1] - x) ** 2)
if dist < 1e-4:
start_idx = i
break
poly = poly.change_first_point_by_index(start_idx)
for (x_exp, y_exp), (x_obs, y_obs) in zip(exterior, poly.exterior):
assert x_exp - 1e-8 < x_obs < x_exp + 1e-8
assert y_exp - 1e-8 < y_obs < y_exp + 1e-8
def test_polygon_with_zero_points(self):
poly_shapely = shapely.geometry.Polygon([])
poly = ia.Polygon.from_shapely(poly_shapely)
assert len(poly.exterior) == 0
class TestPolygon_copy(unittest.TestCase):
def test_square_polygon_with_label(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
poly_cp = poly.copy()
assert poly.exterior.dtype.type == poly_cp.exterior.dtype.type
assert poly.exterior.shape == poly_cp.exterior.shape
assert np.allclose(poly.exterior, poly_cp.exterior)
assert poly.label == poly_cp.label
class TestPolygon_deepcopy(unittest.TestCase):
def test_square_polygon_with_label(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
poly_cp = poly.deepcopy()
assert poly.exterior.dtype.type == poly_cp.exterior.dtype.type
assert poly.exterior.shape == poly_cp.exterior.shape
assert np.allclose(poly.exterior, poly_cp.exterior)
assert poly.label == poly_cp.label
def test_copy_is_not_shallow(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
poly_cp = poly.deepcopy()
poly_cp.exterior[0, 0] = 100.0
poly_cp.label = "test2"
assert poly.exterior.dtype.type == poly_cp.exterior.dtype.type
assert poly.exterior.shape == poly_cp.exterior.shape
assert not np.allclose(poly.exterior, poly_cp.exterior)
assert not poly.label == poly_cp.label
class TestPolygon___repr___and___str__(unittest.TestCase):
def test_with_int_coordinates_provided_to_init(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label="test")
expected = (
"Polygon(["
"(x=0.000, y=0.000), "
"(x=1.000, y=0.000), "
"(x=1.000, y=1.000), "
"(x=0.000, y=1.000)"
"] (4 points), label=test)"
)
assert poly.__repr__() == expected
assert poly.__str__() == expected
def test_with_float_coordinates_provided_to_init(self):
poly = ia.Polygon([(0, 0.5), (1.5, 0), (1, 1), (0, 1)], label="test")
expected = (
"Polygon(["
"(x=0.000, y=0.500), "
"(x=1.500, y=0.000), "
"(x=1.000, y=1.000), "
"(x=0.000, y=1.000)"
"] (4 points), label=test)"
)
assert poly.__repr__() == expected
assert poly.__str__() == expected
def test_label_is_none(self):
poly = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)], label=None)
expected = (
"Polygon(["
"(x=0.000, y=0.000), "
"(x=1.000, y=0.000), "
"(x=1.000, y=1.000), "
"(x=0.000, y=1.000)"
"] (4 points), label=None)"
)
assert poly.__repr__() == expected
assert poly.__str__() == expected
def test_polygon_has_zero_points(self):
poly = ia.Polygon([], label="test")
expected = "Polygon([] (0 points), label=test)"
assert poly.__repr__() == expected
assert poly.__str__() == expected
class TestPolygon_coords_almost_equals(unittest.TestCase):
@mock.patch("imgaug.augmentables.polys.Polygon.exterior_almost_equals")
def test_calls_exterior_almost_equals(self, mock_eae):
mock_eae.return_value = "foo"
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
result = poly_a.coords_almost_equals(poly_b)
assert result == "foo"
mock_eae.assert_called_once_with(poly_b, max_distance=1e-4,
points_per_edge=8)
@mock.patch("imgaug.augmentables.polys.Polygon.exterior_almost_equals")
def test_calls_exterior_almost_equals__no_defaults(self, mock_eae):
mock_eae.return_value = "foo"
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
result = poly_a.coords_almost_equals(poly_b, max_distance=1,
points_per_edge=2)
assert result == "foo"
mock_eae.assert_called_once_with(poly_b, max_distance=1,
points_per_edge=2)
class TestPolygon_exterior_almost_equals(unittest.TestCase):
def test_exactly_same_exterior(self):
# exactly same exterior
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
assert poly_a.exterior_almost_equals(poly_b)
def test_one_point_duplicated(self):
# one point duplicated
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0, 0), (1, 0), (1, 1), (1, 1), (0, 1)])
assert poly_a.exterior_almost_equals(poly_b)
def test_several_points_added_without_changing_basic_shape(self):
# several points added without changing geometry
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0, 0), (0.5, 0), (1, 0), (1, 0.5), (1, 1), (0.5, 1), (0, 1), (0, 0.5)])
assert poly_a.exterior_almost_equals(poly_b)
def test_different_order(self):
# different order
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0, 1), (1, 1), (1, 0), (0, 0)])
assert poly_a.exterior_almost_equals(poly_b)
def test_tiny_shift_below_max_distance(self):
# tiny shift below tolerance
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0+1e-6, 0), (1+1e-6, 0), (1+1e-6, 1),
(0+1e-6, 1)])
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-3)
def test_tiny_shift_above_max_distance(self):
# tiny shift above tolerance
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0+1e-6, 0), (1+1e-6, 0), (1+1e-6, 1),
(0+1e-6, 1)])
assert not poly_a.exterior_almost_equals(poly_b, max_distance=1e-9)
def test_polygons_with_half_intersection(self):
# shifted polygon towards half overlap
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(0.5, 0), (1.5, 0), (1.5, 1), (0.5, 1)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_polygons_without_any_intersection(self):
# shifted polygon towards no overlap at all
poly_a = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly_b = ia.Polygon([(100, 0), (101, 0), (101, 1), (100, 1)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_zero_points(self):
# both polygons without points
poly_a = ia.Polygon([])
poly_b = ia.Polygon([])
assert poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_one_point__both_identical(self):
# both polygons with one point
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0)])
assert poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_zero_points__both_different(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(100, 100)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_zero_points__difference_below_max_dist(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0+1e-6, 0)])
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
def test_both_polygons_with_zero_points_difference_above_max_dist(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0+1, 0)])
assert not poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
def test_both_polygons_with_two_points__identical(self):
# both polygons with two points
poly_a = ia.Polygon([(0, 0), (1, 0)])
poly_b = ia.Polygon([(0, 0), (1, 0)])
assert poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_two_points__identical_and_zero_area(self):
poly_a = ia.Polygon([(0, 0), (0, 0)])
poly_b = ia.Polygon([(0, 0), (0, 0)])
assert poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_two_points__second_point_different(self):
poly_a = ia.Polygon([(0, 0), (1, 0)])
poly_b = ia.Polygon([(0, 0), (2, 0)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_two_points__difference_below_max_dist(self):
poly_a = ia.Polygon([(0, 0), (1, 0)])
poly_b = ia.Polygon([(0+1e-6, 0), (1+1e-6, 0)])
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
def test_both_polygons_with_three_points__identical(self):
# both polygons with three points
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
poly_b = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
assert poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_three_points__one_point_differs(self):
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
poly_b = ia.Polygon([(0, 0), (1, -1), (0.5, 1)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_both_polygons_with_three_points__difference_below_max_dist(self):
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
poly_b = ia.Polygon([(0, 0), (1+1e-6, 0), (0.5, 1)])
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
def test_one_polygon_zero_points_other_one_point(self):
# one polygon with zero points, other with one
poly_a = ia.Polygon([])
poly_b = ia.Polygon([(0, 0)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_one_point_other_zero_points(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_one_point_other_two_points(self):
# one polygon with one point, other with two
poly_a = ia.Polygon([(-10, -20)])
poly_b = ia.Polygon([(0, 0), (1, 0)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_one_point_other_two_points_2(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (1, 0)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_two_points_other_one_point(self):
poly_a = ia.Polygon([(0, 0), (1, 0)])
poly_b = ia.Polygon([(0, 0)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_two_points_other_one_point__all_identical(self):
poly_a = ia.Polygon([(0, 0), (0, 0)])
poly_b = ia.Polygon([(0, 0)])
assert poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_one_point_other_two_points__all_identical(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (0, 0)])
assert poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_two_points_other_one_point__diff_below_max_dist(self):
poly_a = ia.Polygon([(0, 0), (0+1e-6, 0)])
poly_b = ia.Polygon([(0, 0)])
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
def test_one_polygon_two_points_other_one_point__diff_above_max_dist(self):
poly_a = ia.Polygon([(0, 0), (0+1e-4, 0)])
poly_b = ia.Polygon([(0, 0)])
assert not poly_a.exterior_almost_equals(poly_b, max_distance=1e-9)
def test_one_polygon_one_point_other_three_points(self):
# one polygon with one point, other with three
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_three_points_other_one_point(self):
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
poly_b = ia.Polygon([(0, 0)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_one_point_other_three_points__all_identical(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (0, 0), (0, 0)])
assert poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_one_point_other_three_points__one_point_differs(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (0, 0), (1, 0)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_one_point_other_three_points__one_point_differs2(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (1, 0), (0, 0)])
assert not poly_a.exterior_almost_equals(poly_b)
def test_one_polygon_one_point_other_three_points__dist_below_max(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (0+1e-6, 0), (0, 0+1e-6)])
assert poly_a.exterior_almost_equals(poly_b, max_distance=1e-2)
def test_one_polygon_one_point_other_three_points__dist_above_max(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (0+1e-4, 0), (0, 0+1e-4)])
assert not poly_a.exterior_almost_equals(poly_b, max_distance=1e-9)
class TestPolygon_almost_equals(unittest.TestCase):
def test_both_polygons_empty(self):
poly_a = ia.Polygon([])
poly_b = ia.Polygon([])
assert poly_a.almost_equals(poly_b)
def test_both_polygons_one_point(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0)])
assert poly_a.almost_equals(poly_b)
def test_one_polygon_one_point_other_two_points__all_identical(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (0, 0)])
assert poly_a.almost_equals(poly_b)
def test_one_polygon_one_point_other_three_points__all_identical(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (0, 0), (0, 0)])
assert poly_a.almost_equals(poly_b)
def test_one_polygon_one_point_other_two_points__diff_below_max_dist(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (0+1e-10, 0)])
assert poly_a.almost_equals(poly_b)
def test_both_polygons_one_point__first_with_label(self):
poly_a = ia.Polygon([(0, 0)], label="test")
poly_b = ia.Polygon([(0, 0)])
assert not poly_a.almost_equals(poly_b)
def test_both_polygons_one_point__second_with_label(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0)], label="test")
assert not poly_a.almost_equals(poly_b)
def test_both_polygons_one_point__both_with_label(self):
poly_a = ia.Polygon([(0, 0)], label="test")
poly_b = ia.Polygon([(0, 0)], label="test")
assert poly_a.almost_equals(poly_b)
def test_both_polygons_one_point__both_with_label_but_point_differs(self):
poly_a = ia.Polygon([(0, 0)], label="test")
poly_b = ia.Polygon([(1, 0)], label="test")
assert not poly_a.almost_equals(poly_b)
def test_both_polygons_one_point__same_point_but_labels_differ(self):
poly_a = ia.Polygon([(0, 0)], label="testA")
poly_b = ia.Polygon([(0, 0)], label="testB")
assert not poly_a.almost_equals(poly_b)
def test_both_polygons_three_points(self):
poly_a = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
poly_b = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
assert poly_a.almost_equals(poly_b)
def test_one_polygon_one_point_other_three_points(self):
poly_a = ia.Polygon([(0, 0)])
poly_b = ia.Polygon([(0, 0), (1, 0), (0.5, 1)])
assert not poly_a.almost_equals(poly_b)
class TestPolygon___getitem__(unittest.TestCase):
def test_with_three_points(self):
cba = ia.Polygon([(1, 2), (3, 4), (5, 5)])
assert np.allclose(cba[0], (1, 2))
assert np.allclose(cba[1], (3, 4))
assert np.allclose(cba[2], (5, 5))
def test_with_three_points_slice(self):
cba = ia.Polygon([(1, 2), (3, 4), (5, 5)])
assert np.allclose(cba[1:], [(3, 4), (5, 5)])
class TestPolygon___iter__(unittest.TestCase):
def test_with_three_points(self):
cba = ia.Polygon([(1, 2), (3, 4), (5, 5)])
for i, xy in enumerate(cba):
assert i in [0, 1, 2]
if i == 0:
assert np.allclose(xy, (1, 2))
elif i == 1:
assert np.allclose(xy, (3, 4))
elif i == 2:
assert np.allclose(xy, (5, 5))
assert i == 2
def test_with_zero_points(self):
cba = ia.Polygon([])
i = 0
for xy in cba:
i += 1
assert i == 0
# TODO add test for _convert_points_to_shapely_line_string
class TestPolygonsOnImage___init__(unittest.TestCase):
def test_with_one_polygon(self):
# standard case with one polygon
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])],
shape=(10, 10, 3)
)
assert len(poly_oi.polygons) == 1
assert np.allclose(
poly_oi.polygons[0].exterior,
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0, atol=1e-4)
assert poly_oi.shape == (10, 10, 3)
def test_with_multiple_polygons(self):
# standard case with multiple polygons
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)])],
shape=(10, 10, 3)
)
assert len(poly_oi.polygons) == 3
assert np.allclose(
poly_oi.polygons[0].exterior,
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0, atol=1e-4)
assert np.allclose(
poly_oi.polygons[1].exterior,
[(0, 0), (1, 0), (1, 1)],
rtol=0, atol=1e-4)
assert np.allclose(
poly_oi.polygons[2].exterior,
[(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)],
rtol=0, atol=1e-4)
assert poly_oi.shape == (10, 10, 3)
def test_with_zero_polygons(self):
# list of polygons is empty
poly_oi = ia.PolygonsOnImage(
[],
shape=(10, 10, 3)
)
assert len(poly_oi.polygons) == 0
def test_with_invalid_polygon(self):
# invalid polygon
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (0.5, 0), (0.5, 1.5), (0, 1), (1, 1), (0, 1)])],
shape=(10, 10, 3)
)
assert len(poly_oi.polygons) == 1
assert np.allclose(
poly_oi.polygons[0].exterior,
[(0, 0), (0.5, 0), (0.5, 1.5), (0, 1), (1, 1), (0, 1)],
rtol=0, atol=1e-4)
def test_with_zero_polygons_and_shape_given_as_array(self):
# shape given as numpy array
with assertWarns(self, ia.DeprecationWarning):
poly_oi = ia.PolygonsOnImage(
[],
shape=np.zeros((10, 10, 3), dtype=np.uint8)
)
assert poly_oi.shape == (10, 10, 3)
def test_with_zero_polygons_and_shape_given_as_2d_tuple(self):
# 2D shape
poly_oi = ia.PolygonsOnImage(
[],
shape=(10, 11)
)
assert poly_oi.shape == (10, 11)
class TestPolygonsOnImage_items(unittest.TestCase):
def test_with_two_polygons(self):
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
poly2 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
psoi = ia.PolygonsOnImage(
[poly1, poly2],
shape=(10, 10, 3)
)
items = psoi.items
assert items == [poly1, poly2]
def test_items_empty(self):
psoi = ia.PolygonsOnImage([], shape=(40, 50, 3))
items = psoi.items
assert items == []
class TestPolygonsOnImage_items_setter(unittest.TestCase):
def test_with_list_of_polygons(self):
ps = [ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(1, 1), (2, 1), (2, 2)])]
psoi = ia.PolygonsOnImage([], shape=(10, 20, 3))
psoi.items = ps
assert np.all([
(np.allclose(ps_i.coords, ps_j.coords))
for ps_i, ps_j
in zip(psoi.polygons, ps)
])
class TestPolygonsOnImage_empty(unittest.TestCase):
def test_with_multiple_polygons(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)])],
shape=(10, 10, 3)
)
assert poly_oi.empty is False
def test_with_zero_polygons(self):
# list of polygons is empty
poly_oi = ia.PolygonsOnImage([], shape=(10, 10, 3))
assert poly_oi.empty is True
class TestPolygonsOnImage_on_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, psoi, image):
return psoi.on_(image)
def test_new_shape_is_identical_to_old_shape(self):
# size unchanged
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)]),
ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)])],
shape=(1, 1, 3)
)
poly_oi_proj = self._func(poly_oi, (1, 1, 3))
assert np.allclose(
poly_oi_proj.polygons[0].exterior,
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0, atol=1e-4)
assert np.allclose(
poly_oi_proj.polygons[1].exterior,
[(0, 0), (1, 0), (1, 1)],
rtol=0, atol=1e-4)
assert np.allclose(
poly_oi_proj.polygons[2].exterior,
[(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)],
rtol=0, atol=1e-4)
assert poly_oi_proj.shape == (1, 1, 3)
def test_new_shape_is_10x_smaller_than_old_shape(self):
# 10x decrease in size
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (10, 0), (10, 10), (0, 10)]),
ia.Polygon([(0, 0), (10, 0), (10, 10)]),
ia.Polygon([(5, 0), (10, 5), (5, 10), (0, 5)])],
shape=(10, 10, 3)
)
poly_oi_proj = self._func(poly_oi, (1, 1, 3))
assert np.allclose(
poly_oi_proj.polygons[0].exterior,
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0, atol=1e-4)
assert np.allclose(
poly_oi_proj.polygons[1].exterior,
[(0, 0), (1, 0), (1, 1)],
rtol=0, atol=1e-4)
assert np.allclose(
poly_oi_proj.polygons[2].exterior,
[(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)],
rtol=0, atol=1e-4)
assert poly_oi_proj.shape == (1, 1, 3)
def test_new_shape_is_2x_width_and_10x_height_of_old_shape(self):
# 2x increase in width, 10x decrease in height
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (50, 0), (50, 100), (0, 100)])],
shape=(100, 100, 3)
)
poly_oi_proj = self._func(poly_oi, (10, 200, 3))
assert np.allclose(
poly_oi_proj.polygons[0].exterior,
[(0, 0), (100, 0), (100, 10), (0, 10)],
rtol=0, atol=1e-4)
assert poly_oi_proj.shape == (10, 200, 3)
class TestPolygonsOnImage_on(TestPolygonsOnImage_on_):
@property
def _is_inplace(self):
return False
def _func(self, psoi, image):
return psoi.on(image)
class TestPolygonsOnImage_draw_on_image(unittest.TestCase):
def test_with_zero_polygons(self):
# no polygons, nothing changed
image = np.zeros((10, 10, 3), dtype=np.uint8)
poly_oi = ia.PolygonsOnImage([], shape=image.shape)
image_drawn = poly_oi.draw_on_image(image)
assert np.sum(image) == 0
assert np.sum(image_drawn) == 0
def test_with_two_polygons(self):
# draw two polygons
image = np.zeros((10, 10, 3), dtype=np.uint8)
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (9, 1), (9, 9), (1, 9)]),
ia.Polygon([(3, 3), (7, 3), (7, 7), (3, 7)])],
shape=image.shape)
image_expected = np.copy(image)
image_expected = poly_oi.polygons[0].draw_on_image(image_expected)
image_expected = poly_oi.polygons[1].draw_on_image(image_expected)
image_drawn = poly_oi.draw_on_image(image)
assert np.sum(image) == 0
assert np.sum(image_drawn) > 0
assert np.sum(image_expected) > 0
assert np.allclose(image_drawn, image_expected)
class TestPolygonsOnImage_remove_out_of_image_(unittest.TestCase):
@property
def _is_inplace(self):
return False
def _func(self, psoi, fully=True, partly=False):
return psoi.remove_out_of_image_(fully, partly)
def test_with_zero_polygons(self):
# no polygons, nothing to remove
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
for fully, partly in [(False, False), (False, True),
(True, False), (True, True)]:
poly_oi_rm = self._func(poly_oi.deepcopy(),
fully=fully, partly=partly)
assert len(poly_oi_rm.polygons) == 0
assert poly_oi_rm.shape == (10, 11, 3)
def test_one_polygon_fully_inside_image(self):
# one polygon, fully inside the image
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (9, 1), (9, 9), (1, 9)])],
shape=(10, 11, 3))
for fully, partly in [(False, False), (False, True),
(True, False), (True, True)]:
poly_oi_rm = self._func(poly_oi.deepcopy(),
fully=fully, partly=partly)
assert len(poly_oi_rm.polygons) == 1
assert np.allclose(poly_oi_rm.polygons[0].exterior,
[(1, 1), (9, 1), (9, 9), (1, 9)],
rtol=0, atol=1e-4)
assert poly_oi_rm.shape == (10, 11, 3)
def test_one_poly_partially_ooi_one_fully_ooi(self):
# two polygons, one partly outside, one fully outside
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (11, 1), (11, 9), (1, 9)]),
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
shape=(10, 10, 3))
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=False, partly=False)
assert len(poly_oi.polygons) == 2
assert len(poly_oi_rm.polygons) == 2
assert np.allclose(poly_oi_rm.polygons[0].exterior,
[(1, 1), (11, 1), (11, 9), (1, 9)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_rm.polygons[1].exterior,
[(100, 100), (200, 100), (200, 200), (100, 200)],
rtol=0, atol=1e-4)
assert poly_oi_rm.shape == (10, 10, 3)
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=True, partly=False)
assert len(poly_oi.polygons) == 2
assert len(poly_oi_rm.polygons) == 1
assert np.allclose(poly_oi_rm.polygons[0].exterior,
[(1, 1), (11, 1), (11, 9), (1, 9)],
rtol=0, atol=1e-4)
assert poly_oi_rm.shape == (10, 10, 3)
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=False, partly=True)
assert len(poly_oi.polygons) == 2
assert len(poly_oi_rm.polygons) == 1
assert np.allclose(poly_oi_rm.polygons[0].exterior,
[(100, 100), (200, 100), (200, 200), (100, 200)],
rtol=0, atol=1e-4)
assert poly_oi_rm.shape == (10, 10, 3)
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=True, partly=True)
assert len(poly_oi.polygons) == 2
assert len(poly_oi_rm.polygons) == 0
assert poly_oi_rm.shape == (10, 10, 3)
def test_inplaceness(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (11, 1), (11, 9), (1, 9)]),
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
shape=(10, 10, 3))
poly_oi_rm = self._func(poly_oi.deepcopy(), fully=True, partly=False)
if self._is_inplace:
assert poly_oi_rm is poly_oi
else:
assert poly_oi_rm is not poly_oi
class TestPolygonsOnImage_remove_out_if_image(
TestPolygonsOnImage_remove_out_of_image_):
@property
def _is_inplace(self):
return False
def _func(self, psoi, fully=True, partly=False):
return psoi.remove_out_of_image(fully, partly)
class TestPolygonsOnImage_remove_out_of_image_fraction_(unittest.TestCase):
def test_three_polygons(self):
item1 = ia.Polygon([(5, 1), (9, 1), (9, 2), (5, 2)])
item2 = ia.Polygon([(5, 1), (15, 1), (15, 2), (5, 2)])
item3 = ia.Polygon([(15, 1), (25, 1), (25, 2), (15, 2)])
cbaoi = ia.PolygonsOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_reduced = cbaoi.remove_out_of_image_fraction_(0.6)
assert len(cbaoi_reduced.items) == 2
assert cbaoi_reduced.items == [item1, item2]
assert cbaoi_reduced is cbaoi
class TestPolygonsOnImage_remove_out_of_image_fraction(unittest.TestCase):
def test_three_polygons(self):
item1 = ia.Polygon([(5, 1), (9, 1), (9, 2), (5, 2)])
item2 = ia.Polygon([(5, 1), (15, 1), (15, 2), (5, 2)])
item3 = ia.Polygon([(15, 1), (25, 1), (25, 2), (15, 2)])
cbaoi = ia.PolygonsOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_reduced = cbaoi.remove_out_of_image_fraction(0.6)
assert len(cbaoi_reduced.items) == 2
assert cbaoi_reduced.items == [item1, item2]
assert cbaoi_reduced is not cbaoi
class TestPolygonsOnImage_clip_out_of_image_(unittest.TestCase):
# NOTE: clip_out_of_image() can change the order of points,
# hence we check here for each expected point whether it appears
# somewhere in the list of points
@property
def _is_inplace(self):
return True
def _func(self, psoi):
return psoi.clip_out_of_image_()
@classmethod
def _any_point_close(cls, points, point_search):
found = False
for point in points:
if np.allclose(point, point_search, atol=1e-4, rtol=0):
found = True
return found
def test_with_zero_polygons(self):
# no polygons
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
poly_oi_clip = self._func(poly_oi)
assert len(poly_oi_clip.polygons) == 0
assert poly_oi_clip.shape == (10, 11, 3)
def test_with_one_polygon_fully_inside(self):
# one polygon, fully inside
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)])],
shape=(10, 11, 3))
poly_oi_clip = self._func(poly_oi)
assert len(poly_oi_clip.polygons) == 1
for point_search in [(1, 1), (8, 1), (8, 9), (1, 9)]:
assert self._any_point_close(poly_oi_clip.polygons[0].exterior,
point_search)
assert poly_oi_clip.shape == (10, 11, 3)
def test_with_one_polygon_partially_ooi(self):
# one polygon, partially outside
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)])],
shape=(10, 11, 3))
poly_oi_clip = self._func(poly_oi)
assert len(poly_oi_clip.polygons) == 1
for point_search in [(1, 1), (11, 1), (11, 9), (1, 9)]:
assert self._any_point_close(poly_oi_clip.polygons[0].exterior,
point_search)
assert poly_oi_clip.shape == (10, 11, 3)
def test_with_one_polygon_fully_ooi(self):
# one polygon, fully outside
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
shape=(10, 11, 3))
poly_oi_clip = self._func(poly_oi)
assert len(poly_oi_clip.polygons) == 0
assert poly_oi_clip.shape == (10, 11, 3)
def test_with_three_pols_one_not_ooi_one_partially_ooi_one_fully_ooi(self):
# three polygons, one fully inside, one partially outside,
# one fully outside
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)]),
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
shape=(10, 11, 3))
poly_oi_clip = self._func(poly_oi)
assert len(poly_oi_clip.polygons) == 2
for point_search in [(1, 1), (8, 1), (8, 9), (1, 9)]:
assert self._any_point_close(poly_oi_clip.polygons[0].exterior,
point_search)
for point_search in [(1, 1), (11, 1), (11, 9), (1, 9)]:
assert self._any_point_close(poly_oi_clip.polygons[1].exterior,
point_search)
assert poly_oi_clip.shape == (10, 11, 3)
def test_inplaceness(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)])],
shape=(10, 11, 3))
poly_oi_clip = self._func(poly_oi)
if self._is_inplace:
assert poly_oi_clip is poly_oi
else:
assert poly_oi_clip is not poly_oi
class TestPolygonsOnImage_clip_out_of_image(
TestPolygonsOnImage_clip_out_of_image_):
@property
def _is_inplace(self):
return False
def _func(self, psoi):
return psoi.clip_out_of_image()
class TestPolygonsOnImage_shift_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, psoi, *args, **kwargs):
def _func_impl():
return psoi.shift_(*args, **kwargs)
if len(psoi.polygons) == 0:
return _func_impl()
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_with_three_polygons_along_xy(self):
# three polygons
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)]),
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
shape=(10, 11, 3))
poly_oi_shifted = self._func(poly_oi, x=1, y=2)
assert len(poly_oi_shifted.polygons) == 3
assert np.allclose(poly_oi_shifted.polygons[0].exterior,
[(1+1, 1+2), (8+1, 1+2), (8+1, 9+2), (1+1, 9+2)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_shifted.polygons[1].exterior,
[(1+1, 1+2), (15+1, 1+2), (15+1, 9+2), (1+1, 9+2)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_shifted.polygons[2].exterior,
[(100+1, 100+2), (200+1, 100+2),
(200+1, 200+2), (100+1, 200+2)],
rtol=0, atol=1e-4)
assert poly_oi_shifted.shape == (10, 11, 3)
def test_inplaceness(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)]),
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
shape=(10, 11, 3))
poly_oi_shifted = self._func(poly_oi, x=1)
if self._is_inplace:
assert poly_oi_shifted is poly_oi
else:
assert poly_oi_shifted is not poly_oi
class TestPolygonsOnImage_shift(TestPolygonsOnImage_shift_):
@property
def _is_inplace(self):
return False
def _func(self, psoi, *args, **kwargs):
def _func_impl():
return psoi.shift(*args, **kwargs)
return wrap_shift_deprecation(_func_impl, *args, **kwargs)
def test_with_zero_polygons(self):
# no polygons
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
poly_oi_shifted = self._func(poly_oi, top=3, right=0, bottom=1,
left=-3)
assert len(poly_oi_shifted.polygons) == 0
assert poly_oi_shifted.shape == (10, 11, 3)
def test_with_three_polygons(self):
# three polygons
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)]),
ia.Polygon([(100, 100), (200, 100), (200, 200), (100, 200)])],
shape=(10, 11, 3))
poly_oi_shifted = self._func(poly_oi, top=3, right=0, bottom=1,
left=-3)
assert len(poly_oi_shifted.polygons) == 3
assert np.allclose(poly_oi_shifted.polygons[0].exterior,
[(1-3, 1+2), (8-3, 1+2), (8-3, 9+2), (1-3, 9+2)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_shifted.polygons[1].exterior,
[(1-3, 1+2), (15-3, 1+2), (15-3, 9+2), (1-3, 9+2)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_shifted.polygons[2].exterior,
[(100-3, 100+2), (200-3, 100+2),
(200-3, 200+2), (100-3, 200+2)],
rtol=0, atol=1e-4)
assert poly_oi_shifted.shape == (10, 11, 3)
class TestPolygonsOnImage_subdivide_(unittest.TestCase):
@property
def _is_inplace(self):
return True
def _func(self, psoi, points_per_edge):
return psoi.subdivide_(points_per_edge)
def test_mocked(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)])],
shape=(10, 11, 3))
mock_sub = mock.Mock()
mock_sub.return_value = "foo"
poly_oi.items[0].subdivide_ = mock_sub
poly_oi.items[1].subdivide_ = mock_sub
poly_oi_sub = self._func(poly_oi, 2)
assert mock_sub.call_count == 2
assert mock_sub.call_args_list[0][0][0] == 2
assert mock_sub.call_args_list[1][0][0] == 2
assert poly_oi_sub.items == ["foo", "foo"]
def test_with_zero_polygons(self):
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
poly_oi_sub = self._func(poly_oi, 1)
assert len(poly_oi_sub.polygons) == 0
assert poly_oi_sub.shape == (10, 11, 3)
def test_with_one_polygon(self):
poly_oi = ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0)])],
shape=(10, 11, 3))
poly_oi_sub = self._func(poly_oi, 1)
assert poly_oi_sub.shape == (10, 11, 3)
assert len(poly_oi_sub.items) == 1
assert poly_oi_sub.items[0].coords_almost_equals([
(0, 0),
(0.5, 0),
(1, 0),
(0.5, 0)
])
def test_inplaceness(self):
poly_oi = ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0)])],
shape=(10, 11, 3))
poly_oi_sub = self._func(poly_oi, 1)
if self._is_inplace:
assert poly_oi_sub is poly_oi
else:
assert poly_oi_sub is not poly_oi
class TestPolygonsOnImage_subdivide(TestPolygonsOnImage_subdivide_):
@property
def _is_inplace(self):
return False
def _func(self, psoi, points_per_edge):
return psoi.subdivide(points_per_edge)
def test_mocked(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(1, 1), (15, 1), (15, 9), (1, 9)])],
shape=(10, 11, 3))
mock_sub = mock.Mock()
mock_sub.return_value = "foo"
poly_oi.items[0].subdivide_ = mock_sub
poly_oi.items[1].subdivide_ = mock_sub
poly_oi_sub = self._func(poly_oi, 2)
# When the PSOI is copied, each polygon is also copied. That leads
# to new Polygon instances that have no longer the subdivide_
# method overwritten by the mock. Hence, the mock is never actually
# called here.
assert mock_sub.call_count == 0
assert poly_oi_sub.items != ["foo", "foo"]
class TestPolygonsOnImage_to_xy_array(unittest.TestCase):
def test_filled_object(self):
psoi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
shape=(2, 2, 3))
xy_out = psoi.to_xy_array()
expected = np.float32([
[0.0, 0.0],
[1.0, 0.0],
[1.0, 1.0],
[10.0, 10.0],
[20.0, 0.0],
[20.0, 20.0]
])
assert xy_out.shape == (6, 2)
assert np.allclose(xy_out, expected)
assert xy_out.dtype.name == "float32"
def test_empty_object(self):
psoi = ia.PolygonsOnImage(
[],
shape=(1, 2, 3))
xy_out = psoi.to_xy_array()
assert xy_out.shape == (0, 2)
assert xy_out.dtype.name == "float32"
class TestPolygonsOnImage_fill_from_xy_array_(unittest.TestCase):
def test_empty_array(self):
xy = np.zeros((0, 2), dtype=np.float32)
psoi = ia.PolygonsOnImage([], shape=(2, 2, 3))
psoi = psoi.fill_from_xy_array_(xy)
assert len(psoi.polygons) == 0
def test_empty_list(self):
xy = []
psoi = ia.PolygonsOnImage([], shape=(2, 2, 3))
psoi = psoi.fill_from_xy_array_(xy)
assert len(psoi.polygons) == 0
def test_array_with_two_coords(self):
xy = np.array(
[(100, 100),
(101, 100),
(101, 101),
(110, 110),
(120, 100),
(120, 120)], dtype=np.float32)
psoi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
shape=(2, 2, 3))
psoi = psoi.fill_from_xy_array_(xy)
assert len(psoi.polygons) == 2
assert np.allclose(
psoi.polygons[0].coords,
[(100, 100), (101, 100), (101, 101)])
assert np.allclose(
psoi.polygons[1].coords,
[(110, 110), (120, 100), (120, 120)])
def test_list_with_two_coords(self):
xy = [(100, 100),
(101, 100),
(101, 101),
(110, 110),
(120, 100),
(120, 120)]
psoi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
shape=(2, 2, 3))
psoi = psoi.fill_from_xy_array_(xy)
assert len(psoi.polygons) == 2
assert np.allclose(
psoi.polygons[0].coords,
[(100, 100), (101, 100), (101, 101)])
assert np.allclose(
psoi.polygons[1].coords,
[(110, 110), (120, 100), (120, 120)])
class TestPolygonsOnImage_to_keypoints_on_image(unittest.TestCase):
def test_filled_instance(self):
psoi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
shape=(1, 2, 3))
kpsoi = psoi.to_keypoints_on_image()
assert len(kpsoi.keypoints) == 2*3
assert kpsoi.keypoints[0].x == 0
assert kpsoi.keypoints[0].y == 0
assert kpsoi.keypoints[1].x == 1
assert kpsoi.keypoints[1].y == 0
assert kpsoi.keypoints[2].x == 1
assert kpsoi.keypoints[2].y == 1
assert kpsoi.keypoints[3].x == 10
assert kpsoi.keypoints[3].y == 10
assert kpsoi.keypoints[4].x == 20
assert kpsoi.keypoints[4].y == 0
assert kpsoi.keypoints[5].x == 20
assert kpsoi.keypoints[5].y == 20
def test_empty_instance(self):
psoi = ia.PolygonsOnImage([], shape=(1, 2, 3))
kpsoi = psoi.to_keypoints_on_image()
assert len(kpsoi.keypoints) == 0
class TestPolygonsOnImage_invert_to_keypoints_on_image(unittest.TestCase):
def test_filled_instance(self):
psoi = ia.PolygonsOnImage(
[ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(10, 10), (20, 0), (20, 20)])],
shape=(1, 2, 3))
kpsoi = ia.KeypointsOnImage(
[ia.Keypoint(100, 100), ia.Keypoint(101, 100),
ia.Keypoint(101, 101),
ia.Keypoint(110, 110), ia.Keypoint(120, 100),
ia.Keypoint(120, 120)],
shape=(10, 20, 30))
psoi_inv = psoi.invert_to_keypoints_on_image_(kpsoi)
assert len(psoi_inv.polygons) == 2
assert psoi_inv.shape == (10, 20, 30)
assert np.allclose(
psoi.polygons[0].coords,
[(100, 100), (101, 100), (101, 101)])
assert np.allclose(
psoi.polygons[1].coords,
[(110, 110), (120, 100), (120, 120)])
def test_empty_instance(self):
psoi = ia.PolygonsOnImage([], shape=(1, 2, 3))
kpsoi = ia.KeypointsOnImage([], shape=(10, 20, 30))
psoi_inv = psoi.invert_to_keypoints_on_image_(kpsoi)
assert len(psoi_inv.polygons) == 0
assert psoi_inv.shape == (10, 20, 30)
class TestPolygonsOnImage_copy(unittest.TestCase):
def test_with_two_polygons(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(2, 2), (16, 2), (16, 10), (2, 10)])],
shape=(10, 11, 3))
poly_oi_copy = poly_oi.copy()
assert len(poly_oi_copy.polygons) == 2
assert np.allclose(poly_oi_copy.polygons[0].exterior,
[(1, 1), (8, 1), (8, 9), (1, 9)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_copy.polygons[1].exterior,
[(2, 2), (16, 2), (16, 10), (2, 10)],
rtol=0, atol=1e-4)
poly_oi_copy.shape = (20, 30, 3)
assert poly_oi.shape == (10, 11, 3)
assert poly_oi_copy.shape == (20, 30, 3)
# make sure that changing the polygons only affects the copy
poly_oi_copy.polygons = [ia.Polygon([(0, 0), (1, 0), (1, 1)])]
assert np.allclose(poly_oi.polygons[0].exterior,
[(1, 1), (8, 1), (8, 9), (1, 9)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_copy.polygons[0].exterior,
[(0, 0), (1, 0), (1, 1)],
rtol=0, atol=1e-4)
def test_polygons_parameter_set(self):
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly2 = ia.Polygon([(0+1, 0), (1+1, 0), (1+1, 1)])
poly3 = ia.Polygon([(0+2, 0), (1+2, 0), (1+2, 1)])
psoi = ia.PolygonsOnImage([poly1, poly2], shape=(40, 50, 3))
psoi_copy = psoi.copy(polygons=[poly3])
assert psoi_copy is not psoi
assert psoi_copy.shape == (40, 50, 3)
assert psoi_copy.polygons == [poly3]
def test_shape_parameter_set(self):
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly2 = ia.Polygon([(0+1, 0), (1+1, 0), (1+1, 1)])
psoi = ia.PolygonsOnImage([poly1, poly2], shape=(40, 50, 3))
psoi_copy = psoi.copy(shape=(40+1, 50+1, 3))
assert psoi_copy is not psoi
assert psoi_copy.shape == (40+1, 50+1, 3)
assert psoi_copy.polygons == [poly1, poly2]
class TestPolygonsOnImage_deepcopy(unittest.TestCase):
def test_with_two_polygons(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(2, 2), (16, 2), (16, 10), (2, 10)])],
shape=(10, 11, 3))
poly_oi_copy = poly_oi.deepcopy()
assert len(poly_oi_copy.polygons) == 2
assert np.allclose(poly_oi_copy.polygons[0].exterior,
[(1, 1), (8, 1), (8, 9), (1, 9)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_copy.polygons[1].exterior,
[(2, 2), (16, 2), (16, 10), (2, 10)],
rtol=0, atol=1e-4)
poly_oi_copy.shape = (20, 30, 3)
assert poly_oi.shape == (10, 11, 3)
assert poly_oi_copy.shape == (20, 30, 3)
# make sure that changing the polygons only affects the copy
poly_oi_copy.polygons[0] = ia.Polygon([(0, 0), (1, 0), (1, 1)])
assert np.allclose(poly_oi.polygons[0].exterior,
[(1, 1), (8, 1), (8, 9), (1, 9)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_copy.polygons[0].exterior,
[(0, 0), (1, 0), (1, 1)],
rtol=0, atol=1e-4)
# make sure that the arrays were also copied
poly_oi_copy.polygons[1].exterior[0][0] = 100
assert np.allclose(poly_oi.polygons[1].exterior,
[(2, 2), (16, 2), (16, 10), (2, 10)],
rtol=0, atol=1e-4)
assert np.allclose(poly_oi_copy.polygons[1].exterior,
[(100, 2), (16, 2), (16, 10), (2, 10)],
rtol=0, atol=1e-4)
def test_polygons_parameter_set(self):
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly2 = ia.Polygon([(0+1, 0), (1+1, 0), (1+1, 1)])
poly3 = ia.Polygon([(0+2, 0), (1+2, 0), (1+2, 1)])
psoi = ia.PolygonsOnImage([poly1, poly2], shape=(40, 50, 3))
psoi_copy = psoi.deepcopy(polygons=[poly3])
assert psoi_copy is not psoi
assert psoi_copy.shape == (40, 50, 3)
assert len(psoi_copy.polygons) == 1
assert psoi_copy.polygons[0].coords_almost_equals(poly3)
def test_shape_parameter_set(self):
poly1 = ia.Polygon([(0, 0), (1, 0), (1, 1)])
poly2 = ia.Polygon([(0+1, 0), (1+1, 0), (1+1, 1)])
psoi = ia.PolygonsOnImage([poly1, poly2], shape=(40, 50, 3))
psoi_copy = psoi.deepcopy(shape=(40+1, 50+1, 3))
assert psoi_copy is not psoi
assert psoi_copy.shape == (40+1, 50+1, 3)
assert len(psoi_copy.polygons) == 2
assert psoi_copy.polygons[0].coords_almost_equals(poly1)
assert psoi_copy.polygons[1].coords_almost_equals(poly2)
class TestPolygonsOnImage___getitem__(unittest.TestCase):
def test_with_two_polygons(self):
cbas = [
ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(1, 1), (2, 1), (2, 2)])
]
cbasoi = ia.PolygonsOnImage(cbas, shape=(3, 4, 3))
assert cbasoi[0] is cbas[0]
assert cbasoi[1] is cbas[1]
assert cbasoi[0:2] == cbas
class TestPolygonsOnImage___iter__(unittest.TestCase):
def test_with_two_polygons(self):
cbas = [ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(1, 0), (2, 2), (1.5, 3)])]
cbasoi = ia.PolygonsOnImage(cbas, shape=(40, 50, 3))
for i, cba in enumerate(cbasoi):
assert cba is cbas[i]
def test_with_zero_polygons(self):
cbasoi = ia.PolygonsOnImage([], shape=(40, 50, 3))
i = 0
for _cba in cbasoi:
i += 1
assert i == 0
class TestPolygonsOnImage___len__(unittest.TestCase):
def test_with_two_polygons(self):
cbas = [ia.Polygon([(0, 0), (1, 0), (1, 1)]),
ia.Polygon([(1, 0), (2, 2), (1.5, 3)])]
cbasoi = ia.PolygonsOnImage(cbas, shape=(40, 50, 3))
assert len(cbasoi) == 2
class TestPolygonsOnImage___repr___and___str__(unittest.TestCase):
def test_with_zero_polygons(self):
poly_oi = ia.PolygonsOnImage([], shape=(10, 11, 3))
expected = "PolygonsOnImage([], shape=(10, 11, 3))"
assert poly_oi.__repr__() == expected
assert poly_oi.__str__() == expected
def test_with_two_polygons(self):
poly_oi = ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (8, 1), (8, 9), (1, 9)]),
ia.Polygon([(2, 2), (16, 2), (16, 10), (2, 10)])],
shape=(10, 11, 3))
expected = (
"PolygonsOnImage(["
"Polygon([(x=1.000, y=1.000), (x=8.000, y=1.000), "
"(x=8.000, y=9.000), (x=1.000, y=9.000)] "
"(4 points), label=None), "
"Polygon([(x=2.000, y=2.000), (x=16.000, y=2.000), "
"(x=16.000, y=10.000), (x=2.000, y=10.000)] "
"(4 points), label=None)"
"], shape=(10, 11, 3))"
)
assert poly_oi.__repr__() == expected
assert poly_oi.__str__() == expected
class Test_ConcavePolygonRecoverer(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _assert_points_are_identical(cls, observed, expected, atol=1e-8,
rtol=0):
assert len(observed) == len(expected)
for i, (ps_obs, ps_exp) in enumerate(zip(observed, expected)):
assert len(ps_obs) == len(ps_exp), "Failed at point %d" % (i,)
for p_obs, p_exp in zip(ps_obs, ps_exp):
assert len(p_obs) == 2
assert len(p_exp) == 2
assert np.allclose(p_obs, p_exp, atol=atol, rtol=rtol), (
"Unexpected coords at %d" % (i,))
# TODO split into multiple tests
def test_recover_from_fails_for_less_than_three_points(self):
old_polygon = ia.Polygon([(0, 0), (1, 0), (1, 1), (0, 1)])
cpr = _ConcavePolygonRecoverer()
with self.assertRaises(AssertionError):
_poly = cpr.recover_from([], old_polygon)
with self.assertRaises(AssertionError):
_poly = cpr.recover_from([(0, 0)], old_polygon)
with self.assertRaises(AssertionError):
_poly = cpr.recover_from([(0, 0), (1, 0)], old_polygon)
_poly = cpr.recover_from([(0, 0), (1, 0), (1, 1)], old_polygon)
def test_recover_from_concave_polygons(self):
cpr = _ConcavePolygonRecoverer()
polys = [
[(0, 0), (1, 0), (1, 1)],
[(0, 0), (1, 0), (1, 1), (0, 1)],
[(0, 0), (0.5, 0), (1, 0), (1, 0.5), (1, 1), (0.5, 1.0), (0, 1)],
]
for poly in polys:
with self.subTest(poly=str(poly)):
old_polygon = ia.Polygon(poly)
poly_concave = cpr.recover_from(poly, old_polygon)
assert poly_concave.is_valid
found = [False] * len(poly)
for i, point in enumerate(poly):
for point_ext in poly_concave.exterior:
dist = np.sqrt((point[0] - point_ext[0])**2
+ (point[1] - point_ext[1])**2)
if dist < 0.01:
found[i] = True
assert np.all(found)
def test_recover_from_line(self):
cpr = _ConcavePolygonRecoverer()
poly = [(0, 0), (1, 0), (2, 0)]
old_polygon = ia.Polygon(poly)
poly_concave = cpr.recover_from(poly, old_polygon)
assert poly_concave.is_valid
found = [False] * len(poly)
for i, point in enumerate(poly):
for point_ext in poly_concave.exterior:
dist = np.sqrt((point[0] - point_ext[0])**2
+ (point[1] - point_ext[1])**2)
if dist < 0.025:
found[i] = True
assert np.all(found)
def test_recover_from_polygon_with_duplicate_points(self):
cpr = _ConcavePolygonRecoverer()
poly = [(0, 0), (1, 0), (1, 0), (1, 1)]
old_polygon = ia.Polygon(poly)
poly_concave = cpr.recover_from(poly, old_polygon)
assert poly_concave.is_valid
found = [False] * len(poly)
for i, point in enumerate(poly):
for point_ext in poly_concave.exterior:
dist = np.sqrt((point[0] - point_ext[0])**2
+ (point[1] - point_ext[1])**2)
if dist < 0.01:
found[i] = True
assert np.all(found)
def test_recover_from_invalid_polygon(self):
cpr = _ConcavePolygonRecoverer()
poly = [(0, 0), (0.5, 0), (0.5, 1.2), (1, 0), (1, 1), (0, 1)]
old_polygon = ia.Polygon(poly)
poly_concave = cpr.recover_from(poly, old_polygon)
assert poly_concave.is_valid
found = [False] * len(poly)
for i, point in enumerate(poly):
for point_ext in poly_concave.exterior:
dist = np.sqrt(
(point[0] - point_ext[0])**2
+ (point[1] - point_ext[1])**2
)
if dist < 0.025:
found[i] = True
assert np.all(found)
def test_recover_from_random_polygons(self):
cpr = _ConcavePolygonRecoverer()
nb_iterations = 10
height, width = 10, 20
nb_points_matrix = np.random.randint(3, 30, size=(nb_iterations,))
for nb_points in nb_points_matrix:
points = np.random.random(size=(nb_points, 2))
points[:, 0] *= width
points[:, 1] *= height
# currently mainly used to copy the label, so not a significant
# issue that it is not concave
old_polygon = ia.Polygon(points)
poly_concave = cpr.recover_from(points, old_polygon)
assert poly_concave.is_valid
# test if all points are in BB around returned polygon
# would be better to directly call a polygon.contains(point) method
# but that does not yet exist
xx = poly_concave.exterior[:, 0]
yy = poly_concave.exterior[:, 1]
bb_x1, bb_x2 = min(xx), max(xx)
bb_y1, bb_y2 = min(yy), max(yy)
bb = ia.BoundingBox(
x1=bb_x1-1e-4, y1=bb_y1-1e-4,
x2=bb_x2+1e-4, y2=bb_y2+1e-4)
for point in points:
assert bb.contains(ia.Keypoint(x=point[0], y=point[1]))
def test__remove_consecutive_duplicate_points(self):
recoverer = _ConcavePolygonRecoverer()
points = [
[(0, 0), (1, 1)],
[(0.0, 0.5), (1.0, 1.0)],
np.float32([(0.0, 0.5), (1.0, 1.0)]),
[(0, 0), (0, 0)],
[(0, 0), (0, 0), (1, 0)],
[(0, 0), (1, 0), (1, 0)],
[(0, 0), (1, 0), (1, 0), (2, 0), (0, 0)]
]
expected = [
[(0, 0), (1, 1)],
[(0.0, 0.5), (1.0, 1.0)],
[(0.0, 0.5), (1.0, 1.0)],
[(0, 0)],
[(0, 0), (1, 0)],
[(0, 0), (1, 0)],
[(0, 0), (1, 0), (2, 0)]
]
for points_i, expected_i in zip(points, expected):
with self.subTest(points=points_i):
points_deduplicated = \
recoverer._remove_consecutive_duplicate_points(points_i)
assert np.allclose(points_deduplicated, expected_i)
# TODO split into multiple tests
def test__jitter_duplicate_points(self):
def _norm(a, b):
return np.linalg.norm(np.float32(a) - np.float32(b))
cpr = _ConcavePolygonRecoverer(threshold_duplicate_points=1e-4)
rng = iarandom.RNG(0)
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
assert np.allclose(points, points_jittered, rtol=0, atol=1e-4)
points = [(0, 0), (1, 0), (0, 1)]
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
assert np.allclose(points, points_jittered, rtol=0, atol=1e-4)
points = [(0, 0), (0.01, 0), (0.01, 0.01), (0, 0.01)]
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
assert np.allclose(points, points_jittered, rtol=0, atol=1e-4)
points = [(0, 0), (1, 0), (1 + 1e-6, 0), (1, 1), (0, 1)]
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
assert np.allclose(
[point
for i, point
in enumerate(points_jittered)
if i in [0, 1, 3, 4]],
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0,
atol=1e-5
)
assert _norm([1, 0], points_jittered[2]) >= 1e-4
points = [(0, 0), (1, 0), (1, 1), (1 + 1e-6, 0), (0, 1)]
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
assert np.allclose(
[point
for i, point
in enumerate(points_jittered)
if i in [0, 1, 2, 4]],
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0,
atol=1e-5
)
assert _norm([1, 0], points_jittered[3]) >= 1e-4
points = [(0, 0), (1, 0), (1, 1), (0, 1), (1 + 1e-6, 0)]
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
assert np.allclose(
[point
for i, point
in enumerate(points_jittered)
if i in [0, 1, 2, 3]],
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0,
atol=1e-5
)
assert _norm([1, 0], points_jittered[4]) >= 1e-4
points = [(0, 0), (1, 0), (1 + 1e-6, 0), (1, 1), (1 + 1e-6, 0), (0, 1),
(1 + 1e-6, 0), (1 + 1e-6, 0 + 1e-6), (1 + 1e-6, 0 + 2e-6)]
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
assert np.allclose(
[point
for i, point
in enumerate(points_jittered)
if i in [0, 1, 3, 5]],
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0,
atol=1e-5
)
assert _norm([1, 0], points_jittered[2]) >= 1e-4
assert _norm([1, 0], points_jittered[4]) >= 1e-4
assert _norm([1, 0], points_jittered[6]) >= 1e-4
assert _norm([1, 0], points_jittered[7]) >= 1e-4
assert _norm([1, 0], points_jittered[8]) >= 1e-4
points = [(0, 0), (1, 0), (0 + 1e-6, 0 - 1e-6), (1 + 1e-6, 0), (1, 1),
(1 + 1e-6, 0), (0, 1), (1 + 1e-6, 0), (1 + 1e-6, 0 + 1e-6),
(1 + 1e-6, 0 + 2e-6)]
points_jittered = cpr._jitter_duplicate_points(points, rng.copy())
assert np.allclose(
[point
for i, point
in enumerate(points_jittered)
if i in [0, 1, 4, 6]],
[(0, 0), (1, 0), (1, 1), (0, 1)],
rtol=0,
atol=1e-5
)
assert _norm([0, 0], points_jittered[2]) >= 1e-4
assert _norm([1, 0], points_jittered[3]) >= 1e-4
assert _norm([1, 0], points_jittered[5]) >= 1e-4
assert _norm([1, 0], points_jittered[7]) >= 1e-4
assert _norm([1, 0], points_jittered[8]) >= 1e-4
assert _norm([1, 0], points_jittered[9]) >= 1e-4
# TODO split into multiple tests
def test__calculate_circumference(self):
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
circ = _ConcavePolygonRecoverer._calculate_circumference(points)
assert np.allclose(circ, 4)
points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
circ = _ConcavePolygonRecoverer._calculate_circumference(points)
assert np.allclose(circ, 4)
points = np.float32([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)])
circ = _ConcavePolygonRecoverer._calculate_circumference(points)
assert np.allclose(circ, 4)
points = [(0, 0), (1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0)]
circ = _ConcavePolygonRecoverer._calculate_circumference(points)
assert np.allclose(circ, 6)
# TODO split into multiple tests
def test__fit_best_valid_polygon(self):
def _assert_ids_match(observed, expected):
assert len(observed) == len(expected), (
"len mismatch: %d vs %d" % (len(observed), len(expected)))
max_count = 0
for i in range(len(observed)):
counter = 0
for j in range(i, i+len(expected)):
observed_item = observed[(i+j) % len(observed)]
expected_item = expected[j % len(expected)]
if observed_item == expected_item:
counter += 1
else:
break
max_count = max(max_count, counter)
assert max_count == len(expected), (
"count mismatch: %d vs %d" % (max_count, len(expected)))
cpr = _ConcavePolygonRecoverer()
rng = iarandom.RNG(0)
points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
points_fit = cpr._fit_best_valid_polygon(
points, random_state=rng.copy())
# doing this without the list(.) wrappers fails on python2.7
assert list(points_fit) == list(sm.xrange(len(points)))
# square-like, but top line has one point in its center which's
# y-coordinate is below the bottom line
points = [(0.0, 0.0), (0.45, 0.0), (0.5, 1.5), (0.55, 0.0), (1.0, 0.0),
(1.0, 1.0), (0.0, 1.0)]
points_fit = cpr._fit_best_valid_polygon(
points, random_state=rng.copy())
_assert_ids_match(points_fit, [0, 1, 3, 4, 5, 2, 6])
assert ia.Polygon([points[idx] for idx in points_fit]).is_valid
# |--| |--|
# | | | |
# | | | |
# |--|--|--|
# | |
# ----
# the intersection points on the bottom line are not provided,
# hence the result is expected to have triangles at the bottom left
# and right
points = [(0.0, 0), (0.25, 0), (0.25, 1.25),
(0.75, 1.25), (0.75, 0), (1.0, 0),
(1.0, 1.0), (0.0, 1.0)]
points_fit = cpr._fit_best_valid_polygon(
points, random_state=rng.copy())
_assert_ids_match(points_fit, [0, 1, 4, 5, 6, 3, 2, 7])
poly_observed = ia.Polygon([points[idx] for idx in points_fit])
assert poly_observed.is_valid
# same as above, but intersection points at the bottom line are
# provided without oversampling, i.e. incorporating these points
# would lead to an invalid polygon
points = [(0.0, 0), (0.25, 0), (0.25, 1.0), (0.25, 1.25),
(0.75, 1.25), (0.75, 1.0), (0.75, 0), (1.0, 0),
(1.0, 1.0), (0.0, 1.0)]
points_fit = cpr._fit_best_valid_polygon(
points, random_state=rng.copy())
assert len(points_fit) >= len(points) - 2 # TODO add IoU check here
poly_observed = ia.Polygon([points[idx] for idx in points_fit])
assert poly_observed.is_valid
# TODO split into multiple tests
def test__fix_polygon_is_line(self):
cpr = _ConcavePolygonRecoverer()
rng = iarandom.RNG(0)
points = [(0, 0), (1, 0), (1, 1)]
points_fixed = cpr._fix_polygon_is_line(points, rng.copy())
assert np.allclose(points_fixed, points, atol=0, rtol=0)
points = [(0, 0), (1, 0), (2, 0)]
points_fixed = cpr._fix_polygon_is_line(points, rng.copy())
assert not np.allclose(points_fixed, points, atol=0, rtol=0)
assert not cpr._is_polygon_line(points_fixed)
assert np.allclose(points_fixed, points, rtol=0, atol=1e-2)
points = [(0, 0), (0, 1), (0, 2)]
points_fixed = cpr._fix_polygon_is_line(points, rng.copy())
assert not np.allclose(points_fixed, points, atol=0, rtol=0)
assert not cpr._is_polygon_line(points_fixed)
assert np.allclose(points_fixed, points, rtol=0, atol=1e-2)
points = [(0, 0), (1, 1), (2, 2)]
points_fixed = cpr._fix_polygon_is_line(points, rng.copy())
assert not np.allclose(points_fixed, points, atol=0, rtol=0)
assert not cpr._is_polygon_line(points_fixed)
assert np.allclose(points_fixed, points, rtol=0, atol=1e-2)
# TODO split into multiple tests
def test__is_polygon_line(self):
points = [(0, 0), (1, 0), (1, 1)]
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
points = np.float32([(0, 0), (1, 0), (1, 1), (0, 1)])
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
points = [(0, 0), (1, 0)]
assert _ConcavePolygonRecoverer._is_polygon_line(points)
points = [(0, 0), (1, 0), (2, 0)]
assert _ConcavePolygonRecoverer._is_polygon_line(points)
points = [(0, 0), (1, 0), (1, 0)]
assert _ConcavePolygonRecoverer._is_polygon_line(points)
points = [(0, 0), (1, 0), (1, 0), (2, 0)]
assert _ConcavePolygonRecoverer._is_polygon_line(points)
points = [(0, 0), (1, 0), (1, 0), (2, 0), (0.5, 0)]
assert _ConcavePolygonRecoverer._is_polygon_line(points)
points = [(0, 0), (1, 0), (1, 0), (2, 0), (1, 1)]
assert not _ConcavePolygonRecoverer._is_polygon_line(points)
# TODO split into multiple tests
def test__generate_intersection_points(self):
cpr = _ConcavePolygonRecoverer()
# triangle
points = [(0.5, 0), (1, 1), (0, 1)]
points_inter = cpr._generate_intersection_points(
points, one_point_per_intersection=False)
assert points_inter == [[], [], []]
# rotated square
points = [(0.5, 0), (1, 0.5), (0.5, 1), (0, 0.5)]
points_inter = cpr._generate_intersection_points(
points, one_point_per_intersection=False)
assert points_inter == [[], [], [], []]
# square
points = [(0, 0), (1, 0), (1, 1), (0, 1)]
points_inter = cpr._generate_intersection_points(
points, one_point_per_intersection=False)
assert points_inter == [[], [], [], []]
# |--| |--|
# | |__| |
# | |
# |--------|
points = [(0.0, 0), (0.25, 0), (0.25, 0.25),
(0.75, 0.25), (0.75, 0), (1.0, 0),
(1.0, 1.0), (0.0, 1.0)]
points_inter = cpr._generate_intersection_points(
points, one_point_per_intersection=False)
assert points_inter == [[], [], [], [], [], [], [], []]
# same as above, but middle part goes much further down,
# crossing the bottom line
points = [(0.0, 0), (0.25, 0), (0.25, 1.25),
(0.75, 1.25), (0.75, 0), (1.0, 0),
(1.0, 1.0), (0.0, 1.0)]
points_inter = cpr._generate_intersection_points(
points, one_point_per_intersection=False)
self._assert_points_are_identical(
points_inter,
[[], [(0.25, 1.0)], [], [(0.75, 1.0)], [], [],
[(0.75, 1.0), (0.25, 1.0)], []])
# square-like structure with intersections in top right area
points = [(0, 0), (0.5, 0), (1.01, 0.5), (1.0, 0), (1, 1), (0, 1),
(0, 0)]
points_inter = cpr._generate_intersection_points(
points, one_point_per_intersection=False)
self._assert_points_are_identical(
points_inter,
[[], [(1.0, 0.4902)], [], [(1.0, 0.4902)], [], [], []],
atol=1e-2)
# same as above, but with a second intersection in bottom left
points = [(0, 0), (0.5, 0), (1.01, 0.5), (1.0, 0), (1, 1), (-0.25, 1),
(0, 1.25)]
points_inter = cpr._generate_intersection_points(
points, one_point_per_intersection=False)
self._assert_points_are_identical(
points_inter,
[[], [(1.0, 0.4902)], [], [(1.0, 0.4902)], [(0, 1.0)], [],
[(0, 1.0)]],
atol=1e-2)
# double triangle with point in center that is shared by both triangles
points = [(0, 0), (0.5, 0.5), (1.0, 0), (1.0, 1.0), (0.5, 0.5),
(0, 1.0)]
points_inter = cpr._generate_intersection_points(
points, one_point_per_intersection=False)
self._assert_points_are_identical(
points_inter,
[[], [], [], [], [], []])
# TODO split into multiple tests
def test__oversample_intersection_points(self):
cpr = _ConcavePolygonRecoverer()
cpr.oversampling = 0.1
points = [(0.0, 0.0), (1.0, 0.0)]
segment_add_points_sorted = [[(0.5, 0.0)], []]
points_oversampled = cpr._oversample_intersection_points(
points, segment_add_points_sorted)
self._assert_points_are_identical(
points_oversampled,
[[(0.45, 0.0), (0.5, 0.0), (0.55, 0.0)], []],
atol=1e-4
)
points = [(0.0, 0.0), (2.0, 0.0)]
segment_add_points_sorted = [[(0.5, 0.0)], []]
points_oversampled = cpr._oversample_intersection_points(
points, segment_add_points_sorted)
self._assert_points_are_identical(
points_oversampled,
[[(0.45, 0.0), (0.5, 0.0), (0.65, 0.0)], []],
atol=1e-4
)
points = [(0.0, 0.0), (1.0, 0.0)]
segment_add_points_sorted = [[(0.5, 0.0), (0.6, 0.0)], []]
points_oversampled = cpr._oversample_intersection_points(
points, segment_add_points_sorted)
self._assert_points_are_identical(
points_oversampled,
[[(0.45, 0.0), (0.5, 0.0), (0.51, 0.0), (0.59, 0.0), (0.6, 0.0),
(0.64, 0.0)], []],
atol=1e-4
)
points = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)]
segment_add_points_sorted = [[(0.5, 0.0)], [], [(0.8, 1.0)],
[(0.0, 0.7)]]
points_oversampled = cpr._oversample_intersection_points(
points, segment_add_points_sorted)
self._assert_points_are_identical(
points_oversampled,
[[(0.45, 0.0), (0.5, 0.0), (0.55, 0.0)],
[],
[(0.82, 1.0), (0.8, 1.0), (0.72, 1.0)],
[(0.0, 0.73), (0.0, 0.7), (0.0, 0.63)]],
atol=1e-4
)
# TODO split into multiple tests
def test__insert_intersection_points(self):
points = [(0, 0), (1, 0), (2, 0)]
segments_add_point_sorted = [[], [], []]
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
points, segments_add_point_sorted)
assert points_inserted == points
segments_add_point_sorted = [[(0.5, 0)], [], []]
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
points, segments_add_point_sorted)
assert points_inserted == [(0, 0), (0.5, 0), (1, 0), (2, 0)]
segments_add_point_sorted = [[(0.5, 0), (0.75, 0)], [], []]
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
points, segments_add_point_sorted)
assert points_inserted == [(0, 0), (0.5, 0), (0.75, 0), (1, 0), (2, 0)]
segments_add_point_sorted = [[(0.5, 0)], [(1.5, 0)], []]
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
points, segments_add_point_sorted)
assert points_inserted == [(0, 0), (0.5, 0), (1, 0), (1.5, 0), (2, 0)]
segments_add_point_sorted = [[(0.5, 0)], [(1.5, 0)], [(2.5, 0)]]
points_inserted = _ConcavePolygonRecoverer._insert_intersection_points(
points, segments_add_point_sorted)
assert points_inserted == [(0, 0), (0.5, 0), (1, 0), (1.5, 0), (2, 0),
(2.5, 0)]
================================================
FILE: test/augmentables/test_segmaps.py
================================================
from __future__ import print_function, division, absolute_import
import itertools
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import imgaug as ia
import imgaug.augmentables.segmaps as segmapslib
# old style segmentation maps (class name differs to new style by "Map"
# instead of "Maps")
class TestSegmentationMapOnImage(unittest.TestCase):
def test_warns_that_it_is_deprecated(self):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
segmap = segmapslib.SegmentationMapOnImage(
np.zeros((1, 1, 1), dtype=np.int32),
shape=(1, 1, 3)
)
assert segmap.arr.dtype.name == "int32"
assert segmap.arr.shape == (1, 1, 1)
assert segmap.shape == (1, 1, 3)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[0].message)
class TestSegmentationMapsOnImage___init__(unittest.TestCase):
def test_uint_int_arrs(self):
dtypes = ["int8", "int16", "int32", "uint8", "uint16"]
ndims = [2, 3]
img_shapes = [(3, 3), (3, 3, 3), (4, 5, 3)]
gen = itertools.product(dtypes, ndims, img_shapes)
for dtype, ndim, img_shape in gen:
with self.subTest(dtype=dtype, ndim=ndim, shape=img_shape):
dtype = np.dtype(dtype)
shape = (3, 3) if ndim == 2 else (3, 3, 1)
arr = np.array([
[0, 0, 1],
[0, 2, 1],
[1, 3, 1]
], dtype=dtype).reshape(shape)
segmap = ia.SegmentationMapsOnImage(arr, shape=img_shape)
assert segmap.shape == img_shape
assert segmap.arr.dtype.name == "int32"
assert segmap.arr.shape == (3, 3, 1)
assert np.array_equal(segmap.arr,
arr.reshape((3, 3, 1)).astype(np.int32))
if ndim == 3:
arr = np.array([
[0, 0, 1],
[0, 2, 1],
[1, 3, 1]
], dtype=dtype).reshape((3, 3, 1))
arr = np.tile(arr, (1, 1, 5))
segmap = ia.SegmentationMapsOnImage(arr, shape=img_shape)
assert segmap.shape == img_shape
assert segmap.arr.dtype.name == "int32"
assert segmap.arr.shape == (3, 3, 5)
assert np.array_equal(segmap.arr, arr.astype(np.int32))
def test_bool_arr_2d(self):
arr = np.array([
[0, 0, 1],
[0, 1, 1],
[1, 1, 1]
], dtype=bool).reshape((3, 3))
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
assert segmap.shape == (3, 3)
assert segmap.arr.dtype.name == "int32"
assert segmap.arr.shape == (3, 3, 1)
assert np.array_equal(segmap.arr,
arr.reshape((3, 3, 1)).astype(np.int32))
def test_bool_arr_3d(self):
arr = np.array([
[0, 0, 1],
[0, 1, 1],
[1, 1, 1]
], dtype=bool).reshape((3, 3, 1))
arr = np.tile(arr, (1, 1, 5))
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
assert segmap.shape == (3, 3)
assert segmap.arr.dtype.name == "int32"
assert segmap.arr.shape == (3, 3, 5)
assert np.array_equal(segmap.arr, arr.astype(np.int32))
# is this different from the test_bool_* tests?
def test_boolean_masks(self):
# Test for #189 (boolean mask inputs into SegmentationMapsOnImage not
# working)
for dt in [bool, np.bool]:
arr = np.array([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
], dtype=dt)
assert arr.dtype.kind == "b"
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
assert np.array_equal(
segmap.arr,
np.int32([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
])[:, :, np.newaxis]
)
assert segmap.get_arr().dtype.name == arr.dtype.name
assert np.array_equal(segmap.get_arr(), arr)
def test_uint32_fails(self):
got_exception = False
try:
arr = np.array([
[0, 0, 1],
[0, 2, 1],
[1, 3, 1]
], dtype=np.uint32)
_segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3, 3))
except Exception as exc:
assert "only uint8 and uint16 " in str(exc)
got_exception = True
assert got_exception
def test_uint64_fails(self):
got_exception = False
try:
arr = np.array([
[0, 0, 1],
[0, 2, 1],
[1, 3, 1]
], dtype=np.int64)
_segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3, 3))
except Exception as exc:
assert "only int8, int16 and int32 " in str(exc)
got_exception = True
assert got_exception
def test_legacy_support_for_float32_2d(self):
arr = np.array([0.4, 0.6], dtype=np.float32).reshape((1, 2))
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
segmap = segmapslib.SegmentationMapsOnImage(arr, shape=(1, 1, 3))
arr_expected = np.array([0, 1], dtype=np.int32).reshape((1, 2, 1))
assert np.array_equal(segmap.arr, arr_expected)
assert segmap.shape == (1, 1, 3)
assert len(caught_warnings) == 1
assert (
"Got a float array as the segmentation map in"
in str(caught_warnings[0].message)
)
def test_legacy_support_for_float32_3d(self):
arr = np.array([
[
[0.4, 0.6],
[0.2, 0.1]
]
], dtype=np.float32).reshape((1, 2, 2))
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
segmap = segmapslib.SegmentationMapsOnImage(arr, shape=(1, 1, 3))
arr_expected = np.array([
[1, 0]
], dtype=np.int32).reshape((1, 2, 1))
assert np.array_equal(segmap.arr, arr_expected)
assert segmap.shape == (1, 1, 3)
assert len(caught_warnings) == 1
assert (
"Got a float array as the segmentation map in"
in str(caught_warnings[0].message)
)
class TestSegmentationMapsOnImage_get_arr(unittest.TestCase):
def test_uint_int(self):
dtypes = ["int8", "int16", "int32", "uint8", "uint16"]
ndims = [2, 3]
for dtype, ndim in itertools.product(dtypes, ndims):
with self.subTest(dtype=dtype, ndim=ndim):
dtype = np.dtype(dtype)
shape = (3, 3) if ndim == 2 else (3, 3, 1)
arr = np.array([
[0, 0, 1],
[0, 2, 1],
[1, 3, 1]
], dtype=dtype).reshape(shape)
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
observed = segmap.get_arr()
assert segmap.arr.dtype.name == "int32"
assert segmap.arr.ndim == 3
assert np.array_equal(observed, arr)
assert observed.dtype.name == dtype.name
assert observed.ndim == ndim
assert np.array_equal(observed, arr)
def test_bool(self):
ndims = [2, 3]
for ndim in ndims:
with self.subTest(ndim=ndim):
shape = (3, 3) if ndim == 2 else (3, 3, 1)
arr = np.array([
[0, 0, 1],
[0, 1, 1],
[1, 1, 1]
], dtype=bool).reshape(shape)
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
observed = segmap.get_arr()
assert segmap.arr.dtype.name == "int32"
assert segmap.arr.ndim == 3
assert np.array_equal(observed, arr)
assert observed.dtype.kind == "b"
assert observed.ndim == ndim
assert np.array_equal(observed, arr)
class TestSegmentationMapsOnImage_draw(unittest.TestCase):
@property
def segmap(self):
arr = np.int32([
[0, 1, 1],
[0, 1, 1],
[0, 1, 1]
])
return ia.SegmentationMapsOnImage(arr, shape=(3, 3))
@classmethod
def col(cls, idx):
return ia.SegmentationMapsOnImage.DEFAULT_SEGMENT_COLORS[idx]
def test_with_two_classes(self):
# simple example with 2 classes
col0 = self.col(0)
col1 = self.col(1)
expected = np.uint8([
[col0, col1, col1],
[col0, col1, col1],
[col0, col1, col1]
])
observed = self.segmap.draw()
assert isinstance(observed, list)
assert len(observed) == 1
assert np.array_equal(observed[0], expected)
def test_use_size_arg_to_resize_to_2x(self):
# same example, with resizing to 2x the size
double_size_args = [
(6, 6),
(2.0, 2.0),
6,
2.0
]
col0 = self.col(0)
col1 = self.col(1)
expected = np.uint8([
[col0, col1, col1],
[col0, col1, col1],
[col0, col1, col1]
])
expected = ia.imresize_single_image(expected,
(6, 6),
interpolation="nearest")
for double_size_arg in double_size_args:
with self.subTest(size=double_size_arg):
observed = self.segmap.draw(size=double_size_arg)
assert isinstance(observed, list)
assert len(observed) == 1
assert np.array_equal(observed[0], expected)
def test_use_size_arg_to_keep_at_same_size(self):
# same example, keeps size at 3x3 via None and (int)3 or (float)1.0
size_args = [
None,
(None, None),
(3, None),
(None, 3),
(1.0, None),
(None, 1.0)
]
col0 = self.col(0)
col1 = self.col(1)
expected = np.uint8([
[col0, col1, col1],
[col0, col1, col1],
[col0, col1, col1]
])
expected = ia.imresize_single_image(expected,
(3, 3),
interpolation="nearest")
for size_arg in size_args:
with self.subTest(size=size_arg):
observed = self.segmap.draw(size=size_arg)
assert isinstance(observed, list)
assert len(observed) == 1
assert np.array_equal(observed[0], expected)
def test_colors(self):
# custom choice of colors
col0 = (10, 10, 10)
col1 = (50, 51, 52)
expected = np.uint8([
[col0, col1, col1],
[col0, col1, col1],
[col0, col1, col1]
])
observed = self.segmap.draw(colors=[col0, col1])
assert isinstance(observed, list)
assert len(observed) == 1
assert np.array_equal(observed[0], expected)
def test_segmap_with_more_than_one_channel(self):
# test segmentation maps with multiple channels
arr_channel_1 = np.int32([
[0, 1, 5],
[0, 1, 1],
[0, 4, 1]
])
arr_channel_2 = np.int32([
[1, 1, 0],
[2, 2, 0],
[1, 1, 0]
])
arr_channel_3 = np.int32([
[1, 0, 0],
[0, 1, 0],
[0, 0, 3]
])
arr_multi = np.stack(
[arr_channel_1, arr_channel_2, arr_channel_3],
axis=-1)
col = ia.SegmentationMapsOnImage.DEFAULT_SEGMENT_COLORS
expected_channel_1 = np.uint8([
[col[0], col[1], col[5]],
[col[0], col[1], col[1]],
[col[0], col[4], col[1]]
])
expected_channel_2 = np.uint8([
[col[1], col[1], col[0]],
[col[2], col[2], col[0]],
[col[1], col[1], col[0]]
])
expected_channel_3 = np.uint8([
[col[1], col[0], col[0]],
[col[0], col[1], col[0]],
[col[0], col[0], col[3]]
])
segmap = ia.SegmentationMapsOnImage(arr_multi, shape=(3, 3, 3))
observed = segmap.draw()
assert isinstance(observed, list)
assert len(observed) == 3
assert np.array_equal(observed[0], expected_channel_1)
assert np.array_equal(observed[1], expected_channel_2)
assert np.array_equal(observed[2], expected_channel_3)
class TestSegmentationMapsOnImage_draw_on_image(unittest.TestCase):
@property
def segmap(self):
arr = np.int32([
[0, 1, 1],
[0, 1, 1],
[0, 1, 1]
])
return ia.SegmentationMapsOnImage(arr, shape=(3, 3))
@property
def image(self):
image = np.uint8([
[0, 10, 20],
[30, 40, 50],
[60, 70, 80]
])
return np.tile(image[:, :, np.newaxis], (1, 1, 3))
@classmethod
def col(cls, idx):
return ia.SegmentationMapsOnImage.DEFAULT_SEGMENT_COLORS[idx]
def test_alpha_only_image_is_visible(self):
# only image visible
observed = self.segmap.draw_on_image(self.image, alpha=0)
assert isinstance(observed, list)
assert len(observed) == 1
assert np.array_equal(observed[0], self.image)
def test_alpha_only_segmap_is_visible(self):
# only segmap visible
observed = self.segmap.draw_on_image(self.image, alpha=1.0,
draw_background=True)
col0 = self.col(0)
col1 = self.col(1)
expected = np.uint8([
[col0, col1, col1],
[col0, col1, col1],
[col0, col1, col1]
])
assert isinstance(observed, list)
assert len(observed) == 1
assert np.array_equal(observed[0], expected)
def test_alpha_with_draw_background(self):
# only segmap visible - in foreground
image = self.image
observed = self.segmap.draw_on_image(image, alpha=1.0,
draw_background=False)
col1 = self.col(1)
expected = np.uint8([
[image[0, 0, :], col1, col1],
[image[1, 0, :], col1, col1],
[image[2, 0, :], col1, col1]
])
assert isinstance(observed, list)
assert len(observed) == 1
assert np.array_equal(observed[0], expected)
def test_alpha_with_draw_background_and_more_than_one_channel(self):
# only segmap visible in foreground + multiple channels in segmap
image = self.image
arr_channel_1 = np.int32([
[0, 1, 5],
[0, 1, 1],
[0, 4, 1]
])
arr_channel_2 = np.int32([
[1, 1, 0],
[2, 2, 0],
[1, 1, 0]
])
arr_channel_3 = np.int32([
[1, 0, 0],
[0, 1, 0],
[0, 0, 3]
])
arr_multi = np.stack(
[arr_channel_1, arr_channel_2, arr_channel_3],
axis=-1)
col = ia.SegmentationMapsOnImage.DEFAULT_SEGMENT_COLORS
expected_channel_1 = np.uint8([
[image[0, 0, :], col[1], col[5]],
[image[1, 0, :], col[1], col[1]],
[image[2, 0, :], col[4], col[1]]
])
expected_channel_2 = np.uint8([
[col[1], col[1], image[0, 2, :]],
[col[2], col[2], image[1, 2, :]],
[col[1], col[1], image[2, 2, :]]
])
expected_channel_3 = np.uint8([
[col[1], image[0, 1, :], image[0, 2, :]],
[image[1, 0, :], col[1], image[1, 2, :]],
[image[2, 0, :], image[2, 1, :], col[3]]
])
segmap_multi = ia.SegmentationMapsOnImage(arr_multi, shape=(3, 3, 3))
observed = segmap_multi.draw_on_image(
image, alpha=1.0, draw_background=False)
assert isinstance(observed, list)
assert len(observed) == 3
assert np.array_equal(observed[0], expected_channel_1)
assert np.array_equal(observed[1], expected_channel_2)
assert np.array_equal(observed[2], expected_channel_3)
def test_non_binary_alpha_with_draw_background(self):
# overlay without background drawn
im = self.image
segmap = self.segmap
a1 = 0.7
a0 = 1.0 - a1
observed = segmap.draw_on_image(im, alpha=a1, draw_background=False)
col1 = np.uint8(self.col(1))
expected = np.float32([
[im[0, 0, :], a0*im[0, 1, :] + a1*col1, a0*im[0, 2, :] + a1*col1],
[im[1, 0, :], a0*im[1, 1, :] + a1*col1, a0*im[1, 2, :] + a1*col1],
[im[2, 0, :], a0*im[2, 1, :] + a1*col1, a0*im[2, 2, :] + a1*col1]
])
d_max = np.max(np.abs(observed[0].astype(np.float32) - expected))
assert isinstance(observed, list)
assert len(observed) == 1
assert observed[0].shape == expected.shape
assert d_max <= 1.0 + 1e-4
def test_non_binary_alpha_with_draw_background_and_bg_class_id(self):
# overlay without background drawn
# different background class id
image = self.image
segmap = self.segmap
a1 = 0.7
a0 = 1.0 - a1
observed = segmap.draw_on_image(image, alpha=a1, draw_background=False,
background_class_id=1)
col0 = np.uint8(self.col(0))
expected = np.float32([
[a0*image[0, 0, :] + a1*col0, image[0, 1, :], image[0, 2, :]],
[a0*image[1, 0, :] + a1*col0, image[1, 1, :], image[1, 2, :]],
[a0*image[2, 0, :] + a1*col0, image[2, 1, :], image[2, 2, :]]
])
d_max = np.max(np.abs(observed[0].astype(np.float32) - expected))
assert isinstance(observed, list)
assert len(observed) == 1
assert observed[0].shape == expected.shape
assert d_max <= 1.0 + 1e-4
def test_non_binary_alpha_with_draw_background_true(self):
# overlay with background drawn
segmap = self.segmap
image = self.image
a1 = 0.7
a0 = 1.0 - a1
observed = segmap.draw_on_image(image, alpha=a1, draw_background=True)
col0 = self.col(0)
col1 = self.col(1)
expected = np.uint8([
[col0, col1, col1],
[col0, col1, col1],
[col0, col1, col1]
])
expected = a0 * image + a1 * expected
d_max = np.max(
np.abs(
observed[0].astype(np.float32)
- expected.astype(np.float32)
)
)
assert isinstance(observed, list)
assert len(observed) == 1
assert observed[0].shape == expected.shape
assert d_max <= 1.0 + 1e-4
def test_resize_segmentation_map_to_image(self):
# resizing of segmap to image
arr = np.int32([
[0, 1, 1]
])
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
image = np.uint8([
[0, 10, 20],
[30, 40, 50],
[60, 70, 80]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
a1 = 0.7
a0 = 1.0 - a1
observed = segmap.draw_on_image(image, alpha=a1, draw_background=True,
resize="segmentation_map")
col0 = self.col(0)
col1 = self.col(1)
expected = np.uint8([
[col0, col1, col1],
[col0, col1, col1],
[col0, col1, col1]
])
expected = a0 * image + a1 * expected
d_max = np.max(
np.abs(
observed[0].astype(np.float32)
- expected.astype(np.float32)
)
)
assert isinstance(observed, list)
assert len(observed) == 1
assert observed[0].shape == expected.shape
assert d_max <= 1.0 + 1e-4
def test_resize_image_to_segmentation_map(self):
# resizing of image to segmap
arr = np.int32([
[0, 1, 1],
[0, 1, 1],
[0, 1, 1]
])
segmap = ia.SegmentationMapsOnImage(arr, shape=(1, 3))
image = np.uint8([[0, 10, 20]])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
image_rs = ia.imresize_single_image(
image, arr.shape[0:2], interpolation="cubic")
a1 = 0.7
a0 = 1.0 - a1
observed = segmap.draw_on_image(image, alpha=a1, draw_background=True,
resize="image")
col0 = self.col(0)
col1 = self.col(1)
expected = np.uint8([
[col0, col1, col1],
[col0, col1, col1],
[col0, col1, col1]
])
expected = a0 * image_rs + a1 * expected
d_max = np.max(
np.abs(
observed[0].astype(np.float32)
- expected.astype(np.float32)
)
)
assert isinstance(observed, list)
assert len(observed) == 1
assert observed[0].shape == expected.shape
assert d_max <= 1.0 + 1e-4
def test_background_threshold_leads_to_deprecation_warning(self):
arr = np.zeros((1, 1, 1), dtype=np.int32)
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
image = np.zeros((1, 1, 3), dtype=np.uint8)
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = segmap.draw_on_image(image, background_threshold=0.01)
assert len(caught_warnings) == 1
assert (
"The argument `background_threshold` is deprecated"
in str(caught_warnings[0].message)
)
class TestSegmentationMapsOnImage_pad(unittest.TestCase):
@property
def segmap(self):
arr = np.int32([
[0, 1, 1],
[0, 2, 1],
[0, 1, 3]
])
return ia.SegmentationMapsOnImage(arr, shape=(3, 3))
def test_default_pad_mode_and_cval(self):
segmap_padded = self.segmap.pad(top=1, right=2, bottom=3, left=4)
observed = segmap_padded.arr
expected = np.pad(
self.segmap.arr,
((1, 3), (4, 2), (0, 0)),
mode="constant",
constant_values=0)
assert np.array_equal(observed, expected)
def test_default_pad_mode(self):
segmap_padded = self.segmap.pad(top=1, right=2, bottom=3, left=4,
cval=1.0)
observed = segmap_padded.arr
expected = np.pad(
self.segmap.arr,
((1, 3), (4, 2), (0, 0)),
mode="constant",
constant_values=1.0)
assert np.array_equal(observed, expected)
def test_default_cval(self):
segmap_padded = self.segmap.pad(top=1, right=2, bottom=3, left=4,
mode="edge")
observed = segmap_padded.arr
expected = np.pad(
self.segmap.arr,
((1, 3), (4, 2), (0, 0)),
mode="edge")
assert np.array_equal(observed, expected)
class TestSegmentationMapsOnImage_pad_to_aspect_ratio(unittest.TestCase):
@property
def segmap(self):
arr = np.int32([
[0, 1, 1],
[0, 2, 1]
])
return ia.SegmentationMapsOnImage(arr, shape=(2, 3))
def test_square_ratio_with_default_pad_mode_and_cval(self):
segmap_padded = self.segmap.pad_to_aspect_ratio(1.0)
observed = segmap_padded.arr
expected = np.pad(
self.segmap.arr,
((0, 1), (0, 0), (0, 0)),
mode="constant",
constant_values=0)
assert np.array_equal(observed, expected)
def test_square_ratio_with_cval_set(self):
segmap_padded = self.segmap.pad_to_aspect_ratio(1.0, cval=1.0)
observed = segmap_padded.arr
expected = np.pad(
self.segmap.arr,
((0, 1), (0, 0), (0, 0)),
mode="constant",
constant_values=1.0)
assert np.array_equal(observed, expected)
def test_square_ratio_with_pad_mode_edge(self):
segmap_padded = self.segmap.pad_to_aspect_ratio(1.0, mode="edge")
observed = segmap_padded.arr
expected = np.pad(
self.segmap.arr,
((0, 1), (0, 0), (0, 0)),
mode="edge")
assert np.array_equal(observed, expected)
def test_higher_than_wide_ratio_with_default_pad_mode_and_cval(self):
segmap_padded = self.segmap.pad_to_aspect_ratio(0.5)
observed = segmap_padded.arr
expected = np.pad(
self.segmap.arr,
((2, 2), (0, 0), (0, 0)),
mode="constant",
constant_values=0)
assert np.array_equal(observed, expected)
def test_return_pad_amounts(self):
segmap_padded, pad_amounts = self.segmap.pad_to_aspect_ratio(
0.5, return_pad_amounts=True)
observed = segmap_padded.arr
expected = np.pad(
self.segmap.arr,
((2, 2), (0, 0), (0, 0)),
mode="constant",
constant_values=0)
assert np.array_equal(observed, expected)
assert pad_amounts == (2, 0, 2, 0)
class TestSegmentationMapsOnImage_resize(unittest.TestCase):
@property
def segmap(self):
arr = np.int32([
[0, 1],
[0, 2]
])
return ia.SegmentationMapsOnImage(arr, shape=(2, 2))
def test_resize_to_twice_the_size(self):
for sizes in [(4, 4), 2.0]:
with self.subTest(sizes=sizes):
# TODO also test other interpolation modes
segmap_scaled = self.segmap.resize(sizes)
observed = segmap_scaled.arr
expected = np.int32([
[0, 0, 1, 1],
[0, 0, 1, 1],
[0, 0, 2, 2],
[0, 0, 2, 2],
]).reshape((4, 4, 1))
assert np.array_equal(observed, expected)
class TestSegmentationMapsOnImage_copy(unittest.TestCase):
@property
def segmap(self):
arr = np.int32([
[0, 1],
[2, 3]
]).reshape((2, 2, 1))
return ia.SegmentationMapsOnImage(arr, shape=(2, 2))
def test_copy(self):
segmap = self.segmap
observed = segmap.copy()
assert np.array_equal(observed.arr, segmap.arr)
assert observed.shape == (2, 2)
assert observed._input_was == segmap._input_was
# ensure shallow copy
observed.arr[0, 0, 0] = 10
assert segmap.arr[0, 0, 0] == 10
def test_set_new_arr(self):
segmap = self.segmap
observed = segmap.copy(np.int32([[10]]).reshape((1, 1, 1)))
assert observed.arr.shape == (1, 1, 1)
assert observed.arr[0, 0, 0] == 10
assert observed._input_was == segmap._input_was
def test_set_new_shape(self):
segmap = self.segmap
observed = segmap.copy(shape=(10, 11, 3))
assert observed.shape == (10, 11, 3)
assert segmap.shape != (10, 11, 3)
assert observed._input_was == segmap._input_was
class TestSegmentationMapsOnImage_deepcopy(unittest.TestCase):
@property
def segmap(self):
arr = np.int32([
[0, 1],
[2, 3]
]).reshape((2, 2, 1))
return ia.SegmentationMapsOnImage(arr, shape=(2, 2))
def test_deepcopy(self):
segmap = self.segmap
observed = segmap.deepcopy()
assert np.array_equal(observed.arr, segmap.arr)
assert observed.shape == (2, 2)
assert observed._input_was == segmap._input_was
observed.arr[0, 0, 0] = 10
assert segmap.arr[0, 0, 0] != 10
def test_set_new_arr(self):
segmap = self.segmap
observed = segmap.deepcopy(np.int32([[10]]).reshape((1, 1, 1)))
assert observed.arr.shape == (1, 1, 1)
assert observed.arr[0, 0, 0] == 10
assert segmap.arr[0, 0, 0] != 10
assert observed._input_was == segmap._input_was
def test_set_new_shape(self):
segmap = self.segmap
observed = segmap.deepcopy(shape=(10, 11, 3))
assert observed.shape == (10, 11, 3)
assert segmap.shape != (10, 11, 3)
assert observed._input_was == segmap._input_was
================================================
FILE: test/augmentables/test_utils.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
from imgaug.augmentables.utils import (
interpolate_points,
interpolate_point_pair,
interpolate_points_by_max_distance,
normalize_shape,
normalize_imglike_shape
)
class Test_interpolate_point_pair(unittest.TestCase):
def test_1_step(self):
point_a = (0, 0)
point_b = (1, 2)
inter = interpolate_point_pair(point_a, point_b, 1)
assert np.allclose(
inter,
np.float32([
[0.5, 1.0]
])
)
def test_2_steps(self):
point_a = (0, 0)
point_b = (1, 2)
inter = interpolate_point_pair(point_a, point_b, 2)
assert np.allclose(
inter,
np.float32([
[1*1/3, 1*2/3],
[2*1/3, 2*2/3]
])
)
def test_0_steps(self):
point_a = (0, 0)
point_b = (1, 2)
inter = interpolate_point_pair(point_a, point_b, 0)
assert len(inter) == 0
class Test_interpolate_points(unittest.TestCase):
def test_2_points_0_steps(self):
points = [
(0, 0),
(1, 2)
]
inter = interpolate_points(points, 0)
assert np.allclose(
inter,
np.float32([
[0, 0],
[1, 2]
])
)
def test_2_points_1_step(self):
points = [
(0, 0),
(1, 2)
]
inter = interpolate_points(points, 1)
assert np.allclose(
inter,
np.float32([
[0, 0],
[0.5, 1.0],
[1, 2],
[0.5, 1.0]
])
)
def test_2_points_1_step_not_closed(self):
points = [
(0, 0),
(1, 2)
]
inter = interpolate_points(points, 1, closed=False)
assert np.allclose(
inter,
np.float32([
[0, 0],
[0.5, 1.0],
[1, 2]
])
)
def test_3_points_0_steps(self):
points = [
(0, 0),
(1, 2),
(0.5, 3)
]
inter = interpolate_points(points, 0)
assert np.allclose(
inter,
np.float32([
[0, 0],
[1, 2],
[0.5, 3]
])
)
def test_3_points_1_step(self):
points = [
(0, 0),
(1, 2),
(0.5, 3)
]
inter = interpolate_points(points, 1)
assert np.allclose(
inter,
np.float32([
[0, 0],
[0.5, 1.0],
[1, 2],
[0.75, 2.5],
[0.5, 3],
[0.25, 1.5]
])
)
def test_3_points_1_step_not_closed(self):
points = [
(0, 0),
(1, 2),
(0.5, 3)
]
inter = interpolate_points(points, 1, closed=False)
assert np.allclose(
inter,
np.float32([
[0, 0],
[0.5, 1.0],
[1, 2],
[0.75, 2.5],
[0.5, 3]
])
)
def test_0_points_1_step(self):
points = []
inter = interpolate_points(points, 1)
assert len(inter) == 0
def test_1_point_0_steps(self):
points = [(0, 0)]
inter = interpolate_points(points, 0)
assert np.allclose(
inter,
np.float32([
[0, 0]
])
)
def test_1_point_1_step(self):
points = [(0, 0)]
inter = interpolate_points(points, 1)
assert np.allclose(
inter,
np.float32([
[0, 0]
])
)
class Test_interpolate_points_by_max_distance(unittest.TestCase):
def test_2_points_dist_10000(self):
points = [
(0, 0),
(0, 2)
]
inter = interpolate_points_by_max_distance(points, 10000)
assert np.allclose(
inter,
points
)
def test_2_points_dist_1(self):
points = [
(0, 0),
(0, 2)
]
inter = interpolate_points_by_max_distance(points, 1.0)
assert np.allclose(
inter,
np.float32([
[0, 0],
[0, 1.0],
[0, 2],
[0, 1.0]
])
)
def test_2_points_dist_1_not_closed(self):
points = [
(0, 0),
(0, 2)
]
inter = interpolate_points_by_max_distance(points, 1.0, closed=False)
assert np.allclose(
inter,
np.float32([
[0, 0],
[0, 1.0],
[0, 2]
])
)
def test_3_points_dist_1(self):
points = [
(0, 0),
(0, 2),
(2, 0)
]
inter = interpolate_points_by_max_distance(points, 1.0)
assert np.allclose(
inter,
np.float32([
[0, 0],
[0, 1.0],
[0, 2],
[1.0, 1.0],
[2, 0],
[1.0, 0]
])
)
def test_3_points_dist_1_not_closed(self):
points = [
(0, 0),
(0, 2),
(2, 0)
]
inter = interpolate_points_by_max_distance(points, 1.0, closed=False)
assert np.allclose(
inter,
np.float32([
[0, 0],
[0, 1.0],
[0, 2],
[1.0, 1.0],
[2, 0]
])
)
def test_0_points_dist_1(self):
points = []
inter = interpolate_points_by_max_distance(points, 1.0)
assert len(inter) == 0
def test_1_point_dist_1(self):
points = [(0, 0)]
inter = interpolate_points_by_max_distance(points, 1.0)
assert np.allclose(
inter,
np.float32([
[0, 0]
])
)
class Test_normalize_shape(unittest.TestCase):
def test_shape_tuple(self):
shape_out = normalize_shape((1, 2))
assert shape_out == (1, 2)
def test_shape_tuple_3d(self):
shape_out = normalize_shape((1, 2, 3))
assert shape_out == (1, 2, 3)
def test_array_1d(self):
arr = np.zeros((5,), dtype=np.uint8)
shape_out = normalize_shape(arr)
assert shape_out == (5,)
def test_array_2d(self):
arr = np.zeros((1, 2), dtype=np.uint8)
shape_out = normalize_shape(arr)
assert shape_out == (1, 2)
def test_array_3d(self):
arr = np.zeros((1, 2, 3), dtype=np.uint8)
shape_out = normalize_shape(arr)
assert shape_out == (1, 2, 3)
class Test_normalize_imglike_shape(unittest.TestCase):
def test_shape_tuple(self):
shape_out = normalize_imglike_shape((1, 2))
assert shape_out == (1, 2)
def test_shape_tuple_3d(self):
shape_out = normalize_imglike_shape((1, 2, 3))
assert shape_out == (1, 2, 3)
def test_array_1d_fails(self):
arr = np.zeros((5,), dtype=np.uint8)
with self.assertRaises(AssertionError):
_ = normalize_imglike_shape(arr)
def test_array_2d(self):
arr = np.zeros((1, 2), dtype=np.uint8)
shape_out = normalize_imglike_shape(arr)
assert shape_out == (1, 2)
def test_array_3d(self):
arr = np.zeros((1, 2, 3), dtype=np.uint8)
shape_out = normalize_imglike_shape(arr)
assert shape_out == (1, 2, 3)
def test_array_4d_fails(self):
arr = np.zeros((1, 2, 3, 4), dtype=np.uint8)
with self.assertRaises(AssertionError):
_ = normalize_imglike_shape(arr)
================================================
FILE: test/augmenters/test_arithmetic.py
================================================
from __future__ import print_function, division, absolute_import
import functools
import sys
import warnings
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import cv2
import six.moves as sm
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug import random as iarandom
from imgaug.testutils import (
array_equal_lists,
keypoints_equal,
reseed,
runtest_pickleable_uint8_img,
assertWarns,
is_parameter_instance
)
import imgaug.augmenters.arithmetic as arithmetic_lib
import imgaug.augmenters.contrast as contrast_lib
from imgaug.augmenters.arithmetic import (
_add_elementwise_cv2_to_uint8,
_multiply_scalar_to_uint8_cv2_mul_,
_multiply_elementwise_to_uint8_,
_invert_uint8_subtract_
)
class Test__add_elementwise_cv2_to_uint8(unittest.TestCase):
def test_image_is_hw(self):
image_shape = (3, 4)
values_shape = (3, 4)
image = np.ones(image_shape, dtype=np.uint8)
values = np.ones(values_shape, dtype=np.float32)
result = _add_elementwise_cv2_to_uint8(image, values)
assert np.array_equal(result, image + 1)
assert result.shape == image_shape
assert result.dtype.name == "uint8"
assert result is not image
def test_image_is_hwn(self):
for nb_channels in [1, 2, 3, 4, 5, 10]:
for values_nb_channels in [None, 1, nb_channels]:
image_shape = (3, 4, nb_channels)
values_shape = (3, 4)
if values_nb_channels is not None:
values_shape = values_shape + (values_nb_channels,)
with self.subTest(image_shape=image_shape,
values_shape=values_shape):
image = np.ones(image_shape, dtype=np.uint8)
values = np.ones(values_shape, dtype=np.float32)
result = _add_elementwise_cv2_to_uint8(image, values)
assert np.array_equal(result, image + 1)
assert result.shape == image_shape
assert result.dtype.name == "uint8"
assert result is not image
def test_image_is_view(self):
for shape in [(4, 3), (4, 3, 3)]:
with self.subTest(shape=shape):
image = np.ones(shape, dtype=np.uint8)
values = np.ones((shape[0]-1, shape[1]), dtype=np.float32)
image = image[0:3, :]
assert image.flags["OWNDATA"] is False
assert image.flags["C_CONTIGUOUS"] is True
result = _add_elementwise_cv2_to_uint8(image, values)
assert np.array_equal(result, image + 1)
assert result.shape == (shape[0]-1, shape[1]) + shape[2:]
assert result.dtype.name == "uint8"
assert result is not image
def test_image_is_non_contiguous(self):
for shape in [(3, 4), (3, 4, 3)]:
with self.subTest(shape=shape):
image = np.ones(shape, dtype=np.uint8, order="F")
values = np.ones(shape, dtype=np.float32)
assert image.flags["OWNDATA"] is True
assert image.flags["C_CONTIGUOUS"] is False
result = _add_elementwise_cv2_to_uint8(image, values)
assert np.array_equal(result, image + 1)
assert result.shape == shape
assert result.dtype.name == "uint8"
assert result is not image
def test_floats_with_decimal_points(self):
image_shape = (3, 4, 3)
values_shape = (3, 4)
image = np.ones(image_shape, dtype=np.uint8)
values = np.full(values_shape, 1.7, dtype=np.float32)
result = _add_elementwise_cv2_to_uint8(image, values)
# cv2.add() performs rounding
assert np.array_equal(result, image + 2)
assert result.shape == image_shape
assert result.dtype.name == "uint8"
assert result is not image
def test_is_saturating(self):
image_shape = (3, 4, 3)
values_shape = (3, 4)
for value in [-1000.5, 1000.5]:
with self.subTest(value=value):
image = np.ones(image_shape, dtype=np.uint8)
values = np.full(values_shape, value, dtype=np.float32)
result = _add_elementwise_cv2_to_uint8(image, values)
if value < 0:
assert np.all(result == 0)
else:
assert np.all(result == 255)
assert result.shape == image_shape
assert result.dtype.name == "uint8"
assert result is not image
def test_values_is_int_uint(self):
image_shape = (3, 4, 3)
values_shape = (3, 4)
dtypes = ["int8", "int16", "int32", "uint8", "uint16"]
values = ["min", -10, -1, 0, 1, 10, "max"]
for dt in dtypes:
for value in values:
vmin, _, vmax = iadt.get_value_range_of_dtype(dt)
if value == "min":
value = max(vmin, -1000)
elif value == "max":
value = min(1000, vmax)
elif value < 0:
value = 0 if dt.startswith("uint") else value
with self.subTest(dtype=dt, value=value):
image = np.full(image_shape, 127, dtype=np.uint8)
values_arr = np.full(values_shape, value, dtype=dt)
result = _add_elementwise_cv2_to_uint8(image, values_arr)
expected_value = min(max(127 + value, 0), 255)
assert np.all(result == expected_value)
assert result.shape == image_shape
assert result.dtype.name == "uint8"
assert result is not image
def test_values_is_float(self):
image_shape = (3, 4, 3)
values_shape = (3, 4)
dtypes = ["float32", "float64"]
values = [
[-1000.0, -255.0, -1.0, 0.0, 1.0, 255.0, 1000.0],
[-1000.0, -255.0, -1.0, 0.0, 1.0, 255.0, 1000.0]
]
for dt, values_dt in zip(dtypes, values):
for value in values_dt:
with self.subTest(dtype=dt, value=value):
image = np.full(image_shape, 127, dtype=np.uint8)
values = np.full(values_shape, value, dtype=dt)
result = _add_elementwise_cv2_to_uint8(image, values)
if value < -1.01:
expected_value = 0
elif np.isclose(value, -1.0):
expected_value = 126
elif np.isclose(value, 0.0):
expected_value = 127
elif np.isclose(value, 1.0):
expected_value = 128
else:
expected_value = 255
assert np.all(result == expected_value)
assert result.shape == image_shape
assert result.dtype.name == "uint8"
assert result is not image
class Test__multiply_scalar_to_uint8_cv2_mul_(unittest.TestCase):
def test_single_multiplier_image_hw(self):
image = np.full((3, 4), 10, dtype=np.uint8)
image_cp = np.copy(image)
multiplier = np.float32(2.67)
observed = _multiply_scalar_to_uint8_cv2_mul_(image_cp, multiplier)
expected = np.full((3, 4), 27, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == image.shape
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_single_multiplier_image_hwc(self):
for nb_channels in [1, 2, 3, 4, 5, 10, 512, 513]:
with self.subTest(nb_channels=nb_channels):
image = np.full((3, 4, nb_channels), 10, dtype=np.uint8)
image_cp = np.copy(image)
multiplier = np.float32(2.6)
observed = _multiply_scalar_to_uint8_cv2_mul_(image_cp,
multiplier)
expected = np.full((3, 4, nb_channels), 26, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == image.shape
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_single_multiplier_saturating(self):
for value in [-0.1, 0, 30, 100.5]:
with self.subTest(value=value):
image = np.full((3, 4), 10, dtype=np.uint8)
image_cp = np.copy(image)
multiplier = np.float32(value)
observed = _multiply_scalar_to_uint8_cv2_mul_(image_cp,
multiplier)
if value <= 0+1e-4:
expected = np.zeros_like(image)
else:
expected = np.full(image.shape, 255, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == image.shape
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_channelwise_multiplier_image_hw(self):
image = np.full((3, 4), 10, dtype=np.uint8)
image_cp = np.copy(image)
multiplier = np.array([2.6], dtype=np.float32)
observed = _multiply_scalar_to_uint8_cv2_mul_(image_cp, multiplier)
expected = np.full((3, 4), 26, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == image.shape
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_channelwise_multiplier_image_hwc(self):
for nb_channels in [1, 2, 3, 4, 5, 10, 512, 513]:
with self.subTest(nb_channels=nb_channels):
image = np.full((3, 4, nb_channels), 10, dtype=np.uint8)
image_cp = np.copy(image)
multiplier = np.ones((nb_channels,), dtype=np.float32)
multiplier[0] = 2.6
if nb_channels >= 2:
multiplier[1] = 4.0
if nb_channels >= 3:
multiplier[2] = 5.6
if nb_channels >= 4:
multiplier[nb_channels-1] = 7.1
observed = _multiply_scalar_to_uint8_cv2_mul_(image_cp,
multiplier)
expected = image
expected[:, :, 0] = 26
if nb_channels >= 2:
expected[:, :, 1] = 40
if nb_channels >= 3:
expected[:, :, 2] = 56
if nb_channels >= 4:
expected[:, :, nb_channels-1] = 71
assert np.array_equal(observed, expected)
assert observed.shape == image.shape
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_image_is_view(self):
image = np.full((4, 3), 10, dtype=np.uint8)
image_cp = np.copy(image)[0:3, :]
multiplier = np.float32(2.6)
observed = _multiply_scalar_to_uint8_cv2_mul_(image_cp, multiplier)
expected = np.full((3, 3), 26, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == (3, 3)
assert observed.dtype.name == "uint8"
def test_image_is_non_contiguous(self):
image = np.full((3, 4), 10, dtype=np.uint8)
image_cp = np.full((3, 4), 10, dtype=np.uint8, order="F")
multiplier = np.float32(2.6)
observed = _multiply_scalar_to_uint8_cv2_mul_(image_cp, multiplier)
expected = np.full((3, 4), 26, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == image.shape
assert observed.dtype.name == "uint8"
class Test_multiply_elementwise_to_non_uint8(unittest.TestCase):
def test_image_is_hw(self):
image = np.full((4, 3), 10, dtype=np.uint8)
image_cp = np.copy(image)
multipliers = np.full((4, 3), 2.7, dtype=np.float32)
observed = _multiply_elementwise_to_uint8_(image_cp, multipliers)
expected = np.full((4, 3), 27, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == (4, 3)
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_image_is_hwn(self):
for nb_channels in [1, 2, 3, 4, 5, 10, 512, 513]:
with self.subTest(nb_channels=nb_channels):
image = np.full((4, 3, nb_channels), 10, dtype=np.uint8)
image_cp = np.copy(image)
multipliers = np.full((4, 3, nb_channels), 1, dtype=np.float32)
multipliers[:, :, 0] = 2.7
if nb_channels >= 2:
multipliers[:, :, 1] = 4.0
if nb_channels >= 3:
multipliers[:, :, 2] = 6.4
if nb_channels >= 4:
multipliers[:, :, -1] = 8.3
observed = _multiply_elementwise_to_uint8_(image_cp,
multipliers)
expected = np.full((4, 3, nb_channels), 10, dtype=np.uint8)
expected[:, :, 0] = 27
if nb_channels >= 2:
expected[:, :, 1] = 40
if nb_channels >= 3:
expected[:, :, 2] = 64
if nb_channels >= 4:
expected[:, :, -1] = 83
assert np.array_equal(observed, expected)
assert observed.shape == (4, 3, nb_channels)
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_multipliers_hw(self):
nb_channels = 3
image = np.full((4, 3, nb_channels), 10, dtype=np.uint8)
image_cp = np.copy(image)
multipliers = np.full((4, 3), 2.7, dtype=np.float32)
observed = _multiply_elementwise_to_uint8_(image_cp,
multipliers)
expected = np.full((4, 3, nb_channels), 27, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == (4, 3, nb_channels)
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_multipliers_hw1(self):
nb_channels = 3
image = np.full((4, 3, nb_channels), 10, dtype=np.uint8)
image_cp = np.copy(image)
multipliers = np.full((4, 3, 1), 2.7, dtype=np.float32)
observed = _multiply_elementwise_to_uint8_(image_cp,
multipliers)
expected = np.full((4, 3, nb_channels), 27, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == (4, 3, nb_channels)
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_multipliers_is_float(self):
dtypes = ["float16", "float32", "float64"]
for dt in dtypes:
image = np.full((4, 3, 3), 10, dtype=np.uint8)
image_cp = np.copy(image)
multipliers = np.full((4, 3, 3), 1, dtype=dt)
multipliers[:, :, 0] = 2.7
multipliers[:, :, 1] = 4.0
multipliers[:, :, 2] = 6.4
observed = _multiply_elementwise_to_uint8_(image_cp,
multipliers)
expected = np.full((4, 3, 3), 10, dtype=np.uint8)
expected[:, :, 0] = 27
expected[:, :, 1] = 40
expected[:, :, 2] = 64
assert np.array_equal(observed, expected)
assert observed.shape == (4, 3, 3)
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_multipliers_is_uint_int(self):
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dt in dtypes:
with self.subTest(dtype=dt):
image = np.full((4, 3, 3), 10, dtype=np.uint8)
image_cp = np.copy(image)
multipliers = np.full((4, 3, 3), 1, dtype=dt)
multipliers[:, :, 0] = 2
multipliers[:, :, 1] = 4
multipliers[:, :, 2] = 5
observed = _multiply_elementwise_to_uint8_(image_cp,
multipliers)
expected = np.full((4, 3, 3), 10, dtype=np.uint8)
expected[:, :, 0] = 20
expected[:, :, 1] = 40
expected[:, :, 2] = 50
assert np.array_equal(observed, expected)
assert observed.shape == (4, 3, 3)
assert observed.dtype.name == "uint8"
assert observed is image_cp
def test_image_is_view(self):
nb_channels = 3
image = np.full((4, 3, nb_channels), 10, dtype=np.uint8)
image_cp = np.copy(image)[0:3, :, :]
assert image_cp.flags["OWNDATA"] is False
assert image_cp.flags["C_CONTIGUOUS"] is True
multipliers = np.full((3, 3, 1), 2.7, dtype=np.float32)
observed = _multiply_elementwise_to_uint8_(image_cp,
multipliers)
expected = np.full((3, 3, nb_channels), 27, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == (3, 3, nb_channels)
assert observed.dtype.name == "uint8"
def test_image_is_noncontiguous(self):
nb_channels = 3
image = np.full((4, 3, nb_channels), 10, dtype=np.uint8, order="F")
assert image.flags["OWNDATA"] is True
assert image.flags["C_CONTIGUOUS"] is False
multipliers = np.full((4, 3, 1), 2.7, dtype=np.float32)
observed = _multiply_elementwise_to_uint8_(image,
multipliers)
expected = np.full((4, 3, nb_channels), 27, dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == (4, 3, nb_channels)
assert observed.dtype.name == "uint8"
class Test_cutout(unittest.TestCase):
@mock.patch("imgaug.augmenters.arithmetic.cutout_")
def test_mocked(self, mock_inplace):
image = np.mod(np.arange(100*100*3), 255).astype(np.uint8).reshape(
(100, 100, 3))
mock_inplace.return_value = "foo"
rng = iarandom.RNG(0)
image_aug = iaa.cutout(image,
x1=10,
y1=20,
x2=30,
y2=40,
fill_mode="gaussian",
cval=1,
fill_per_channel=0.5,
seed=rng)
assert mock_inplace.call_count == 1
assert image_aug == "foo"
args = mock_inplace.call_args_list[0][0]
assert args[0] is not image
assert np.array_equal(args[0], image)
assert np.isclose(args[1], 10)
assert np.isclose(args[2], 20)
assert np.isclose(args[3], 30)
assert np.isclose(args[4], 40)
assert args[5] == "gaussian"
assert args[6] == 1
assert np.isclose(args[7], 0.5)
assert args[8] is rng
class Test_cutout_(unittest.TestCase):
def test_with_simple_image(self):
image = np.mod(np.arange(100*100*3), 255).astype(np.uint8).reshape(
(100, 100, 3))
image = 1 + image
image_aug = iaa.cutout_(image,
x1=10,
y1=20,
x2=30,
y2=40,
fill_mode="constant",
cval=0,
fill_per_channel=False,
seed=None)
mask = np.zeros(image.shape, dtype=bool)
mask[20:40, 10:30, :] = True
overlap_inside = np.sum(image_aug[mask] == 0) / np.sum(mask)
overlap_outside = np.sum(image_aug[~mask] > 0) / np.sum(~mask)
assert image_aug is image
assert overlap_inside >= 1.0 - 1e-4
assert overlap_outside >= 1.0 - 1e-4
@mock.patch("imgaug.augmenters.arithmetic._fill_rectangle_constant_")
def test_fill_mode_constant_mocked(self, mock_fill):
self._test_with_fill_mode_mocked("constant", mock_fill)
@mock.patch("imgaug.augmenters.arithmetic._fill_rectangle_gaussian_")
def test_fill_mode_gaussian_mocked(self, mock_fill):
self._test_with_fill_mode_mocked("gaussian", mock_fill)
@classmethod
def _test_with_fill_mode_mocked(cls, fill_mode, mock_fill):
image = np.mod(np.arange(100*100*3), 256).astype(np.uint8).reshape(
(100, 100, 3))
mock_fill.return_value = image
seed = iarandom.RNG(0)
image_aug = iaa.cutout_(image,
x1=10,
y1=20,
x2=30,
y2=40,
fill_mode=fill_mode,
cval=0,
fill_per_channel=False,
seed=seed)
assert mock_fill.call_count == 1
args = mock_fill.call_args_list[0][0]
kwargs = mock_fill.call_args_list[0][1]
assert image_aug is image
assert args[0] is image
assert kwargs["x1"] == 10
assert kwargs["y1"] == 20
assert kwargs["x2"] == 30
assert kwargs["y2"] == 40
assert kwargs["cval"] == 0
assert kwargs["per_channel"] is False
assert kwargs["random_state"] is seed
def test_zero_height(self):
image = np.mod(np.arange(100*100*3), 255).astype(np.uint8).reshape(
(100, 100, 3))
image = 1 + image
image_cp = np.copy(image)
image_aug = iaa.cutout_(image,
x1=10,
y1=20,
x2=30,
y2=20,
fill_mode="constant",
cval=0,
fill_per_channel=False,
seed=None)
assert np.array_equal(image_aug, image_cp)
def test_zero_height_width(self):
image = np.mod(np.arange(100*100*3), 255).astype(np.uint8).reshape(
(100, 100, 3))
image = 1 + image
image_cp = np.copy(image)
image_aug = iaa.cutout_(image,
x1=10,
y1=20,
x2=10,
y2=40,
fill_mode="constant",
cval=0,
fill_per_channel=False,
seed=None)
assert np.array_equal(image_aug, image_cp)
def test_position_outside_of_image_rect_fully_outside(self):
image = np.mod(np.arange(100*100*3), 255).astype(np.uint8).reshape(
(100, 100, 3))
image = 1 + image
image_cp = np.copy(image)
image_aug = iaa.cutout_(image,
x1=-50,
y1=150,
x2=-1,
y2=200,
fill_mode="constant",
cval=0,
fill_per_channel=False,
seed=None)
assert np.array_equal(image_aug, image_cp)
def test_position_outside_of_image_rect_partially_inside(self):
image = np.mod(np.arange(100*100*3), 255).astype(np.uint8).reshape(
(100, 100, 3))
image = 1 + image
image_aug = iaa.cutout_(image,
x1=-25,
y1=-25,
x2=25,
y2=25,
fill_mode="constant",
cval=0,
fill_per_channel=False,
seed=None)
assert np.all(image_aug[0:25, 0:25] == 0)
assert np.all(image_aug[0:25, 25:] > 0)
assert np.all(image_aug[25:, :] > 0)
def test_zero_sized_axes(self):
shapes = [(0, 0, 0),
(1, 0, 0),
(0, 1, 0),
(0, 1, 1),
(1, 1, 0),
(1, 0, 1),
(1, 0),
(0, 1),
(0, 0)]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_cp = np.copy(image)
image_aug = iaa.cutout_(image,
x1=-5,
y1=-5,
x2=5,
y2=5,
fill_mode="constant",
cval=0)
assert np.array_equal(image_aug, image_cp)
class Test_fill_rectangle_gaussian_(unittest.TestCase):
def test_simple_image(self):
image = np.mod(np.arange(100*100*3), 256).astype(np.uint8).reshape(
(100, 100, 3))
image_cp = np.copy(image)
rng = iarandom.RNG(0)
image_aug = arithmetic_lib._fill_rectangle_gaussian_(
image,
x1=10,
y1=20,
x2=60,
y2=70,
cval=0,
per_channel=False,
random_state=rng)
assert np.array_equal(image_aug[:20, :],
image_cp[:20, :])
assert not np.array_equal(image_aug[20:70, 10:60],
image_cp[20:70, 10:60])
assert np.isclose(np.average(image_aug[20:70, 10:60]), 127.5,
rtol=0, atol=5.0)
assert np.isclose(np.std(image_aug[20:70, 10:60]), 255.0/2.0/3.0,
rtol=0, atol=2.5)
def test_per_channel(self):
image = np.uint8([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
image = np.tile(image.reshape((1, 10, 1)), (1, 1, 3))
image_aug = arithmetic_lib._fill_rectangle_gaussian_(
np.copy(image),
x1=0, y1=0, x2=10, y2=1,
cval=0,
per_channel=False,
random_state=iarandom.RNG(0))
image_aug_pc = arithmetic_lib._fill_rectangle_gaussian_(
np.copy(image),
x1=0, y1=0, x2=10, y2=1,
cval=0,
per_channel=True,
random_state=iarandom.RNG(0))
diff11 = (image_aug[..., 0] != image_aug[..., 1])
diff12 = (image_aug[..., 0] != image_aug[..., 2])
diff21 = (image_aug_pc[..., 0] != image_aug_pc[..., 1])
diff22 = (image_aug_pc[..., 0] != image_aug_pc[..., 2])
assert not np.any(diff11)
assert not np.any(diff12)
assert np.any(diff21)
assert np.any(diff22)
def test_deterministic_with_same_seed(self):
image = np.uint8([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
image = np.tile(image.reshape((1, 10, 1)), (1, 1, 3))
image_aug_pc1 = arithmetic_lib._fill_rectangle_gaussian_(
np.copy(image),
x1=0, y1=0, x2=10, y2=1,
cval=0,
per_channel=True,
random_state=iarandom.RNG(0))
image_aug_pc2 = arithmetic_lib._fill_rectangle_gaussian_(
np.copy(image),
x1=0, y1=0, x2=10, y2=1,
cval=0,
per_channel=True,
random_state=iarandom.RNG(0))
image_aug_pc3 = arithmetic_lib._fill_rectangle_gaussian_(
np.copy(image),
x1=0, y1=0, x2=10, y2=1,
cval=0,
per_channel=True,
random_state=iarandom.RNG(1))
assert np.array_equal(image_aug_pc1, image_aug_pc2)
assert not np.array_equal(image_aug_pc2, image_aug_pc3)
def test_no_channels(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
image = np.uint8([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
image = image.reshape((1, 10))
image_aug = arithmetic_lib._fill_rectangle_gaussian_(
np.copy(image),
x1=0, y1=0, x2=10, y2=1,
cval=0,
per_channel=per_channel,
random_state=iarandom.RNG(0))
assert not np.array_equal(image_aug, image)
def test_unusual_channel_numbers(self):
for nb_channels in [1, 2, 3, 4, 5, 511, 512, 513]:
for per_channel in [False, True]:
with self.subTest(nb_channels=nb_channels,
per_channel=per_channel):
image = np.uint8([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
image = np.tile(image.reshape((1, 10, 1)),
(1, 1, nb_channels))
image_aug = arithmetic_lib._fill_rectangle_gaussian_(
np.copy(image),
x1=0, y1=0, x2=10, y2=1,
cval=0,
per_channel=True,
random_state=iarandom.RNG(0))
assert not np.array_equal(image_aug, image)
def test_other_dtypes_bool(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
image = np.array([0, 1], dtype=bool)
image = np.tile(image, (int(3*300*300/2),))
image = image.reshape((300, 300, 3))
image_cp = np.copy(image)
rng = iarandom.RNG(0)
image_aug = arithmetic_lib._fill_rectangle_gaussian_(
image,
x1=10,
y1=10,
x2=300-10,
y2=300-10,
cval=0,
per_channel=per_channel,
random_state=rng)
rect = image_aug[10:-10, 10:-10]
p_true = np.sum(rect) / rect.size
assert np.array_equal(image_aug[:10, :], image_cp[:10, :])
assert not np.array_equal(rect, image_cp[10:-10, 10:-10])
assert np.isclose(p_true, 0.5, rtol=0, atol=0.1)
if per_channel:
for c in np.arange(1, image.shape[2]):
assert not np.array_equal(image_aug[..., 0],
image_aug[..., c])
def test_other_dtypes_int_uint(self):
try:
high_res_dt = np.float128
except AttributeError:
high_res_dt = np.float64
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
dynamic_range = int(max_value) - int(min_value)
gaussian_min = iarandom.RNG(0).normal(min_value, 0.0001,
size=(1,))
gaussian_max = iarandom.RNG(0).normal(max_value, 0.0001,
size=(1,))
assert min_value - 1.0 <= gaussian_min <= min_value + 1.0
assert max_value - 1.0 <= gaussian_max <= max_value + 1.0
for per_channel in [False, True]:
with self.subTest(dtype=dtype, per_channel=per_channel):
# dont generate image from choice() here, that seems
# to not support uint64 (max value not in result)
image = np.array([min_value, min_value+1,
int(center_value),
max_value-1, max_value], dtype=dtype)
image = np.tile(image, (int(3*300*300/5),))
image = image.reshape((300, 300, 3))
assert min_value in image
assert max_value in image
image_cp = np.copy(image)
rng = iarandom.RNG(0)
image_aug = arithmetic_lib._fill_rectangle_gaussian_(
image, x1=10, y1=10, x2=300-10, y2=300-10,
cval=0, per_channel=per_channel, random_state=rng)
rect = image_aug[10:-10, 10:-10]
mean = np.average(high_res_dt(rect))
std = np.std(high_res_dt(rect) - center_value)
assert np.array_equal(image_aug[:10, :], image_cp[:10, :])
assert not np.array_equal(rect,
image_cp[10:-10, 10:-10])
assert np.isclose(mean, center_value, rtol=0,
atol=0.05*dynamic_range)
assert np.isclose(std, dynamic_range/2.0/3.0, rtol=0,
atol=0.05*dynamic_range/2.0/3.0)
assert np.min(rect) < min_value + 0.2 * dynamic_range
assert np.max(rect) > max_value - 0.2 * dynamic_range
if per_channel:
for c in np.arange(1, image.shape[2]):
assert not np.array_equal(image_aug[..., 0],
image_aug[..., c])
def test_other_dtypes_float(self):
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
min_value = 0.0
center_value = 0.5
max_value = 1.0
dynamic_range = high_res_dt(max_value) - high_res_dt(min_value)
gaussian_min = iarandom.RNG(0).normal(min_value, 0.0001,
size=(1,))
gaussian_max = iarandom.RNG(0).normal(max_value, 0.0001,
size=(1,))
assert min_value - 1.0 <= gaussian_min <= min_value + 1.0
assert max_value - 1.0 <= gaussian_max <= max_value + 1.0
for per_channel in [False, True]:
with self.subTest(dtype=dtype, per_channel=per_channel):
# dont generate image from choice() here, that seems
# to not support uint64 (max value not in result)
image = np.array([min_value, min_value+1,
int(center_value),
max_value-1, max_value], dtype=dtype)
image = np.tile(image, (int(3*300*300/5),))
image = image.reshape((300, 300, 3))
assert np.any(np.isclose(image, min_value,
rtol=0, atol=1e-4))
assert np.any(np.isclose(image, max_value,
rtol=0, atol=1e-4))
image_cp = np.copy(image)
rng = iarandom.RNG(0)
image_aug = arithmetic_lib._fill_rectangle_gaussian_(
image, x1=10, y1=10, x2=300-10, y2=300-10,
cval=0, per_channel=per_channel, random_state=rng)
rect = image_aug[10:-10, 10:-10]
mean = np.average(high_res_dt(rect))
std = np.std(high_res_dt(rect) - center_value)
assert np.allclose(image_aug[:10, :], image_cp[:10, :],
rtol=0, atol=1e-4)
assert not np.allclose(rect, image_cp[10:-10, 10:-10],
rtol=0, atol=1e-4)
assert np.isclose(mean, center_value, rtol=0,
atol=0.05*dynamic_range)
assert np.isclose(std, dynamic_range/2.0/3.0, rtol=0,
atol=0.05*dynamic_range/2.0/3.0)
assert np.min(rect) < min_value + 0.2 * dynamic_range
assert np.max(rect) > max_value - 0.2 * dynamic_range
if per_channel:
for c in np.arange(1, image.shape[2]):
assert not np.allclose(image_aug[..., 0],
image_aug[..., c],
rtol=0, atol=1e-4)
class Test_fill_rectangle_constant_(unittest.TestCase):
def test_simple_image(self):
image = np.mod(np.arange(100*100*3), 256).astype(np.uint8).reshape(
(100, 100, 3))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=20, x2=60, y2=70,
cval=17, per_channel=False, random_state=None)
assert np.array_equal(image_aug[:20, :], image_cp[:20, :])
assert np.all(image_aug[20:70, 10:60] == 17)
def test_iterable_cval_but_per_channel_is_false(self):
image = np.mod(np.arange(100*100*3), 256).astype(np.uint8).reshape(
(100, 100, 3))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=20, x2=60, y2=70,
cval=[17, 21, 25], per_channel=False, random_state=None)
assert np.array_equal(image_aug[:20, :], image_cp[:20, :])
assert np.all(image_aug[20:70, 10:60] == 17)
def test_iterable_cval_with_per_channel_is_true(self):
image = np.mod(np.arange(100*100*3), 256).astype(np.uint8).reshape(
(100, 100, 3))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=20, x2=60, y2=70,
cval=[17, 21, 25], per_channel=True, random_state=None)
assert np.array_equal(image_aug[:20, :], image_cp[:20, :])
assert np.all(image_aug[20:70, 10:60, 0] == 17)
assert np.all(image_aug[20:70, 10:60, 1] == 21)
assert np.all(image_aug[20:70, 10:60, 2] == 25)
def test_iterable_cval_with_per_channel_is_true_channel_mismatch(self):
image = np.mod(np.arange(100*100*5), 256).astype(np.uint8).reshape(
(100, 100, 5))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=20, x2=60, y2=70,
cval=[17, 21], per_channel=True, random_state=None)
assert np.array_equal(image_aug[:20, :], image_cp[:20, :])
assert np.all(image_aug[20:70, 10:60, 0] == 17)
assert np.all(image_aug[20:70, 10:60, 1] == 21)
assert np.all(image_aug[20:70, 10:60, 2] == 17)
assert np.all(image_aug[20:70, 10:60, 3] == 21)
assert np.all(image_aug[20:70, 10:60, 4] == 17)
def test_single_cval_with_per_channel_is_true(self):
image = np.mod(np.arange(100*100*3), 256).astype(np.uint8).reshape(
(100, 100, 3))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=20, x2=60, y2=70,
cval=17, per_channel=True, random_state=None)
assert np.array_equal(image_aug[:20, :], image_cp[:20, :])
assert np.all(image_aug[20:70, 10:60, 0] == 17)
assert np.all(image_aug[20:70, 10:60, 1] == 17)
assert np.all(image_aug[20:70, 10:60, 2] == 17)
def test_no_channels_single_cval(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
image = np.mod(
np.arange(100*100), 256
).astype(np.uint8).reshape((100, 100))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=20, x2=60, y2=70,
cval=17, per_channel=per_channel, random_state=None)
assert np.array_equal(image_aug[:20, :], image_cp[:20, :])
assert np.all(image_aug[20:70, 10:60] == 17)
def test_no_channels_iterable_cval(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
image = np.mod(
np.arange(100*100), 256
).astype(np.uint8).reshape((100, 100))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=20, x2=60, y2=70,
cval=[17, 21, 25], per_channel=per_channel,
random_state=None)
assert np.array_equal(image_aug[:20, :], image_cp[:20, :])
assert np.all(image_aug[20:70, 10:60] == 17)
def test_unusual_channel_numbers(self):
for nb_channels in [1, 2, 4, 5, 511, 512, 513]:
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
image = np.mod(
np.arange(100*100*nb_channels), 256
).astype(np.uint8).reshape((100, 100, nb_channels))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=20, x2=60, y2=70,
cval=[17, 21], per_channel=per_channel,
random_state=None)
assert np.array_equal(image_aug[:20, :], image_cp[:20, :])
if per_channel:
for c in np.arange(nb_channels):
val = 17 if c % 2 == 0 else 21
assert np.all(image_aug[20:70, 10:60, c] == val)
else:
assert np.all(image_aug[20:70, 10:60, :] == 17)
def test_other_dtypes_bool(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
image = np.array([0, 1], dtype=bool)
image = np.tile(image, (int(3*300*300/2),))
image = image.reshape((300, 300, 3))
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=10, x2=300-10, y2=300-10,
cval=[0, 1], per_channel=per_channel,
random_state=None)
rect = image_aug[10:-10, 10:-10]
assert np.array_equal(image_aug[:10, :], image_cp[:10, :])
if per_channel:
assert np.all(image_aug[10:-10, 10:-10, 0] == 0)
assert np.all(image_aug[10:-10, 10:-10, 1] == 1)
assert np.all(image_aug[10:-10, 10:-10, 2] == 0)
else:
assert np.all(image_aug[20:70, 10:60] == 0)
def test_other_dtypes_uint_int(self):
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dtype in dtypes:
for per_channel in [False, True]:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
with self.subTest(dtype=dtype, per_channel=per_channel):
image = np.array([min_value, min_value+1,
int(center_value),
max_value-1, max_value], dtype=dtype)
image = np.tile(image, (int(3*300*300/5),))
image = image.reshape((300, 300, 3))
assert min_value in image
assert max_value in image
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=10, x2=300-10, y2=300-10,
cval=[min_value, 10, max_value],
per_channel=per_channel,
random_state=None)
assert np.array_equal(image_aug[:10, :], image_cp[:10, :])
if per_channel:
assert np.all(image_aug[10:-10, 10:-10, 0]
== min_value)
assert np.all(image_aug[10:-10, 10:-10, 1]
== 10)
assert np.all(image_aug[10:-10, 10:-10, 2]
== max_value)
else:
assert np.all(image_aug[-10:-10, 10:-10] == min_value)
def test_other_dtypes_float(self):
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
for per_channel in [False, True]:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
with self.subTest(dtype=dtype, per_channel=per_channel):
image = np.array([min_value, min_value+1,
int(center_value),
max_value-1, max_value], dtype=dtype)
image = np.tile(image, (int(3*300*300/5),))
image = image.reshape((300, 300, 3))
# Use this here instead of any(isclose(...)) because
# the latter one leads to overflow warnings.
assert image.flat[0] <= high_res_dt(min_value) + 1.0
assert image.flat[4] >= high_res_dt(max_value) - 1.0
image_cp = np.copy(image)
image_aug = arithmetic_lib._fill_rectangle_constant_(
image,
x1=10, y1=10, x2=300-10, y2=300-10,
cval=[min_value, 10, max_value],
per_channel=per_channel,
random_state=None)
assert image_aug.dtype.name == dtype
assert np.allclose(image_aug[:10, :], image_cp[:10, :],
rtol=0, atol=1e-4)
if per_channel:
assert np.allclose(image_aug[10:-10, 10:-10, 0],
high_res_dt(min_value),
rtol=0, atol=1e-4)
assert np.allclose(image_aug[10:-10, 10:-10, 1],
high_res_dt(10),
rtol=0, atol=1e-4)
assert np.allclose(image_aug[10:-10, 10:-10, 2],
high_res_dt(max_value),
rtol=0, atol=1e-4)
else:
assert np.allclose(image_aug[-10:-10, 10:-10],
high_res_dt(min_value),
rtol=0, atol=1e-4)
class TestAdd(unittest.TestCase):
def setUp(self):
reseed()
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_ = iaa.Add(value="test")
except Exception:
got_exception = True
assert got_exception
got_exception = False
try:
_ = iaa.Add(value=1, per_channel="test")
except Exception:
got_exception = True
assert got_exception
def test_add_zero(self):
# no add, shouldnt change anything
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.Add(value=0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
def test_add_one(self):
# add > 0
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.Add(value=1)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images + 1
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = [images_list[0] + 1]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images + 1
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [images_list[0] + 1]
assert array_equal_lists(observed, expected)
def test_minus_one(self):
# add < 0
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.Add(value=-1)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images - 1
assert np.array_equal(observed, expected)
observed = aug.augment_images(images_list)
expected = [images_list[0] - 1]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images - 1
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [images_list[0] - 1]
assert array_equal_lists(observed, expected)
def test_uint8_every_possible_value(self):
# uint8, every possible addition for base value 127
for value_type in [float, int]:
for per_channel in [False, True]:
for value in np.arange(-255, 255+1):
aug = iaa.Add(value=value_type(value), per_channel=per_channel)
expected = np.clip(127 + value_type(value), 0, 255)
img = np.full((1, 1), 127, dtype=np.uint8)
img_aug = aug.augment_image(img)
assert img_aug.item(0) == expected
img = np.full((1, 1, 3), 127, dtype=np.uint8)
img_aug = aug.augment_image(img)
assert np.all(img_aug == expected)
def test_add_floats(self):
# specific tests with floats
aug = iaa.Add(value=0.75)
img = np.full((1, 1), 1, dtype=np.uint8)
img_aug = aug.augment_image(img)
assert img_aug.item(0) == 2
img = np.full((1, 1), 1, dtype=np.uint16)
img_aug = aug.augment_image(img)
assert img_aug.item(0) == 2
aug = iaa.Add(value=0.45)
img = np.full((1, 1), 1, dtype=np.uint8)
img_aug = aug.augment_image(img)
assert img_aug.item(0) == 1
img = np.full((1, 1), 1, dtype=np.uint16)
img_aug = aug.augment_image(img)
assert img_aug.item(0) == 1
def test_stochastic_parameters_as_value(self):
# test other parameters
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
aug = iaa.Add(value=iap.DiscreteUniform(1, 10))
observed = aug.augment_images(images)
assert 100 + 1 <= np.average(observed) <= 100 + 10
aug = iaa.Add(value=iap.Uniform(1, 10))
observed = aug.augment_images(images)
assert 100 + 1 <= np.average(observed) <= 100 + 10
aug = iaa.Add(value=iap.Clip(iap.Normal(1, 1), -3, 3))
observed = aug.augment_images(images)
assert 100 - 3 <= np.average(observed) <= 100 + 3
aug = iaa.Add(value=iap.Discretize(iap.Clip(iap.Normal(1, 1), -3, 3)))
observed = aug.augment_images(images)
assert 100 - 3 <= np.average(observed) <= 100 + 3
def test_keypoints_dont_change(self):
# keypoints shouldnt be changed
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=base_img.shape)]
aug = iaa.Add(value=1)
aug_det = iaa.Add(value=1).to_deterministic()
observed = aug.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
def test_tuple_as_value(self):
# varying values
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
aug = iaa.Add(value=(0, 10))
aug_det = aug.to_deterministic()
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.7)
assert nb_changed_aug_det == 0
def test_per_channel(self):
# test channelwise
aug = iaa.Add(value=iap.Choice([0, 1]), per_channel=True)
observed = aug.augment_image(np.zeros((1, 1, 100), dtype=np.uint8))
uq = np.unique(observed)
assert observed.shape == (1, 1, 100)
assert 0 in uq
assert 1 in uq
assert len(uq) == 2
def test_per_channel_with_probability(self):
# test channelwise with probability
aug = iaa.Add(value=iap.Choice([0, 1]), per_channel=0.5)
seen = [0, 0]
for _ in sm.xrange(400):
observed = aug.augment_image(np.zeros((1, 1, 20), dtype=np.uint8))
assert observed.shape == (1, 1, 20)
uq = np.unique(observed)
per_channel = (len(uq) == 2)
if per_channel:
seen[0] += 1
else:
seen[1] += 1
assert 150 < seen[0] < 250
assert 150 < seen[1] < 250
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Add(1)
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Add(1)
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_get_parameters(self):
# test get_parameters()
aug = iaa.Add(value=1, per_channel=False)
params = aug.get_parameters()
is_parameter_instance(params[0], iap.Deterministic)
is_parameter_instance(params[1], iap.Deterministic)
assert params[0].value == 1
assert params[1].value == 0
def test_heatmaps(self):
# test heatmaps (not affected by augmenter)
aug = iaa.Add(value=10)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_other_dtypes_bool(self):
image = np.zeros((3, 3), dtype=bool)
aug = iaa.Add(value=1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.Add(value=1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.Add(value=-1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.Add(value=-2)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
def test_other_dtypes_uint_int(self):
for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
image = np.full((3, 3), min_value, dtype=dtype)
aug = iaa.Add(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value + 1)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.Add(11)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value + 21)
image = np.full((3, 3), max_value - 2, dtype=dtype)
aug = iaa.Add(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value - 1)
image = np.full((3, 3), max_value - 1, dtype=dtype)
aug = iaa.Add(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
image = np.full((3, 3), max_value - 1, dtype=dtype)
aug = iaa.Add(2)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.Add(-9)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value + 1)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.Add(-10)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.Add(-11)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value)
for _ in sm.xrange(10):
image = np.full((1, 1, 3), 20, dtype=dtype)
aug = iaa.Add(iap.Uniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) == 1
image = np.full((1, 1, 100), 20, dtype=dtype)
aug = iaa.Add(iap.Uniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
image = np.full((1, 1, 3), 20, dtype=dtype)
aug = iaa.Add(iap.DiscreteUniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) == 1
image = np.full((1, 1, 100), 20, dtype=dtype)
aug = iaa.Add(iap.DiscreteUniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
def test_other_dtypes_float(self):
# float
for dtype in [np.float16, np.float32]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
if dtype == np.float16:
atol = 1e-3 * max_value
else:
atol = 1e-9 * max_value
_allclose = functools.partial(np.allclose, atol=atol, rtol=0)
image = np.full((3, 3), min_value, dtype=dtype)
aug = iaa.Add(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value + 1)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.Add(11)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value + 21)
image = np.full((3, 3), max_value - 2, dtype=dtype)
aug = iaa.Add(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, max_value - 1)
image = np.full((3, 3), max_value - 1, dtype=dtype)
aug = iaa.Add(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, max_value)
image = np.full((3, 3), max_value - 1, dtype=dtype)
aug = iaa.Add(2)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, max_value)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.Add(-9)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value + 1)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.Add(-10)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.Add(-11)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value)
for _ in sm.xrange(10):
image = np.full((50, 1, 3), 0, dtype=dtype)
aug = iaa.Add(iap.Uniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-10 - 1e-2 < image_aug, image_aug < 10 + 1e-2))
assert np.allclose(image_aug[1:, :, 0], image_aug[:-1, :, 0])
assert np.allclose(image_aug[..., 0], image_aug[..., 1])
image = np.full((1, 1, 100), 0, dtype=dtype)
aug = iaa.Add(iap.Uniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-10 - 1e-2 < image_aug, image_aug < 10 + 1e-2))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1])
image = np.full((50, 1, 3), 0, dtype=dtype)
aug = iaa.Add(iap.DiscreteUniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-10 - 1e-2 < image_aug, image_aug < 10 + 1e-2))
assert np.allclose(image_aug[1:, :, 0], image_aug[:-1, :, 0])
assert np.allclose(image_aug[..., 0], image_aug[..., 1])
image = np.full((1, 1, 100), 0, dtype=dtype)
aug = iaa.Add(iap.DiscreteUniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-10 - 1e-2 < image_aug, image_aug < 10 + 1e-2))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1])
def test_pickleable(self):
aug = iaa.Add((0, 50), per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
class TestAddElementwise(unittest.TestCase):
def setUp(self):
reseed()
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_aug = iaa.AddElementwise(value="test")
except Exception:
got_exception = True
assert got_exception
got_exception = False
try:
_aug = iaa.AddElementwise(value=1, per_channel="test")
except Exception:
got_exception = True
assert got_exception
def test_add_zero(self):
# no add, shouldnt change anything
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.AddElementwise(value=0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
def test_add_one(self):
# add > 0
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.AddElementwise(value=1)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images + 1
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = [images_list[0] + 1]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images + 1
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [images_list[0] + 1]
assert array_equal_lists(observed, expected)
def test_add_minus_one(self):
# add < 0
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.AddElementwise(value=-1)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images - 1
assert np.array_equal(observed, expected)
observed = aug.augment_images(images_list)
expected = [images_list[0] - 1]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images - 1
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [images_list[0] - 1]
assert array_equal_lists(observed, expected)
def test_uint8_every_possible_value(self):
# uint8, every possible addition for base value 127
for value_type in [int]:
for per_channel in [False, True]:
for value in np.arange(-255, 255+1):
aug = iaa.AddElementwise(value=value_type(value), per_channel=per_channel)
expected = np.clip(127 + value_type(value), 0, 255)
img = np.full((1, 1), 127, dtype=np.uint8)
img_aug = aug.augment_image(img)
assert img_aug.item(0) == expected
img = np.full((1, 1, 3), 127, dtype=np.uint8)
img_aug = aug.augment_image(img)
assert np.all(img_aug == expected)
def test_stochastic_parameters_as_value(self):
# test other parameters
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
aug = iaa.AddElementwise(value=iap.DiscreteUniform(1, 10))
observed = aug.augment_images(images)
assert np.min(observed) >= 100 + 1
assert np.max(observed) <= 100 + 10
aug = iaa.AddElementwise(value=iap.Uniform(1, 10))
observed = aug.augment_images(images)
assert np.min(observed) >= 100 + 1
assert np.max(observed) <= 100 + 10
aug = iaa.AddElementwise(value=iap.Clip(iap.Normal(1, 1), -3, 3))
observed = aug.augment_images(images)
assert np.min(observed) >= 100 - 3
assert np.max(observed) <= 100 + 3
aug = iaa.AddElementwise(value=iap.Discretize(iap.Clip(iap.Normal(1, 1), -3, 3)))
observed = aug.augment_images(images)
assert np.min(observed) >= 100 - 3
assert np.max(observed) <= 100 + 3
def test_keypoints_dont_change(self):
# keypoints shouldnt be changed
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=base_img.shape)]
aug = iaa.AddElementwise(value=1)
aug_det = iaa.AddElementwise(value=1).to_deterministic()
observed = aug.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
def test_tuple_as_value(self):
# varying values
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
aug = iaa.AddElementwise(value=(0, 10))
aug_det = aug.to_deterministic()
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.7)
assert nb_changed_aug_det == 0
def test_samples_change_by_spatial_location(self):
# values should change between pixels
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
aug = iaa.AddElementwise(value=(-50, 50))
nb_same = 0
nb_different = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_flat = observed_aug.flatten()
last = None
for j in sm.xrange(observed_aug_flat.size):
if last is not None:
v = observed_aug_flat[j]
if v - 0.0001 <= last <= v + 0.0001:
nb_same += 1
else:
nb_different += 1
last = observed_aug_flat[j]
assert nb_different > 0.9 * (nb_different + nb_same)
def test_per_channel(self):
# test channelwise
aug = iaa.AddElementwise(value=iap.Choice([0, 1]), per_channel=True)
observed = aug.augment_image(np.zeros((100, 100, 3), dtype=np.uint8))
sums = np.sum(observed, axis=2)
values = np.unique(sums)
assert all([(value in values) for value in [0, 1, 2, 3]])
def test_per_channel_with_probability(self):
# test channelwise with probability
aug = iaa.AddElementwise(value=iap.Choice([0, 1]), per_channel=0.5)
seen = [0, 0]
for _ in sm.xrange(400):
observed = aug.augment_image(np.zeros((20, 20, 3), dtype=np.uint8))
sums = np.sum(observed, axis=2)
values = np.unique(sums)
all_values_found = all([(value in values) for value in [0, 1, 2, 3]])
if all_values_found:
seen[0] += 1
else:
seen[1] += 1
assert 150 < seen[0] < 250
assert 150 < seen[1] < 250
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.AddElementwise(1)
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.AddElementwise(1)
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_get_parameters(self):
# test get_parameters()
aug = iaa.AddElementwise(value=1, per_channel=False)
params = aug.get_parameters()
is_parameter_instance(params[0], iap.Deterministic)
is_parameter_instance(params[1], iap.Deterministic)
assert params[0].value == 1
assert params[1].value == 0
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.AddElementwise(value=10)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_other_dtypes_bool(self):
# bool
image = np.zeros((3, 3), dtype=bool)
aug = iaa.AddElementwise(value=1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.AddElementwise(value=1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.AddElementwise(value=-1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.AddElementwise(value=-2)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
def test_other_dtypes_uint_int(self):
# uint, int
for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
image = np.full((3, 3), min_value, dtype=dtype)
aug = iaa.AddElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value + 1)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.AddElementwise(11)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value + 21)
image = np.full((3, 3), max_value - 2, dtype=dtype)
aug = iaa.AddElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value - 1)
image = np.full((3, 3), max_value - 1, dtype=dtype)
aug = iaa.AddElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
image = np.full((3, 3), max_value - 1, dtype=dtype)
aug = iaa.AddElementwise(2)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.AddElementwise(-9)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value + 1)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.AddElementwise(-10)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.AddElementwise(-11)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value)
for _ in sm.xrange(10):
image = np.full((5, 5, 3), 20, dtype=dtype)
aug = iaa.AddElementwise(iap.Uniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
assert np.all(image_aug[..., 0] == image_aug[..., 1])
image = np.full((1, 1, 100), 20, dtype=dtype)
aug = iaa.AddElementwise(iap.Uniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
image = np.full((5, 5, 3), 20, dtype=dtype)
aug = iaa.AddElementwise(iap.DiscreteUniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
assert np.all(image_aug[..., 0] == image_aug[..., 1])
image = np.full((1, 1, 100), 20, dtype=dtype)
aug = iaa.AddElementwise(iap.DiscreteUniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
def test_other_dtypes_float(self):
# float
for dtype in [np.float16, np.float32]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
if dtype == np.float16:
atol = 1e-3 * max_value
else:
atol = 1e-9 * max_value
_allclose = functools.partial(np.allclose, atol=atol, rtol=0)
image = np.full((3, 3), min_value, dtype=dtype)
aug = iaa.AddElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value + 1)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.AddElementwise(11)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value + 21)
image = np.full((3, 3), max_value - 2, dtype=dtype)
aug = iaa.AddElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, max_value - 1)
image = np.full((3, 3), max_value - 1, dtype=dtype)
aug = iaa.AddElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, max_value)
image = np.full((3, 3), max_value - 1, dtype=dtype)
aug = iaa.AddElementwise(2)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, max_value)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.AddElementwise(-9)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value + 1)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.AddElementwise(-10)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value)
image = np.full((3, 3), min_value + 10, dtype=dtype)
aug = iaa.AddElementwise(-11)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value)
for _ in sm.xrange(10):
image = np.full((50, 1, 3), 0, dtype=dtype)
aug = iaa.AddElementwise(iap.Uniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-10 - 1e-2 < image_aug, image_aug < 10 + 1e-2))
assert not np.allclose(image_aug[1:, :, 0], image_aug[:-1, :, 0])
assert np.allclose(image_aug[..., 0], image_aug[..., 1])
image = np.full((1, 1, 100), 0, dtype=dtype)
aug = iaa.AddElementwise(iap.Uniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-10 - 1e-2 < image_aug, image_aug < 10 + 1e-2))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1])
image = np.full((50, 1, 3), 0, dtype=dtype)
aug = iaa.AddElementwise(iap.DiscreteUniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-10 - 1e-2 < image_aug, image_aug < 10 + 1e-2))
assert not np.allclose(image_aug[1:, :, 0], image_aug[:-1, :, 0])
assert np.allclose(image_aug[..., 0], image_aug[..., 1])
image = np.full((1, 1, 100), 0, dtype=dtype)
aug = iaa.AddElementwise(iap.DiscreteUniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-10 - 1e-2 < image_aug, image_aug < 10 + 1e-2))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1])
def test_pickleable(self):
aug = iaa.AddElementwise((0, 50), per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=2)
class AdditiveGaussianNoise(unittest.TestCase):
def setUp(self):
reseed()
def test_loc_zero_scale_zero(self):
# no noise, shouldnt change anything
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
images = np.array([base_img])
aug = iaa.AdditiveGaussianNoise(loc=0, scale=0)
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
def test_loc_zero_scale_nonzero(self):
# zero-centered noise
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
images = np.array([base_img])
images_list = [base_img]
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=base_img.shape)]
aug = iaa.AdditiveGaussianNoise(loc=0, scale=0.2 * 255)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
assert not np.array_equal(observed, images)
observed = aug_det.augment_images(images)
assert not np.array_equal(observed, images)
observed = aug.augment_images(images_list)
assert not array_equal_lists(observed, images_list)
observed = aug_det.augment_images(images_list)
assert not array_equal_lists(observed, images_list)
observed = aug.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints)
observed = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints)
def test_std_dev_of_added_noise_matches_scale(self):
# std correct?
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
aug = iaa.AdditiveGaussianNoise(loc=0, scale=0.2 * 255)
images = np.ones((1, 1, 1, 1), dtype=np.uint8) * 128
nb_iterations = 1000
values = []
for i in sm.xrange(nb_iterations):
images_aug = aug.augment_images(images)
values.append(images_aug[0, 0, 0, 0])
values = np.array(values)
assert np.min(values) == 0
assert 0.1 < np.std(values) / 255.0 < 0.4
def test_nonzero_loc(self):
# non-zero loc
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
aug = iaa.AdditiveGaussianNoise(loc=0.25 * 255, scale=0.01 * 255)
images = np.ones((1, 1, 1, 1), dtype=np.uint8) * 128
nb_iterations = 1000
values = []
for i in sm.xrange(nb_iterations):
images_aug = aug.augment_images(images)
values.append(images_aug[0, 0, 0, 0] - 128)
values = np.array(values)
assert 54 < np.average(values) < 74 # loc=0.25 should be around 255*0.25=64 average
def test_tuple_as_loc(self):
# varying locs
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
aug = iaa.AdditiveGaussianNoise(loc=(0, 0.5 * 255), scale=0.0001 * 255)
aug_det = aug.to_deterministic()
images = np.ones((1, 1, 1, 1), dtype=np.uint8) * 128
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.95)
assert nb_changed_aug_det == 0
def test_stochastic_parameter_as_loc(self):
# varying locs by stochastic param
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
aug = iaa.AdditiveGaussianNoise(loc=iap.Choice([-20, 20]), scale=0.0001 * 255)
images = np.ones((1, 1, 1, 1), dtype=np.uint8) * 128
seen = [0, 0]
for i in sm.xrange(200):
observed = aug.augment_images(images)
mean = np.mean(observed)
diff_m20 = abs(mean - (128-20))
diff_p20 = abs(mean - (128+20))
if diff_m20 <= 1:
seen[0] += 1
elif diff_p20 <= 1:
seen[1] += 1
else:
assert False
assert 75 < seen[0] < 125
assert 75 < seen[1] < 125
def test_tuple_as_scale(self):
# varying stds
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
aug = iaa.AdditiveGaussianNoise(loc=0, scale=(0.01 * 255, 0.2 * 255))
aug_det = aug.to_deterministic()
images = np.ones((1, 1, 1, 1), dtype=np.uint8) * 128
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.95)
assert nb_changed_aug_det == 0
def test_stochastic_parameter_as_scale(self):
# varying stds by stochastic param
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
aug = iaa.AdditiveGaussianNoise(loc=0, scale=iap.Choice([1, 20]))
images = np.ones((1, 20, 20, 1), dtype=np.uint8) * 128
seen = [0, 0, 0]
for i in sm.xrange(200):
observed = aug.augment_images(images)
std = np.std(observed.astype(np.int32) - 128)
diff_1 = abs(std - 1)
diff_20 = abs(std - 20)
if diff_1 <= 2:
seen[0] += 1
elif diff_20 <= 5:
seen[1] += 1
else:
seen[2] += 1
assert seen[2] <= 5
assert 75 < seen[0] < 125
assert 75 < seen[1] < 125
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_ = iaa.AdditiveGaussianNoise(loc="test")
except Exception:
got_exception = True
assert got_exception
got_exception = False
try:
_ = iaa.AdditiveGaussianNoise(scale="test")
except Exception:
got_exception = True
assert got_exception
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 128
aug = iaa.AdditiveGaussianNoise(loc=0.5, scale=10)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_pickleable(self):
aug = iaa.AdditiveGaussianNoise(scale=(0.1, 10), per_channel=True,
seed=1)
runtest_pickleable_uint8_img(aug, iterations=2)
class TestCutout(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
aug = iaa.Cutout()
assert aug.nb_iterations.value == 1
assert is_parameter_instance(aug.position[0], iap.Uniform)
assert is_parameter_instance(aug.position[1], iap.Uniform)
assert np.isclose(aug.size.value, 0.2)
assert aug.squared.value == 1
assert aug.fill_mode.value == "constant"
assert aug.cval.value == 128
assert aug.fill_per_channel.value == 0
def test___init___custom(self):
aug = iaa.Cutout(
nb_iterations=1,
position=(0.5, 0.5),
size=0.1,
squared=0.6,
fill_mode=["gaussian", "constant"],
cval=(0, 255),
fill_per_channel=0.5
)
assert aug.nb_iterations.value == 1
assert np.isclose(aug.position[0].value, 0.5)
assert np.isclose(aug.position[1].value, 0.5)
assert np.isclose(aug.size.value, 0.1)
assert np.isclose(aug.squared.p.value, 0.6)
assert aug.fill_mode.a == ["gaussian", "constant"]
assert np.isclose(aug.cval.a.value, 0)
assert np.isclose(aug.cval.b.value, 255)
assert np.isclose(aug.fill_per_channel.p.value, 0.5)
def test___init___fill_mode_is_stochastic_param(self):
param = iap.Deterministic("constant")
aug = iaa.Cutout(fill_mode=param)
assert aug.fill_mode is param
@mock.patch("imgaug.augmenters.arithmetic.cutout_")
def test_mocked__squared_false(self, mock_apply):
aug = iaa.Cutout(nb_iterations=2,
position=(0.5, 0.6),
size=iap.DeterministicList([0.1, 0.2]),
squared=False,
fill_mode="gaussian",
cval=1,
fill_per_channel=True)
image = np.zeros((10, 30, 3), dtype=np.uint8)
# dont return image itself, otherwise the loop below will fail
# at its second iteration as the method is expected to handle
# internally a copy of the image and not the image itself
mock_apply.return_value = np.copy(image)
_ = aug(image=image)
assert mock_apply.call_count == 2
for call_idx in np.arange(2):
args = mock_apply.call_args_list[call_idx][0]
kwargs = mock_apply.call_args_list[call_idx][1]
assert args[0] is not image
assert np.array_equal(args[0], image)
assert np.isclose(kwargs["x1"], 0.5*30 - 0.5 * (0.2*30))
assert np.isclose(kwargs["y1"], 0.6*10 - 0.5 * (0.1*10))
assert np.isclose(kwargs["x2"], 0.5*30 + 0.5 * (0.2*30))
assert np.isclose(kwargs["y2"], 0.6*10 + 0.5 * (0.1*10))
assert kwargs["fill_mode"] == "gaussian"
assert np.array_equal(kwargs["cval"], [1, 1, 1])
assert np.isclose(kwargs["fill_per_channel"], 1.0)
assert isinstance(kwargs["seed"], iarandom.RNG)
@mock.patch("imgaug.augmenters.arithmetic.cutout_")
def test_mocked__squared_true(self, mock_apply):
aug = iaa.Cutout(nb_iterations=2,
position=(0.5, 0.6),
size=iap.DeterministicList([0.1, 0.2]),
squared=True,
fill_mode="gaussian",
cval=1,
fill_per_channel=True)
image = np.zeros((10, 30, 3), dtype=np.uint8)
# dont return image itself, otherwise the loop below will fail
# at its second iteration as the method is expected to handle
# internally a copy of the image and not the image itself
mock_apply.return_value = np.copy(image)
_ = aug(image=image)
assert mock_apply.call_count == 2
for call_idx in np.arange(2):
args = mock_apply.call_args_list[call_idx][0]
kwargs = mock_apply.call_args_list[call_idx][1]
assert args[0] is not image
assert np.array_equal(args[0], image)
assert np.isclose(kwargs["x1"], 0.5*30 - 0.5 * (0.1*10))
assert np.isclose(kwargs["y1"], 0.6*10 - 0.5 * (0.1*10))
assert np.isclose(kwargs["x2"], 0.5*30 + 0.5 * (0.1*10))
assert np.isclose(kwargs["y2"], 0.6*10 + 0.5 * (0.1*10))
assert kwargs["fill_mode"] == "gaussian"
assert np.array_equal(kwargs["cval"], [1, 1, 1])
assert np.isclose(kwargs["fill_per_channel"], 1.0)
assert isinstance(kwargs["seed"], iarandom.RNG)
def test_simple_image(self):
aug = iaa.Cutout(nb_iterations=2,
position=(
iap.DeterministicList([0.2, 0.8]),
iap.DeterministicList([0.2, 0.8])
),
size=0.2,
fill_mode="constant",
cval=iap.DeterministicList([0, 0, 0, 1, 1, 1]))
image = np.full((100, 100, 3), 255, dtype=np.uint8)
for _ in np.arange(3):
images_aug = aug(images=[image, image])
for image_aug in images_aug:
values = np.unique(image_aug)
assert len(values) == 3
assert 0 in values
assert 1 in values
assert 255 in values
def test_batch_contains_only_non_image_data(self):
aug = iaa.Cutout()
segmap_arr = np.ones((3, 3, 1), dtype=np.int32)
segmap = ia.SegmentationMapsOnImage(segmap_arr, shape=(3, 3, 3))
segmap_aug = aug.augment_segmentation_maps(segmap)
assert np.array_equal(segmap.get_arr(), segmap_aug.get_arr())
def test_sampling_when_position_is_stochastic_parameter(self):
# sampling of position works slightly differently when it is a single
# parameter instead of tuple (paramX, paramY), so we have an extra
# test for that situation here
param = iap.DeterministicList([0.5, 0.6])
aug = iaa.Cutout(position=param)
samples = aug._draw_samples([
np.zeros((3, 3, 3), dtype=np.uint8),
np.zeros((3, 3, 3), dtype=np.uint8)
], iarandom.RNG(0))
assert np.allclose(samples.pos_x, [0.5, 0.5])
assert np.allclose(samples.pos_y, [0.6, 0.6])
def test_by_comparison_to_official_implementation(self):
image = np.ones((10, 8, 2), dtype=np.uint8)
aug = iaa.Cutout(1, position="uniform", size=0.2, squared=True,
cval=0)
aug_official = _CutoutOfficial(n_holes=1, length=int(10*0.2))
dropped = np.zeros((10, 8, 2), dtype=np.int32)
dropped_official = np.copy(dropped)
height = np.zeros((10, 8, 2), dtype=np.int32)
width = np.copy(height)
height_official = np.copy(height)
width_official = np.copy(width)
nb_iterations = 3 * 1000
images_aug = aug(images=[image] * nb_iterations)
for image_aug in images_aug:
image_aug_off = aug_official(image)
mask = (image_aug == 0)
mask_off = (image_aug_off == 0)
dropped += mask
dropped_official += mask_off
ydrop = np.max(mask, axis=(2, 1))
xdrop = np.max(mask, axis=(2, 0))
wx = np.where(xdrop)
wy = np.where(ydrop)
x1 = wx[0][0]
x2 = wx[0][-1]
y1 = wy[0][0]
y2 = wy[0][-1]
ydrop_off = np.max(mask_off, axis=(2, 1))
xdrop_off = np.max(mask_off, axis=(2, 0))
wx_off = np.where(xdrop_off)
wy_off = np.where(ydrop_off)
x1_off = wx_off[0][0]
x2_off = wx_off[0][-1]
y1_off = wy_off[0][0]
y2_off = wy_off[0][-1]
height += (
np.full(height.shape, 1 + (y2 - y1), dtype=np.int32)
* mask)
width += (
np.full(width.shape, 1 + (x2 - x1), dtype=np.int32)
* mask)
height_official += (
np.full(height_official.shape, 1 + (y2_off - y1_off),
dtype=np.int32)
* mask_off)
width_official += (
np.full(width_official.shape, 1 + (x2_off - x1_off),
dtype=np.int32)
* mask_off)
dropped_prob = dropped / nb_iterations
dropped_prob_off = dropped_official / nb_iterations
height_avg = height / (dropped + 1e-4)
height_avg_off = height_official / (dropped_official + 1e-4)
width_avg = width / (dropped + 1e-4)
width_avg_off = width_official / (dropped_official + 1e-4)
prob_max_diff = np.max(np.abs(dropped_prob - dropped_prob_off))
height_avg_max_diff = np.max(np.abs(height_avg - height_avg_off))
width_avg_max_diff = np.max(np.abs(width_avg - width_avg_off))
assert prob_max_diff < 0.04
assert height_avg_max_diff < 0.3
assert width_avg_max_diff < 0.3
def test_determinism(self):
aug = iaa.Cutout(nb_iterations=(1, 3),
size=(0.1, 0.2),
fill_mode=["gaussian", "constant"],
cval=(0, 255))
image = np.mod(
np.arange(100*100*3), 256
).reshape((100, 100, 3)).astype(np.uint8)
sums = []
for _ in np.arange(10):
aug_det = aug.to_deterministic()
image_aug1 = aug_det(image=image)
image_aug2 = aug_det(image=image)
assert np.array_equal(image_aug1, image_aug2)
sums.append(np.sum(image_aug1))
assert len(np.unique(sums)) > 1
def test_get_parameters(self):
aug = iaa.Cutout(
nb_iterations=1,
position=(0.5, 0.5),
size=0.1,
squared=0.6,
fill_mode=["gaussian", "constant"],
cval=(0, 255),
fill_per_channel=0.5
)
params = aug.get_parameters()
assert params[0] is aug.nb_iterations
assert params[1] is aug.position
assert params[2] is aug.size
assert params[3] is aug.squared
assert params[4] is aug.fill_mode
assert params[5] is aug.cval
assert params[6] is aug.fill_per_channel
def test_pickleable(self):
aug = iaa.Cutout(
nb_iterations=1,
position=(0.5, 0.5),
size=0.1,
squared=0.6,
fill_mode=["gaussian", "constant"],
cval=(0, 255),
fill_per_channel=0.5
)
runtest_pickleable_uint8_img(aug)
# this is mostly copy-pasted cutout code from
# https://github.com/uoguelph-mlrg/Cutout/blob/master/util/cutout.py
# we use this to compare our implementation against
# we changed some pytorch to numpy stuff
class _CutoutOfficial(object):
"""Randomly mask out one or more patches from an image.
Args:
n_holes (int): Number of patches to cut out of each image.
length (int): The length (in pixels) of each square patch.
"""
def __init__(self, n_holes, length):
self.n_holes = n_holes
self.length = length
def __call__(self, img):
"""
Args:
img (Tensor): Tensor image of size (C, H, W).
Returns:
Tensor: Image with n_holes of dimension length x length cut out of
it.
"""
# h = img.size(1)
# w = img.size(2)
h = img.shape[0]
w = img.shape[1]
mask = np.ones((h, w), np.float32)
for n in range(self.n_holes):
y = np.random.randint(h)
x = np.random.randint(w)
y1 = np.clip(y - self.length // 2, 0, h)
y2 = np.clip(y + self.length // 2, 0, h)
x1 = np.clip(x - self.length // 2, 0, w)
x2 = np.clip(x + self.length // 2, 0, w)
# note that in the paper they normalize to 0-mean,
# i.e. 0 here is actually not black but grayish pixels
mask[y1: y2, x1: x2] = 0
# mask = torch.from_numpy(mask)
# mask = mask.expand_as(img)
if img.ndim != 2:
mask = np.tile(mask[:, :, np.newaxis], (1, 1, img.shape[-1]))
img = img * mask
return img
class TestDropout(unittest.TestCase):
def setUp(self):
reseed()
def test_p_is_zero(self):
# no dropout, shouldnt change anything
base_img = np.ones((512, 512, 1), dtype=np.uint8) * 255
images = np.array([base_img])
images_list = [base_img]
aug = iaa.Dropout(p=0)
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
observed = aug.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
# 100% dropout, should drop everything
aug = iaa.Dropout(p=1.0)
observed = aug.augment_images(images)
expected = np.zeros((1, 512, 512, 1), dtype=np.uint8)
assert np.array_equal(observed, expected)
observed = aug.augment_images(images_list)
expected = [np.zeros((512, 512, 1), dtype=np.uint8)]
assert array_equal_lists(observed, expected)
def test_p_is_50_percent(self):
# 50% dropout
base_img = np.ones((512, 512, 1), dtype=np.uint8) * 255
images = np.array([base_img])
images_list = [base_img]
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=base_img.shape)]
aug = iaa.Dropout(p=0.5)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
assert not np.array_equal(observed, images)
percent_nonzero = len(observed.flatten().nonzero()[0]) \
/ (base_img.shape[0] * base_img.shape[1] * base_img.shape[2])
assert 0.35 <= (1 - percent_nonzero) <= 0.65
observed = aug_det.augment_images(images)
assert not np.array_equal(observed, images)
percent_nonzero = len(observed.flatten().nonzero()[0]) \
/ (base_img.shape[0] * base_img.shape[1] * base_img.shape[2])
assert 0.35 <= (1 - percent_nonzero) <= 0.65
observed = aug.augment_images(images_list)
assert not array_equal_lists(observed, images_list)
percent_nonzero = len(observed[0].flatten().nonzero()[0]) \
/ (base_img.shape[0] * base_img.shape[1] * base_img.shape[2])
assert 0.35 <= (1 - percent_nonzero) <= 0.65
observed = aug_det.augment_images(images_list)
assert not array_equal_lists(observed, images_list)
percent_nonzero = len(observed[0].flatten().nonzero()[0]) \
/ (base_img.shape[0] * base_img.shape[1] * base_img.shape[2])
assert 0.35 <= (1 - percent_nonzero) <= 0.65
observed = aug.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints)
observed = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints)
def test_tuple_as_p(self):
# varying p
aug = iaa.Dropout(p=(0.0, 1.0))
aug_det = aug.to_deterministic()
images = np.ones((1, 8, 8, 1), dtype=np.uint8) * 255
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.95)
assert nb_changed_aug_det == 0
def test_list_as_p(self):
aug = iaa.Dropout(p=[0.0, 0.5, 1.0])
images = np.ones((1, 20, 20, 1), dtype=np.uint8) * 255
nb_seen = [0, 0, 0, 0]
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
n_dropped = np.sum(observed_aug == 0)
p_observed = n_dropped / observed_aug.size
if 0 <= p_observed <= 0.01:
nb_seen[0] += 1
elif 0.5 - 0.05 <= p_observed <= 0.5 + 0.05:
nb_seen[1] += 1
elif 1.0-0.01 <= p_observed <= 1.0:
nb_seen[2] += 1
else:
nb_seen[3] += 1
assert np.allclose(nb_seen[0:3], nb_iterations*0.33, rtol=0, atol=75)
assert nb_seen[3] < 30
def test_stochastic_parameter_as_p(self):
# varying p by stochastic parameter
aug = iaa.Dropout(p=iap.Binomial(1-iap.Choice([0.0, 0.5])))
images = np.ones((1, 20, 20, 1), dtype=np.uint8) * 255
seen = [0, 0, 0]
for i in sm.xrange(400):
observed = aug.augment_images(images)
p = np.mean(observed == 0)
if 0.4 < p < 0.6:
seen[0] += 1
elif p < 0.1:
seen[1] += 1
else:
seen[2] += 1
assert seen[2] <= 10
assert 150 < seen[0] < 250
assert 150 < seen[1] < 250
def test___init___bad_datatypes(self):
# test exception for wrong parameter datatype
got_exception = False
try:
_aug = iaa.Dropout(p="test")
except Exception:
got_exception = True
assert got_exception
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.Dropout(p=1.0)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_pickleable(self):
aug = iaa.Dropout(p=0.5, per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestCoarseDropout(unittest.TestCase):
def setUp(self):
reseed()
def test_p_is_zero(self):
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 100
aug = iaa.CoarseDropout(p=0, size_px=4, size_percent=None, per_channel=False, min_size=4)
observed = aug.augment_image(base_img)
expected = base_img
assert np.array_equal(observed, expected)
def test_p_is_one(self):
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 100
aug = iaa.CoarseDropout(p=1.0, size_px=4, size_percent=None, per_channel=False, min_size=4)
observed = aug.augment_image(base_img)
expected = np.zeros_like(base_img)
assert np.array_equal(observed, expected)
def test_p_is_50_percent(self):
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 100
aug = iaa.CoarseDropout(p=0.5, size_px=1, size_percent=None, per_channel=False, min_size=1)
averages = []
for _ in sm.xrange(50):
observed = aug.augment_image(base_img)
averages.append(np.average(observed))
assert all([v in [0, 100] for v in averages])
assert 50 - 20 < np.average(averages) < 50 + 20
def test_size_percent(self):
base_img = np.ones((16, 16, 1), dtype=np.uint8) * 100
aug = iaa.CoarseDropout(p=0.5, size_px=None, size_percent=0.001, per_channel=False, min_size=1)
averages = []
for _ in sm.xrange(50):
observed = aug.augment_image(base_img)
averages.append(np.average(observed))
assert all([v in [0, 100] for v in averages])
assert 50 - 20 < np.average(averages) < 50 + 20
def test_per_channel(self):
aug = iaa.CoarseDropout(p=0.5, size_px=1, size_percent=None, per_channel=True, min_size=1)
base_img = np.ones((4, 4, 3), dtype=np.uint8) * 100
found = False
for _ in sm.xrange(100):
observed = aug.augment_image(base_img)
avgs = np.average(observed, axis=(0, 1))
if len(set(avgs)) >= 2:
found = True
break
assert found
def test_stochastic_parameter_as_p(self):
# varying p by stochastic parameter
aug = iaa.CoarseDropout(p=iap.Binomial(1-iap.Choice([0.0, 0.5])), size_px=50)
images = np.ones((1, 100, 100, 1), dtype=np.uint8) * 255
seen = [0, 0, 0]
for i in sm.xrange(400):
observed = aug.augment_images(images)
p = np.mean(observed == 0)
if 0.4 < p < 0.6:
seen[0] += 1
elif p < 0.1:
seen[1] += 1
else:
seen[2] += 1
assert seen[2] <= 10
assert 150 < seen[0] < 250
assert 150 < seen[1] < 250
def test___init___bad_datatypes(self):
# test exception for bad parameters
got_exception = False
try:
_ = iaa.CoarseDropout(p="test")
except Exception:
got_exception = True
assert got_exception
def test___init___size_px_and_size_percent_both_none(self):
aug = iaa.CoarseDropout(p=0.5, size_px=None, size_percent=None)
assert np.isclose(aug.mul.size_px.a.value, 3)
assert np.isclose(aug.mul.size_px.b.value, 8)
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.CoarseDropout(p=1.0, size_px=2)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_pickleable(self):
aug = iaa.CoarseDropout(p=0.5, size_px=10, per_channel=True,
seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(40, 40, 3))
class TestDropout2d(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
aug = iaa.Dropout2d()
assert is_parameter_instance(aug.p, iap.Binomial)
assert np.isclose(aug.p.p.value, 1-0.1)
assert aug.nb_keep_channels == 1
def test___init___p_is_float(self):
aug = iaa.Dropout2d(p=0.7)
assert is_parameter_instance(aug.p, iap.Binomial)
assert np.isclose(aug.p.p.value, 0.3)
assert aug.nb_keep_channels == 1
def test___init___nb_keep_channels_is_int(self):
aug = iaa.Dropout2d(p=0, nb_keep_channels=2)
assert is_parameter_instance(aug.p, iap.Binomial)
assert np.isclose(aug.p.p.value, 1.0)
assert aug.nb_keep_channels == 2
def test_no_images_in_batch(self):
aug = iaa.Dropout2d(p=0.0, nb_keep_channels=0)
heatmaps = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
heatmaps = ia.HeatmapsOnImage(heatmaps, shape=(2, 2, 3))
heatmaps_aug = aug(heatmaps=heatmaps)
assert np.allclose(heatmaps_aug.arr_0to1, heatmaps.arr_0to1)
def test_p_is_1(self):
image = np.full((1, 2, 3), 255, dtype=np.uint8)
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=0)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
assert image_aug.dtype.name == image.dtype.name
assert np.sum(image_aug) == 0
def test_p_is_1_heatmaps(self):
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=0)
arr = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
hm = ia.HeatmapsOnImage(arr, shape=(2, 2, 3))
heatmaps_aug = aug(heatmaps=hm)
assert np.allclose(heatmaps_aug.arr_0to1, 0.0)
def test_p_is_1_segmentation_maps(self):
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=0)
arr = np.int32([
[0, 1],
[0, 1]
])
segmaps = ia.SegmentationMapsOnImage(arr, shape=(2, 2, 3))
segmaps_aug = aug(segmentation_maps=segmaps)
assert np.allclose(segmaps_aug.arr, 0.0)
def test_p_is_1_cbaois(self):
cbaois = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(2, 2, 3)),
ia.BoundingBoxesOnImage([ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)],
shape=(2, 2, 3)),
ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(2, 2, 3)),
ia.LineStringsOnImage([ia.LineString([(0, 0), (1, 0)])],
shape=(2, 2, 3))
]
cbaoi_names = ["keypoints", "bounding_boxes", "polygons",
"line_strings"]
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=0)
for name, cbaoi in zip(cbaoi_names, cbaois):
with self.subTest(datatype=name):
cbaoi_aug = aug(**{name: cbaoi})
assert cbaoi_aug.shape == (2, 2, 3)
assert cbaoi_aug.items == []
def test_p_is_1_heatmaps__keep_one_channel(self):
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=1)
arr = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
hm = ia.HeatmapsOnImage(arr, shape=(2, 2, 3))
heatmaps_aug = aug(heatmaps=hm)
assert np.allclose(heatmaps_aug.arr_0to1, hm.arr_0to1)
def test_p_is_1_segmentation_maps__keep_one_channel(self):
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=1)
arr = np.int32([
[0, 1],
[0, 1]
])
segmaps = ia.SegmentationMapsOnImage(arr, shape=(2, 2, 3))
segmaps_aug = aug(segmentation_maps=segmaps)
assert np.allclose(segmaps_aug.arr, segmaps.arr)
def test_p_is_1_cbaois__keep_one_channel(self):
cbaois = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(2, 2, 3)),
ia.BoundingBoxesOnImage([ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)],
shape=(2, 2, 3)),
ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(2, 2, 3)),
ia.LineStringsOnImage([ia.LineString([(0, 0), (1, 0)])],
shape=(2, 2, 3))
]
cbaoi_names = ["keypoints", "bounding_boxes", "polygons",
"line_strings"]
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=1)
for name, cbaoi in zip(cbaoi_names, cbaois):
with self.subTest(datatype=name):
cbaoi_aug = aug(**{name: cbaoi})
assert cbaoi_aug.shape == (2, 2, 3)
assert np.allclose(
cbaoi_aug.items[0].coords,
cbaoi.items[0].coords
)
def test_p_is_0(self):
image = np.full((1, 2, 3), 255, dtype=np.uint8)
aug = iaa.Dropout2d(p=0.0, nb_keep_channels=0)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
assert image_aug.dtype.name == image.dtype.name
assert np.array_equal(image_aug, image)
def test_p_is_0_heatmaps(self):
aug = iaa.Dropout2d(p=0.0, nb_keep_channels=0)
arr = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
hm = ia.HeatmapsOnImage(arr, shape=(2, 2, 3))
heatmaps_aug = aug(heatmaps=hm)
assert np.allclose(heatmaps_aug.arr_0to1, hm.arr_0to1)
def test_p_is_0_segmentation_maps(self):
aug = iaa.Dropout2d(p=0.0, nb_keep_channels=0)
arr = np.int32([
[0, 1],
[0, 1]
])
segmaps = ia.SegmentationMapsOnImage(arr, shape=(2, 2, 3))
segmaps_aug = aug(segmentation_maps=segmaps)
assert np.allclose(segmaps_aug.arr, segmaps.arr)
def test_p_is_0_cbaois(self):
cbaois = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(2, 2, 3)),
ia.BoundingBoxesOnImage([ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)],
shape=(2, 2, 3)),
ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(2, 2, 3)),
ia.LineStringsOnImage([ia.LineString([(0, 0), (1, 0)])],
shape=(2, 2, 3))
]
cbaoi_names = ["keypoints", "bounding_boxes", "polygons",
"line_strings"]
aug = iaa.Dropout2d(p=0.0, nb_keep_channels=0)
for name, cbaoi in zip(cbaoi_names, cbaois):
with self.subTest(datatype=name):
cbaoi_aug = aug(**{name: cbaoi})
assert cbaoi_aug.shape == (2, 2, 3)
assert np.allclose(
cbaoi_aug.items[0].coords,
cbaoi.items[0].coords
)
def test_p_is_075(self):
image = np.full((1, 1, 3000), 255, dtype=np.uint8)
aug = iaa.Dropout2d(p=0.75, nb_keep_channels=0)
image_aug = aug(image=image)
nb_kept = np.sum(image_aug == 255)
nb_dropped = image.shape[2] - nb_kept
assert image_aug.shape == image.shape
assert image_aug.dtype.name == image.dtype.name
assert np.isclose(nb_dropped, image.shape[2]*0.75, atol=75)
def test_force_nb_keep_channels(self):
image = np.full((1, 1, 3), 255, dtype=np.uint8)
images = np.array([image] * 1000)
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=1)
images_aug = aug(images=images)
ids_kept = [np.nonzero(image[0, 0, :]) for image in images_aug]
ids_kept_uq = np.unique(ids_kept)
nb_kept = np.sum(images_aug == 255)
nb_dropped = (len(images) * images.shape[3]) - nb_kept
assert images_aug.shape == images.shape
assert images_aug.dtype.name == images.dtype.name
# on average, keep 1 of 3 channels
# due to p=1.0 we expect to get exactly 2/3 dropped
assert np.isclose(nb_dropped,
(len(images)*images.shape[3])*(2/3), atol=1)
# every channel dropped at least once, i.e. which one is kept is random
assert sorted(ids_kept_uq.tolist()) == [0, 1, 2]
def test_some_images_below_nb_keep_channels(self):
image_2c = np.full((1, 1, 2), 255, dtype=np.uint8)
image_3c = np.full((1, 1, 3), 255, dtype=np.uint8)
images = [image_2c if i % 2 == 0 else image_3c
for i in sm.xrange(100)]
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=2)
images_aug = aug(images=images)
for i, image_aug in enumerate(images_aug):
assert np.sum(image_aug == 255) == 2
if i % 2 == 0:
assert np.sum(image_aug == 0) == 0
else:
assert np.sum(image_aug == 0) == 1
def test_all_images_below_nb_keep_channels(self):
image = np.full((1, 1, 2), 255, dtype=np.uint8)
images = np.array([image] * 100)
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=3)
images_aug = aug(images=images)
nb_kept = np.sum(images_aug == 255)
nb_dropped = (len(images) * images.shape[3]) - nb_kept
assert nb_dropped == 0
def test_get_parameters(self):
aug = iaa.Dropout2d(p=0.7, nb_keep_channels=2)
params = aug.get_parameters()
is_parameter_instance(params[0], iap.Binomial)
assert np.isclose(params[0].p.value, 0.3)
assert params[1] == 2
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 255, dtype=np.uint8)
aug = iaa.Dropout2d(1.0, nb_keep_channels=0)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_other_dtypes_bool(self):
image = np.full((1, 1, 10), 1, dtype=bool)
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=3)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
assert image_aug.dtype.name == "bool"
assert np.sum(image_aug == 1) == 3
assert np.sum(image_aug == 0) == 7
def test_other_dtypes_uint_int(self):
dts = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dt in dts:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
values = [min_value, int(center_value), max_value]
for value in values:
with self.subTest(dtype=dt, value=value):
image = np.full((1, 1, 10), value, dtype=dt)
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=3)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
assert image_aug.dtype.name == dt
if value == 0:
assert np.sum(image_aug == value) == 10
else:
assert np.sum(image_aug == value) == 3
assert np.sum(image_aug == 0) == 7
def test_other_dtypes_float(self):
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dt in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
values = [min_value, -10.0, center_value, 10.0, max_value]
atol = 1e-3*max_value if dt == "float16" else 1e-9 * max_value
_isclose = functools.partial(np.isclose, atol=atol, rtol=0)
for value in values:
with self.subTest(dtype=dt, value=value):
image = np.full((1, 1, 10), value, dtype=dt)
aug = iaa.Dropout2d(p=1.0, nb_keep_channels=3)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
assert image_aug.dtype.name == dt
if _isclose(value, 0.0):
assert np.sum(_isclose(image_aug, value)) == 10
else:
assert (
np.sum(_isclose(image_aug, high_res_dt(value)))
== 3)
assert np.sum(image_aug == 0) == 7
def test_pickleable(self):
aug = iaa.Dropout2d(p=0.5, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(1, 1, 50))
class TestTotalDropout(unittest.TestCase):
def setUp(self):
reseed()
def test___init___p(self):
aug = iaa.TotalDropout(p=0)
assert is_parameter_instance(aug.p, iap.Binomial)
assert np.isclose(aug.p.p.value, 1.0)
def test_p_is_1(self):
image = np.full((1, 2, 3), 255, dtype=np.uint8)
aug = iaa.TotalDropout(p=1.0)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
assert image_aug.dtype.name == image.dtype.name
assert np.sum(image_aug) == 0
def test_p_is_1_multiple_images_list(self):
image = np.full((1, 2, 3), 255, dtype=np.uint8)
images = [image, image, image]
aug = iaa.TotalDropout(p=1.0)
images_aug = aug(images=images)
for image_aug, image_ in zip(images_aug, images):
assert image_aug.shape == image_.shape
assert image_aug.dtype.name == image_.dtype.name
assert np.sum(image_aug) == 0
def test_p_is_1_multiple_images_array(self):
image = np.full((1, 2, 3), 255, dtype=np.uint8)
images = np.array([image, image, image], dtype=np.uint8)
aug = iaa.TotalDropout(p=1.0)
images_aug = aug(images=images)
assert images_aug.shape == images.shape
assert images_aug.dtype.name == images.dtype.name
assert np.sum(images_aug) == 0
def test_p_is_1_heatmaps(self):
aug = iaa.TotalDropout(p=1.0)
arr = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
hm = ia.HeatmapsOnImage(arr, shape=(2, 2, 3))
heatmaps_aug = aug(heatmaps=hm)
assert np.allclose(heatmaps_aug.arr_0to1, 0.0)
def test_p_is_1_segmentation_maps(self):
aug = iaa.TotalDropout(p=1.0)
arr = np.int32([
[0, 1],
[0, 1]
])
segmaps = ia.SegmentationMapsOnImage(arr, shape=(2, 2, 3))
segmaps_aug = aug(segmentation_maps=segmaps)
assert np.allclose(segmaps_aug.arr, 0.0)
def test_p_is_1_cbaois(self):
cbaois = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(2, 2, 3)),
ia.BoundingBoxesOnImage([ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)],
shape=(2, 2, 3)),
ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(2, 2, 3)),
ia.LineStringsOnImage([ia.LineString([(0, 0), (1, 0)])],
shape=(2, 2, 3))
]
cbaoi_names = ["keypoints", "bounding_boxes", "polygons",
"line_strings"]
aug = iaa.TotalDropout(p=1.0)
for name, cbaoi in zip(cbaoi_names, cbaois):
with self.subTest(datatype=name):
cbaoi_aug = aug(**{name: cbaoi})
assert cbaoi_aug.shape == (2, 2, 3)
assert cbaoi_aug.items == []
def test_p_is_0(self):
image = np.full((1, 2, 3), 255, dtype=np.uint8)
aug = iaa.TotalDropout(p=0.0)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
assert image_aug.dtype.name == image.dtype.name
assert np.array_equal(image_aug, image)
def test_p_is_0_multiple_images_list(self):
image = np.full((1, 2, 3), 255, dtype=np.uint8)
images = [image, image, image]
aug = iaa.TotalDropout(p=0.0)
images_aug = aug(images=images)
for image_aug, image_ in zip(images_aug, images):
assert image_aug.shape == image_.shape
assert image_aug.dtype.name == image_.dtype.name
assert np.array_equal(image_aug, image_)
def test_p_is_0_multiple_images_array(self):
image = np.full((1, 2, 3), 255, dtype=np.uint8)
images = np.array([image, image, image], dtype=np.uint8)
aug = iaa.TotalDropout(p=0.0)
images_aug = aug(images=images)
for image_aug, image_ in zip(images_aug, images):
assert image_aug.shape == image_.shape
assert image_aug.dtype.name == image_.dtype.name
assert np.array_equal(image_aug, image_)
def test_p_is_0_heatmaps(self):
aug = iaa.TotalDropout(p=0.0)
arr = np.float32([
[0.0, 1.0],
[0.0, 1.0]
])
hm = ia.HeatmapsOnImage(arr, shape=(2, 2, 3))
heatmaps_aug = aug(heatmaps=hm)
assert np.allclose(heatmaps_aug.arr_0to1, hm.arr_0to1)
def test_p_is_0_segmentation_maps(self):
aug = iaa.TotalDropout(p=0.0)
arr = np.int32([
[0, 1],
[0, 1]
])
segmaps = ia.SegmentationMapsOnImage(arr, shape=(2, 2, 3))
segmaps_aug = aug(segmentation_maps=segmaps)
assert np.allclose(segmaps_aug.arr, segmaps.arr)
def test_p_is_0_cbaois(self):
cbaois = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(2, 2, 3)),
ia.BoundingBoxesOnImage([ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)],
shape=(2, 2, 3)),
ia.PolygonsOnImage([ia.Polygon([(0, 0), (1, 0), (1, 1)])],
shape=(2, 2, 3)),
ia.LineStringsOnImage([ia.LineString([(0, 0), (1, 0)])],
shape=(2, 2, 3))
]
cbaoi_names = ["keypoints", "bounding_boxes", "polygons",
"line_strings"]
aug = iaa.TotalDropout(p=0.0)
for name, cbaoi in zip(cbaoi_names, cbaois):
with self.subTest(datatype=name):
cbaoi_aug = aug(**{name: cbaoi})
assert cbaoi_aug.shape == (2, 2, 3)
assert np.allclose(
cbaoi_aug.items[0].coords,
cbaoi.items[0].coords
)
def test_p_is_075_multiple_images_list(self):
images = [np.full((1, 1, 1), 255, dtype=np.uint8)] * 3000
aug = iaa.TotalDropout(p=0.75)
images_aug = aug(images=images)
nb_kept = np.sum([np.sum(image_aug == 255) for image_aug in images_aug])
nb_dropped = len(images) - nb_kept
for image_aug in images_aug:
assert image_aug.shape == images[0].shape
assert image_aug.dtype.name == images[0].dtype.name
assert np.isclose(nb_dropped, len(images)*0.75, atol=75)
def test_p_is_075_multiple_images_array(self):
images = np.full((3000, 1, 1, 1), 255, dtype=np.uint8)
aug = iaa.TotalDropout(p=0.75)
images_aug = aug(images=images)
nb_kept = np.sum(images_aug == 255)
nb_dropped = len(images) - nb_kept
assert images_aug.shape == images.shape
assert images_aug.dtype.name == images.dtype.name
assert np.isclose(nb_dropped, len(images)*0.75, atol=75)
def test_get_parameters(self):
aug = iaa.TotalDropout(p=0.0)
params = aug.get_parameters()
assert params[0] is aug.p
def test_unusual_channel_numbers(self):
shapes = [
(5, 1, 1, 4),
(5, 1, 1, 5),
(5, 1, 1, 512),
(5, 1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
images = np.zeros(shape, dtype=np.uint8)
aug = iaa.TotalDropout(1.0)
images_aug = aug(images=images)
assert np.all(images_aug == 0)
assert images_aug.dtype.name == "uint8"
assert images_aug.shape == shape
def test_zero_sized_axes(self):
with assertWarns(self, iaa.SuspiciousMultiImageShapeWarning):
shapes = [
(5, 0, 0),
(5, 0, 1),
(5, 1, 0),
(5, 0, 1, 0),
(5, 1, 0, 0),
(5, 0, 1, 1),
(5, 1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
images = np.full(shape, 255, dtype=np.uint8)
aug = iaa.TotalDropout(1.0)
images_aug = aug(images=images)
assert images_aug.dtype.name == "uint8"
assert images_aug.shape == images.shape
def test_other_dtypes_bool(self):
image = np.full((1, 1, 10), 1, dtype=bool)
aug = iaa.TotalDropout(p=1.0)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
assert image_aug.dtype.name == "bool"
assert np.sum(image_aug == 1) == 0
def test_other_dtypes_uint_int(self):
dts = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dt in dts:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
values = [min_value, int(center_value), max_value]
for value in values:
for p in [1.0, 0.0]:
with self.subTest(dtype=dt, value=value, p=p):
images = np.full((5, 1, 1, 3), value, dtype=dt)
aug = iaa.TotalDropout(p=p)
images_aug = aug(images=images)
assert images_aug.shape == images.shape
assert images_aug.dtype.name == dt
if np.isclose(p, 1.0) or value == 0:
assert np.sum(images_aug == 0) == 5*3
else:
assert np.sum(images_aug == value) == 5*3
def test_other_dtypes_float(self):
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dt in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
values = [min_value, -10.0, center_value, 10.0, max_value]
atol = 1e-3*max_value if dt == "float16" else 1e-9 * max_value
_isclose = functools.partial(np.isclose, atol=atol, rtol=0)
for value in values:
for p in [1.0, 0.0]:
with self.subTest(dtype=dt, value=value, p=p):
images = np.full((5, 1, 1, 3), value, dtype=dt)
aug = iaa.TotalDropout(p=p)
images_aug = aug(images=images)
assert images_aug.shape == images.shape
assert images_aug.dtype.name == dt
if np.isclose(p, 1.0):
assert np.sum(_isclose(images_aug, 0.0)) == 5*3
else:
assert (
np.sum(_isclose(images_aug, high_res_dt(value)))
== 5*3)
def test_pickleable(self):
aug = iaa.TotalDropout(p=0.5, seed=1)
runtest_pickleable_uint8_img(aug, iterations=30, shape=(4, 4, 2))
class TestMultiply(unittest.TestCase):
def setUp(self):
reseed()
def test_mul_is_one(self):
# no multiply, shouldnt change anything
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.Multiply(mul=1.0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
def test_mul_is_above_one(self):
# multiply >1.0
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.Multiply(mul=1.2)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = np.ones((1, 3, 3, 1), dtype=np.uint8) * 120
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = [np.ones((3, 3, 1), dtype=np.uint8) * 120]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = np.ones((1, 3, 3, 1), dtype=np.uint8) * 120
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [np.ones((3, 3, 1), dtype=np.uint8) * 120]
assert array_equal_lists(observed, expected)
def test_mul_is_below_one(self):
# multiply <1.0
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.Multiply(mul=0.8)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = np.ones((1, 3, 3, 1), dtype=np.uint8) * 80
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = [np.ones((3, 3, 1), dtype=np.uint8) * 80]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = np.ones((1, 3, 3, 1), dtype=np.uint8) * 80
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [np.ones((3, 3, 1), dtype=np.uint8) * 80]
assert array_equal_lists(observed, expected)
def test_keypoints_dont_change(self):
# keypoints shouldnt be changed
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=base_img.shape)]
aug = iaa.Multiply(mul=1.2)
aug_det = iaa.Multiply(mul=1.2).to_deterministic()
observed = aug.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
def test_tuple_as_mul(self):
# varying multiply factors
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
aug = iaa.Multiply(mul=(0, 2.0))
aug_det = aug.to_deterministic()
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.95)
assert nb_changed_aug_det == 0
def test_per_channel(self):
aug = iaa.Multiply(mul=iap.Choice([0, 2]), per_channel=True)
observed = aug.augment_image(np.ones((1, 1, 100), dtype=np.uint8))
uq = np.unique(observed)
assert observed.shape == (1, 1, 100)
assert 0 in uq
assert 2 in uq
assert len(uq) == 2
def test_per_channel_with_probability(self):
# test channelwise with probability
aug = iaa.Multiply(mul=iap.Choice([0, 2]), per_channel=0.5)
seen = [0, 0]
for _ in sm.xrange(400):
observed = aug.augment_image(np.ones((1, 1, 20), dtype=np.uint8))
assert observed.shape == (1, 1, 20)
uq = np.unique(observed)
per_channel = (len(uq) == 2)
if per_channel:
seen[0] += 1
else:
seen[1] += 1
assert 150 < seen[0] < 250
assert 150 < seen[1] < 250
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_ = iaa.Multiply(mul="test")
except Exception:
got_exception = True
assert got_exception
got_exception = False
try:
_ = iaa.Multiply(mul=1, per_channel="test")
except Exception:
got_exception = True
assert got_exception
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.ones(shape, dtype=np.uint8)
aug = iaa.Multiply(1)
image_aug = aug(image=image)
assert np.all(image_aug == 2)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.ones(shape, dtype=np.uint8)
aug = iaa.Multiply(2)
image_aug = aug(image=image)
assert np.all(image_aug == 2)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_get_parameters(self):
# test get_parameters()
aug = iaa.Multiply(mul=1, per_channel=False)
params = aug.get_parameters()
is_parameter_instance(params[0], iap.Deterministic)
is_parameter_instance(params[1], iap.Deterministic)
assert params[0].value == 1
assert params[1].value == 0
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.Multiply(mul=2)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_other_dtypes_bool(self):
# bool
image = np.zeros((3, 3), dtype=bool)
aug = iaa.Multiply(1.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.Multiply(1.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.Multiply(2.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.Multiply(0.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.Multiply(-1.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
def test_other_dtypes_uint_int(self):
# uint, int
for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
dtype = np.dtype(dtype)
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.Multiply(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 10)
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.Multiply(10)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 100)
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.Multiply(0.5)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 5)
image = np.full((3, 3), 0, dtype=dtype)
aug = iaa.Multiply(0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 0)
if np.dtype(dtype).kind == "u":
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.Multiply(-1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 0)
else:
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.Multiply(-1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == -10)
image = np.full((3, 3), int(center_value), dtype=dtype)
aug = iaa.Multiply(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == int(center_value))
image = np.full((3, 3), int(center_value), dtype=dtype)
aug = iaa.Multiply(1.2)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == int(1.2 * int(center_value)))
if np.dtype(dtype).kind == "u":
image = np.full((3, 3), int(center_value), dtype=dtype)
aug = iaa.Multiply(100)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.Multiply(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
# non-uint8 currently don't increase the itemsize
if dtype.name == "uint8":
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.Multiply(10)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.Multiply(0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 0)
# non-uint8 currently don't increase the itemsize
if dtype.name == "uint8":
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.Multiply(-2)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value)
# non-uint8 currently don't increase the itemsize
if dtype.name == "uint8":
for _ in sm.xrange(10):
image = np.full((1, 1, 3), 10, dtype=dtype)
aug = iaa.Multiply(iap.Uniform(0.5, 1.5))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(5 <= image_aug, image_aug <= 15))
assert len(np.unique(image_aug)) == 1
image = np.full((1, 1, 100), 10, dtype=dtype)
aug = iaa.Multiply(iap.Uniform(0.5, 1.5), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(5 <= image_aug, image_aug <= 15))
assert len(np.unique(image_aug)) > 1
image = np.full((1, 1, 3), 10, dtype=dtype)
aug = iaa.Multiply(iap.DiscreteUniform(1, 3))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) == 1
image = np.full((1, 1, 100), 10, dtype=dtype)
aug = iaa.Multiply(iap.DiscreteUniform(1, 3), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
def test_other_dtypes_float(self):
# float
for dtype in [np.float16, np.float32]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
if dtype == np.float16:
atol = 1e-3 * max_value
else:
atol = 1e-9 * max_value
_allclose = functools.partial(np.allclose, atol=atol, rtol=0)
image = np.full((3, 3), 10.0, dtype=dtype)
aug = iaa.Multiply(1.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 10.0)
image = np.full((3, 3), 10.0, dtype=dtype)
aug = iaa.Multiply(2.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 20.0)
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), max_value, dtype=dtype)
# aug = iaa.Multiply(-10)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert _allclose(image_aug, min_value)
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.Multiply(0.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 0.0)
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.Multiply(0.5)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 0.5*max_value)
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), min_value, dtype=dtype)
# aug = iaa.Multiply(-2.0)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert _allclose(image_aug, max_value)
image = np.full((3, 3), min_value, dtype=dtype)
aug = iaa.Multiply(0.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 0.0)
# using tolerances of -100 - 1e-2 and 100 + 1e-2 is not enough for float16, had to be increased to -/+ 1e-1
# deactivated, because itemsize increase was deactivated
"""
for _ in sm.xrange(10):
image = np.full((1, 1, 3), 10.0, dtype=dtype)
aug = iaa.Multiply(iap.Uniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-100 - 1e-1 < image_aug, image_aug < 100 + 1e-1))
assert np.allclose(image_aug[1:, :, 0], image_aug[:-1, :, 0])
assert np.allclose(image_aug[..., 0], image_aug[..., 1])
image = np.full((1, 1, 100), 10.0, dtype=dtype)
aug = iaa.Multiply(iap.Uniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-100 - 1e-1 < image_aug, image_aug < 100 + 1e-1))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1])
image = np.full((1, 1, 3), 10.0, dtype=dtype)
aug = iaa.Multiply(iap.DiscreteUniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-100 - 1e-1 < image_aug, image_aug < 100 + 1e-1))
assert np.allclose(image_aug[1:, :, 0], image_aug[:-1, :, 0])
assert np.allclose(image_aug[..., 0], image_aug[..., 1])
image = np.full((1, 1, 100), 10.0, dtype=dtype)
aug = iaa.Multiply(iap.DiscreteUniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-100 - 1e-1 < image_aug, image_aug < 100 + 1e-1))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1])
"""
def test_pickleable(self):
aug = iaa.Multiply((0.5, 1.5), per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
class TestMultiplyElementwise(unittest.TestCase):
def setUp(self):
reseed()
def test_mul_is_one(self):
# no multiply, shouldnt change anything
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.MultiplyElementwise(mul=1.0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
def test_mul_is_above_one(self):
# multiply >1.0
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.MultiplyElementwise(mul=1.2)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = np.ones((1, 3, 3, 1), dtype=np.uint8) * 120
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = [np.ones((3, 3, 1), dtype=np.uint8) * 120]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = np.ones((1, 3, 3, 1), dtype=np.uint8) * 120
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [np.ones((3, 3, 1), dtype=np.uint8) * 120]
assert array_equal_lists(observed, expected)
def test_mul_is_below_one(self):
# multiply <1.0
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
images_list = [base_img]
aug = iaa.MultiplyElementwise(mul=0.8)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = np.ones((1, 3, 3, 1), dtype=np.uint8) * 80
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = [np.ones((3, 3, 1), dtype=np.uint8) * 80]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = np.ones((1, 3, 3, 1), dtype=np.uint8) * 80
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [np.ones((3, 3, 1), dtype=np.uint8) * 80]
assert array_equal_lists(observed, expected)
def test_keypoints_dont_change(self):
# keypoints shouldnt be changed
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=base_img.shape)]
aug = iaa.MultiplyElementwise(mul=1.2)
aug_det = iaa.Multiply(mul=1.2).to_deterministic()
observed = aug.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
def test_tuple_as_mul(self):
# varying multiply factors
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
aug = iaa.MultiplyElementwise(mul=(0, 2.0))
aug_det = aug.to_deterministic()
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.95)
assert nb_changed_aug_det == 0
def test_samples_change_by_spatial_location(self):
# values should change between pixels
base_img = np.ones((3, 3, 1), dtype=np.uint8) * 100
images = np.array([base_img])
aug = iaa.MultiplyElementwise(mul=(0.5, 1.5))
nb_same = 0
nb_different = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_flat = observed_aug.flatten()
last = None
for j in sm.xrange(observed_aug_flat.size):
if last is not None:
v = observed_aug_flat[j]
if v - 0.0001 <= last <= v + 0.0001:
nb_same += 1
else:
nb_different += 1
last = observed_aug_flat[j]
assert nb_different > 0.95 * (nb_different + nb_same)
def test_per_channel(self):
# test channelwise
aug = iaa.MultiplyElementwise(mul=iap.Choice([0, 1]), per_channel=True)
observed = aug.augment_image(np.ones((100, 100, 3), dtype=np.uint8))
sums = np.sum(observed, axis=2)
values = np.unique(sums)
assert all([(value in values) for value in [0, 1, 2, 3]])
assert observed.shape == (100, 100, 3)
def test_per_channel_with_probability(self):
# test channelwise with probability
aug = iaa.MultiplyElementwise(mul=iap.Choice([0, 1]), per_channel=0.5)
seen = [0, 0]
for _ in sm.xrange(400):
observed = aug.augment_image(np.ones((20, 20, 3), dtype=np.uint8))
assert observed.shape == (20, 20, 3)
sums = np.sum(observed, axis=2)
values = np.unique(sums)
all_values_found = all([(value in values) for value in [0, 1, 2, 3]])
if all_values_found:
seen[0] += 1
else:
seen[1] += 1
assert 150 < seen[0] < 250
assert 150 < seen[1] < 250
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_aug = iaa.MultiplyElementwise(mul="test")
except Exception:
got_exception = True
assert got_exception
got_exception = False
try:
_aug = iaa.MultiplyElementwise(mul=1, per_channel="test")
except Exception:
got_exception = True
assert got_exception
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.ones(shape, dtype=np.uint8)
aug = iaa.MultiplyElementwise(2)
image_aug = aug(image=image)
assert np.all(image_aug == 2)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.ones(shape, dtype=np.uint8)
aug = iaa.MultiplyElementwise(2)
image_aug = aug(image=image)
assert np.all(image_aug == 2)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_get_parameters(self):
# test get_parameters()
aug = iaa.MultiplyElementwise(mul=1, per_channel=False)
params = aug.get_parameters()
is_parameter_instance(params[0], iap.Deterministic)
is_parameter_instance(params[1], iap.Deterministic)
assert params[0].value == 1
assert params[1].value == 0
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.MultiplyElementwise(mul=2)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_other_dtypes_bool(self):
# bool
image = np.zeros((3, 3), dtype=bool)
aug = iaa.MultiplyElementwise(1.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.MultiplyElementwise(1.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.MultiplyElementwise(2.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.MultiplyElementwise(0.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
image = np.full((3, 3), True, dtype=bool)
aug = iaa.MultiplyElementwise(-1.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
def test_other_dtypes_uint_int(self):
# uint, int
for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
dtype = np.dtype(dtype)
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 10)
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), 10, dtype=dtype)
# aug = iaa.MultiplyElementwise(10)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert np.all(image_aug == 100)
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(0.5)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 5)
image = np.full((3, 3), 0, dtype=dtype)
aug = iaa.MultiplyElementwise(0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 0)
# partially deactivated, because itemsize increase was deactivated
if dtype.name == "uint8":
if dtype.kind == "u":
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(-1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 0)
else:
image = np.full((3, 3), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(-1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == -10)
image = np.full((3, 3), int(center_value), dtype=dtype)
aug = iaa.MultiplyElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == int(center_value))
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), int(center_value), dtype=dtype)
# aug = iaa.MultiplyElementwise(1.2)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert np.all(image_aug == int(1.2 * int(center_value)))
# deactivated, because itemsize increase was deactivated
if dtype.name == "uint8":
if dtype.kind == "u":
image = np.full((3, 3), int(center_value), dtype=dtype)
aug = iaa.MultiplyElementwise(100)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.MultiplyElementwise(1)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), max_value, dtype=dtype)
# aug = iaa.MultiplyElementwise(10)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert np.all(image_aug == max_value)
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.MultiplyElementwise(0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 0)
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), max_value, dtype=dtype)
# aug = iaa.MultiplyElementwise(-2)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert np.all(image_aug == min_value)
# partially deactivated, because itemsize increase was deactivated
if dtype.name == "uint8":
for _ in sm.xrange(10):
image = np.full((5, 5, 3), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(iap.Uniform(0.5, 1.5))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(5 <= image_aug, image_aug <= 15))
assert len(np.unique(image_aug)) > 1
assert np.all(image_aug[..., 0] == image_aug[..., 1])
image = np.full((1, 1, 100), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(iap.Uniform(0.5, 1.5), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(5 <= image_aug, image_aug <= 15))
assert len(np.unique(image_aug)) > 1
image = np.full((5, 5, 3), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(iap.DiscreteUniform(1, 3))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
assert np.all(image_aug[..., 0] == image_aug[..., 1])
image = np.full((1, 1, 100), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(iap.DiscreteUniform(1, 3), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(10 <= image_aug, image_aug <= 30))
assert len(np.unique(image_aug)) > 1
def test_other_dtypes_float(self):
# float
for dtype in [np.float16, np.float32]:
dtype = np.dtype(dtype)
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
if dtype == np.float16:
atol = 1e-3 * max_value
else:
atol = 1e-9 * max_value
_allclose = functools.partial(np.allclose, atol=atol, rtol=0)
image = np.full((3, 3), 10.0, dtype=dtype)
aug = iaa.MultiplyElementwise(1.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 10.0)
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), 10.0, dtype=dtype)
# aug = iaa.MultiplyElementwise(2.0)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert _allclose(image_aug, 20.0)
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), max_value, dtype=dtype)
# aug = iaa.MultiplyElementwise(-10)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert _allclose(image_aug, min_value)
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.MultiplyElementwise(0.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 0.0)
image = np.full((3, 3), max_value, dtype=dtype)
aug = iaa.MultiplyElementwise(0.5)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 0.5*max_value)
# deactivated, because itemsize increase was deactivated
# image = np.full((3, 3), min_value, dtype=dtype)
# aug = iaa.MultiplyElementwise(-2.0)
# image_aug = aug.augment_image(image)
# assert image_aug.dtype.type == dtype
# assert _allclose(image_aug, max_value)
image = np.full((3, 3), min_value, dtype=dtype)
aug = iaa.MultiplyElementwise(0.0)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, 0.0)
# using tolerances of -100 - 1e-2 and 100 + 1e-2 is not enough for float16, had to be increased to -/+ 1e-1
# deactivated, because itemsize increase was deactivated
"""
for _ in sm.xrange(10):
image = np.full((50, 1, 3), 10.0, dtype=dtype)
aug = iaa.MultiplyElementwise(iap.Uniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-100 - 1e-1 < image_aug, image_aug < 100 + 1e-1))
assert not np.allclose(image_aug[1:, :, 0], image_aug[:-1, :, 0])
assert np.allclose(image_aug[..., 0], image_aug[..., 1])
image = np.full((1, 1, 100), 10.0, dtype=dtype)
aug = iaa.MultiplyElementwise(iap.Uniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-100 - 1e-1 < image_aug, image_aug < 100 + 1e-1))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1])
image = np.full((50, 1, 3), 10.0, dtype=dtype)
aug = iaa.MultiplyElementwise(iap.DiscreteUniform(-10, 10))
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-100 - 1e-1 < image_aug, image_aug < 100 + 1e-1))
assert not np.allclose(image_aug[1:, :, 0], image_aug[:-1, :, 0])
assert np.allclose(image_aug[..., 0], image_aug[..., 1])
image = np.full((1, 1, 100), 10, dtype=dtype)
aug = iaa.MultiplyElementwise(iap.DiscreteUniform(-10, 10), per_channel=True)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(-100 - 1e-1 < image_aug, image_aug < 100 + 1e-1))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1])
"""
def test_pickleable(self):
aug = iaa.MultiplyElementwise((0.5, 1.5), per_channel=True,
seed=1)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestReplaceElementwise(unittest.TestCase):
def setUp(self):
reseed()
def test_mask_is_always_zero(self):
# no replace, shouldnt change anything
base_img = np.ones((3, 3, 1), dtype=np.uint8) + 99
images = np.array([base_img])
images_list = [base_img]
aug = iaa.ReplaceElementwise(mask=0, replacement=0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
def test_mask_is_always_one(self):
# replace at 100 percent prob., should change everything
base_img = np.ones((3, 3, 1), dtype=np.uint8) + 99
images = np.array([base_img])
images_list = [base_img]
aug = iaa.ReplaceElementwise(mask=1, replacement=0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = np.zeros((1, 3, 3, 1), dtype=np.uint8)
assert np.array_equal(observed, expected)
assert observed.shape == (1, 3, 3, 1)
observed = aug.augment_images(images_list)
expected = [np.zeros((3, 3, 1), dtype=np.uint8)]
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images)
expected = np.zeros((1, 3, 3, 1), dtype=np.uint8)
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images_list)
expected = [np.zeros((3, 3, 1), dtype=np.uint8)]
assert array_equal_lists(observed, expected)
def test_mask_is_stochastic_parameter(self):
# replace half
aug = iaa.ReplaceElementwise(mask=iap.Binomial(p=0.5), replacement=0)
img = np.ones((100, 100, 1), dtype=np.uint8)
nb_iterations = 100
nb_diff_all = 0
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(img)
nb_diff = np.sum(img != observed)
nb_diff_all += nb_diff
p = nb_diff_all / (nb_iterations * 100 * 100)
assert 0.45 <= p <= 0.55
def test_mask_is_list(self):
# mask is list
aug = iaa.ReplaceElementwise(mask=[0.2, 0.7], replacement=1)
img = np.zeros((20, 20, 1), dtype=np.uint8)
seen = [0, 0, 0]
for i in sm.xrange(400):
observed = aug.augment_image(img)
p = np.mean(observed)
if 0.1 < p < 0.3:
seen[0] += 1
elif 0.6 < p < 0.8:
seen[1] += 1
else:
seen[2] += 1
assert seen[2] <= 10
assert 150 < seen[0] < 250
assert 150 < seen[1] < 250
def test_keypoints_dont_change(self):
# keypoints shouldnt be changed
base_img = np.ones((3, 3, 1), dtype=np.uint8) + 99
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=base_img.shape)]
aug = iaa.ReplaceElementwise(mask=iap.Binomial(p=0.5), replacement=0)
aug_det = iaa.ReplaceElementwise(mask=iap.Binomial(p=0.5), replacement=0).to_deterministic()
observed = aug.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
def test_replacement_is_stochastic_parameter(self):
# different replacements
aug = iaa.ReplaceElementwise(mask=1, replacement=iap.Choice([100, 200]))
img = np.zeros((1000, 1000, 1), dtype=np.uint8)
img100 = img + 100
img200 = img + 200
observed = aug.augment_image(img)
nb_diff_100 = np.sum(img100 != observed)
nb_diff_200 = np.sum(img200 != observed)
p100 = nb_diff_100 / (1000 * 1000)
p200 = nb_diff_200 / (1000 * 1000)
assert 0.45 <= p100 <= 0.55
assert 0.45 <= p200 <= 0.55
# test channelwise
aug = iaa.MultiplyElementwise(mul=iap.Choice([0, 1]), per_channel=True)
observed = aug.augment_image(np.ones((100, 100, 3), dtype=np.uint8))
sums = np.sum(observed, axis=2)
values = np.unique(sums)
assert all([(value in values) for value in [0, 1, 2, 3]])
def test_per_channel_with_probability(self):
# test channelwise with probability
aug = iaa.ReplaceElementwise(mask=iap.Choice([0, 1]), replacement=1, per_channel=0.5)
seen = [0, 0]
for _ in sm.xrange(400):
observed = aug.augment_image(np.zeros((20, 20, 3), dtype=np.uint8))
assert observed.shape == (20, 20, 3)
sums = np.sum(observed, axis=2)
values = np.unique(sums)
all_values_found = all([(value in values) for value in [0, 1, 2, 3]])
if all_values_found:
seen[0] += 1
else:
seen[1] += 1
assert 150 < seen[0] < 250
assert 150 < seen[1] < 250
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_aug = iaa.ReplaceElementwise(mask="test", replacement=1)
except Exception:
got_exception = True
assert got_exception
got_exception = False
try:
_aug = iaa.ReplaceElementwise(mask=1, replacement=1, per_channel="test")
except Exception:
got_exception = True
assert got_exception
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.ReplaceElementwise(1.0, 1)
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.ReplaceElementwise(1.0, 1)
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_get_parameters(self):
# test get_parameters()
aug = iaa.ReplaceElementwise(mask=0.5, replacement=2, per_channel=False)
params = aug.get_parameters()
is_parameter_instance(params[0], iap.Binomial)
is_parameter_instance(params[0].p, iap.Deterministic)
is_parameter_instance(params[1], iap.Deterministic)
is_parameter_instance(params[2], iap.Deterministic)
assert 0.5 - 1e-6 < params[0].p.value < 0.5 + 1e-6
assert params[1].value == 2
assert params[2].value == 0
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.ReplaceElementwise(mask=1, replacement=0.5)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_other_dtypes_bool(self):
# bool
aug = iaa.ReplaceElementwise(mask=1, replacement=0)
image = np.full((3, 3), False, dtype=bool)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
aug = iaa.ReplaceElementwise(mask=1, replacement=1)
image = np.full((3, 3), False, dtype=bool)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
aug = iaa.ReplaceElementwise(mask=1, replacement=0)
image = np.full((3, 3), True, dtype=bool)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
aug = iaa.ReplaceElementwise(mask=1, replacement=1)
image = np.full((3, 3), True, dtype=bool)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
aug = iaa.ReplaceElementwise(mask=1, replacement=0.7)
image = np.full((3, 3), False, dtype=bool)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 1)
aug = iaa.ReplaceElementwise(mask=1, replacement=0.2)
image = np.full((3, 3), False, dtype=bool)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == 0)
def test_other_dtypes_uint_int(self):
# uint, int
for dtype in [np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32]:
dtype = np.dtype(dtype)
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
aug = iaa.ReplaceElementwise(mask=1, replacement=1)
image = np.full((3, 3), 0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 1)
aug = iaa.ReplaceElementwise(mask=1, replacement=2)
image = np.full((3, 3), 1, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == 2)
# deterministic stochastic parameters are by default int32 for
# any integer value and hence cannot cover the full uint32 value
# range
if dtype.name != "uint32":
aug = iaa.ReplaceElementwise(mask=1, replacement=max_value)
image = np.full((3, 3), min_value, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == max_value)
aug = iaa.ReplaceElementwise(mask=1, replacement=min_value)
image = np.full((3, 3), max_value, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == min_value)
aug = iaa.ReplaceElementwise(mask=1, replacement=iap.Uniform(1.0, 10.0))
image = np.full((100, 1), 0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(1 <= image_aug, image_aug <= 10))
assert len(np.unique(image_aug)) > 1
aug = iaa.ReplaceElementwise(mask=1, replacement=iap.DiscreteUniform(1, 10))
image = np.full((100, 1), 0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(1 <= image_aug, image_aug <= 10))
assert len(np.unique(image_aug)) > 1
aug = iaa.ReplaceElementwise(mask=0.5, replacement=iap.DiscreteUniform(1, 10), per_channel=True)
image = np.full((1, 1, 100), 0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(0 <= image_aug, image_aug <= 10))
assert len(np.unique(image_aug)) > 2
def test_other_dtypes_float(self):
# float
for dtype in [np.float16, np.float32, np.float64]:
dtype = np.dtype(dtype)
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
atol = 1e-3*max_value if dtype == np.float16 else 1e-9 * max_value
_allclose = functools.partial(np.allclose, atol=atol, rtol=0)
aug = iaa.ReplaceElementwise(mask=1, replacement=1.0)
image = np.full((3, 3), 0.0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.allclose(image_aug, 1.0)
aug = iaa.ReplaceElementwise(mask=1, replacement=2.0)
image = np.full((3, 3), 1.0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.allclose(image_aug, 2.0)
# deterministic stochastic parameters are by default float32 for
# any float value and hence cannot cover the full float64 value
# range
if dtype.name != "float64":
aug = iaa.ReplaceElementwise(mask=1, replacement=max_value)
image = np.full((3, 3), min_value, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, max_value)
aug = iaa.ReplaceElementwise(mask=1, replacement=min_value)
image = np.full((3, 3), max_value, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert _allclose(image_aug, min_value)
aug = iaa.ReplaceElementwise(mask=1, replacement=iap.Uniform(1.0, 10.0))
image = np.full((100, 1), 0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(1 <= image_aug, image_aug <= 10))
assert not np.allclose(image_aug[1:, :], image_aug[:-1, :], atol=0.01)
aug = iaa.ReplaceElementwise(mask=1, replacement=iap.DiscreteUniform(1, 10))
image = np.full((100, 1), 0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(1 <= image_aug, image_aug <= 10))
assert not np.allclose(image_aug[1:, :], image_aug[:-1, :], atol=0.01)
aug = iaa.ReplaceElementwise(mask=0.5, replacement=iap.DiscreteUniform(1, 10), per_channel=True)
image = np.full((1, 1, 100), 0, dtype=dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(np.logical_and(0 <= image_aug, image_aug <= 10))
assert not np.allclose(image_aug[:, :, 1:], image_aug[:, :, :-1], atol=0.01)
def test_pickleable(self):
aug = iaa.ReplaceElementwise(mask=0.5, replacement=(0, 255),
per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3)
# not more tests necessary here as SaltAndPepper is just a tiny wrapper around
# ReplaceElementwise
class TestSaltAndPepper(unittest.TestCase):
def setUp(self):
reseed()
def test_p_is_fifty_percent(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.SaltAndPepper(p=0.5)
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
assert 0.4 < p < 0.6
def test_p_is_one(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.SaltAndPepper(p=1.0)
observed = aug.augment_image(base_img)
nb_pepper = np.sum(observed < 40)
nb_salt = np.sum(observed > 255 - 40)
assert nb_pepper > 200
assert nb_salt > 200
def test_pickleable(self):
aug = iaa.SaltAndPepper(p=0.5, per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestCoarseSaltAndPepper(unittest.TestCase):
def setUp(self):
reseed()
def test_p_is_fifty_percent(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.CoarseSaltAndPepper(p=0.5, size_px=100)
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
assert 0.4 < p < 0.6
def test_size_px(self):
aug1 = iaa.CoarseSaltAndPepper(p=0.5, size_px=100)
aug2 = iaa.CoarseSaltAndPepper(p=0.5, size_px=10)
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
ps1 = []
ps2 = []
for _ in sm.xrange(100):
observed1 = aug1.augment_image(base_img)
observed2 = aug2.augment_image(base_img)
p1 = np.mean(observed1 != 128)
p2 = np.mean(observed2 != 128)
ps1.append(p1)
ps2.append(p2)
assert 0.4 < np.mean(ps2) < 0.6
assert np.std(ps1)*1.5 < np.std(ps2)
def test_p_is_list(self):
aug = iaa.CoarseSaltAndPepper(p=[0.2, 0.5], size_px=100)
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
seen = [0, 0, 0]
for _ in sm.xrange(200):
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
diff_020 = abs(0.2 - p)
diff_050 = abs(0.5 - p)
if diff_020 < 0.025:
seen[0] += 1
elif diff_050 < 0.025:
seen[1] += 1
else:
seen[2] += 1
assert seen[2] < 10
assert 75 < seen[0] < 125
assert 75 < seen[1] < 125
def test_p_is_tuple(self):
aug = iaa.CoarseSaltAndPepper(p=(0.0, 1.0), size_px=50)
base_img = np.zeros((50, 50, 1), dtype=np.uint8) + 128
ps = []
for _ in sm.xrange(200):
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
ps.append(p)
nb_bins = 5
hist, _ = np.histogram(ps, bins=nb_bins, range=(0.0, 1.0), density=False)
tolerance = 0.05
for nb_seen in hist:
density = nb_seen / len(ps)
assert density - tolerance < density < density + tolerance
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_ = iaa.CoarseSaltAndPepper(p="test", size_px=100)
except Exception:
got_exception = True
assert got_exception
def test___init___size_px_and_size_percent_both_none(self):
aug = iaa.CoarseSaltAndPepper(p=0.5, size_px=None, size_percent=None)
assert np.isclose(aug.mask.size_px.a.value, 3)
assert np.isclose(aug.mask.size_px.b.value, 8)
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.CoarseSaltAndPepper(p=1.0, size_px=2)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_pickleable(self):
aug = iaa.CoarseSaltAndPepper(p=0.5, size_px=(4, 15),
per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
# not more tests necessary here as Salt is just a tiny wrapper around
# ReplaceElementwise
class TestSalt(unittest.TestCase):
def setUp(self):
reseed()
def test_p_is_fifty_percent(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.Salt(p=0.5)
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
assert 0.4 < p < 0.6
# Salt() occasionally replaces with 127, which probably should be the center-point here anyways
assert np.all(observed >= 127)
def test_p_is_one(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.Salt(p=1.0)
observed = aug.augment_image(base_img)
nb_pepper = np.sum(observed < 40)
nb_salt = np.sum(observed > 255 - 40)
assert nb_pepper == 0
assert nb_salt > 200
def test_pickleable(self):
aug = iaa.Salt(p=0.5, per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestCoarseSalt(unittest.TestCase):
def setUp(self):
reseed()
def test_p_is_fifty_percent(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.CoarseSalt(p=0.5, size_px=100)
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
assert 0.4 < p < 0.6
def test_size_px(self):
aug1 = iaa.CoarseSalt(p=0.5, size_px=100)
aug2 = iaa.CoarseSalt(p=0.5, size_px=10)
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
ps1 = []
ps2 = []
for _ in sm.xrange(100):
observed1 = aug1.augment_image(base_img)
observed2 = aug2.augment_image(base_img)
p1 = np.mean(observed1 != 128)
p2 = np.mean(observed2 != 128)
ps1.append(p1)
ps2.append(p2)
assert 0.4 < np.mean(ps2) < 0.6
assert np.std(ps1)*1.5 < np.std(ps2)
def test_p_is_list(self):
aug = iaa.CoarseSalt(p=[0.2, 0.5], size_px=100)
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
seen = [0, 0, 0]
for _ in sm.xrange(200):
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
diff_020 = abs(0.2 - p)
diff_050 = abs(0.5 - p)
if diff_020 < 0.025:
seen[0] += 1
elif diff_050 < 0.025:
seen[1] += 1
else:
seen[2] += 1
assert seen[2] < 10
assert 75 < seen[0] < 125
assert 75 < seen[1] < 125
def test_p_is_tuple(self):
aug = iaa.CoarseSalt(p=(0.0, 1.0), size_px=50)
base_img = np.zeros((50, 50, 1), dtype=np.uint8) + 128
ps = []
for _ in sm.xrange(200):
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
ps.append(p)
nb_bins = 5
hist, _ = np.histogram(ps, bins=nb_bins, range=(0.0, 1.0), density=False)
tolerance = 0.05
for nb_seen in hist:
density = nb_seen / len(ps)
assert density - tolerance < density < density + tolerance
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_ = iaa.CoarseSalt(p="test", size_px=100)
except Exception:
got_exception = True
assert got_exception
def test___init___size_px_and_size_percent_both_none(self):
aug = iaa.CoarseSalt(p=0.5, size_px=None, size_percent=None)
assert np.isclose(aug.mask.size_px.a.value, 3)
assert np.isclose(aug.mask.size_px.b.value, 8)
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.CoarseSalt(p=1.0, size_px=2)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_pickleable(self):
aug = iaa.CoarseSalt(p=0.5, size_px=(4, 15),
per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
# not more tests necessary here as Salt is just a tiny wrapper around
# ReplaceElementwise
class TestPepper(unittest.TestCase):
def setUp(self):
reseed()
def test_probability_is_fifty_percent(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.Pepper(p=0.5)
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
assert 0.4 < p < 0.6
assert np.all(observed <= 128)
def test_probability_is_one(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.Pepper(p=1.0)
observed = aug.augment_image(base_img)
nb_pepper = np.sum(observed < 40)
nb_salt = np.sum(observed > 255 - 40)
assert nb_pepper > 200
assert nb_salt == 0
def test_pickleable(self):
aug = iaa.Pepper(p=0.5, per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestCoarsePepper(unittest.TestCase):
def setUp(self):
reseed()
def test_p_is_fifty_percent(self):
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
aug = iaa.CoarsePepper(p=0.5, size_px=100)
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
assert 0.4 < p < 0.6
def test_size_px(self):
aug1 = iaa.CoarsePepper(p=0.5, size_px=100)
aug2 = iaa.CoarsePepper(p=0.5, size_px=10)
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
ps1 = []
ps2 = []
for _ in sm.xrange(100):
observed1 = aug1.augment_image(base_img)
observed2 = aug2.augment_image(base_img)
p1 = np.mean(observed1 != 128)
p2 = np.mean(observed2 != 128)
ps1.append(p1)
ps2.append(p2)
assert 0.4 < np.mean(ps2) < 0.6
assert np.std(ps1)*1.5 < np.std(ps2)
def test_p_is_list(self):
aug = iaa.CoarsePepper(p=[0.2, 0.5], size_px=100)
base_img = np.zeros((100, 100, 1), dtype=np.uint8) + 128
seen = [0, 0, 0]
for _ in sm.xrange(200):
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
diff_020 = abs(0.2 - p)
diff_050 = abs(0.5 - p)
if diff_020 < 0.025:
seen[0] += 1
elif diff_050 < 0.025:
seen[1] += 1
else:
seen[2] += 1
assert seen[2] < 10
assert 75 < seen[0] < 125
assert 75 < seen[1] < 125
def test_p_is_tuple(self):
aug = iaa.CoarsePepper(p=(0.0, 1.0), size_px=50)
base_img = np.zeros((50, 50, 1), dtype=np.uint8) + 128
ps = []
for _ in sm.xrange(200):
observed = aug.augment_image(base_img)
p = np.mean(observed != 128)
ps.append(p)
nb_bins = 5
hist, _ = np.histogram(ps, bins=nb_bins, range=(0.0, 1.0), density=False)
tolerance = 0.05
for nb_seen in hist:
density = nb_seen / len(ps)
assert density - tolerance < density < density + tolerance
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_ = iaa.CoarsePepper(p="test", size_px=100)
except Exception:
got_exception = True
assert got_exception
def test___init___size_px_and_size_percent_both_none(self):
aug = iaa.CoarsePepper(p=0.5, size_px=None, size_percent=None)
assert np.isclose(aug.mask.size_px.a.value, 3)
assert np.isclose(aug.mask.size_px.b.value, 8)
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.CoarsePepper(p=1.0, size_px=2)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_pickleable(self):
aug = iaa.CoarsePepper(p=0.5, size_px=(4, 15),
per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
class Test_invert(unittest.TestCase):
@mock.patch("imgaug.augmenters.arithmetic.invert_")
def test_mocked_defaults(self, mock_invert):
mock_invert.return_value = "foo"
arr = np.zeros((1,), dtype=np.uint8)
observed = iaa.invert(arr)
assert observed == "foo"
args = mock_invert.call_args_list[0]
assert np.array_equal(mock_invert.call_args_list[0][0][0], arr)
assert args[1]["min_value"] is None
assert args[1]["max_value"] is None
assert args[1]["threshold"] is None
assert args[1]["invert_above_threshold"] is True
@mock.patch("imgaug.augmenters.arithmetic.invert_")
def test_mocked(self, mock_invert):
mock_invert.return_value = "foo"
arr = np.zeros((1,), dtype=np.uint8)
observed = iaa.invert(arr, min_value=1, max_value=10, threshold=5,
invert_above_threshold=False)
assert observed == "foo"
args = mock_invert.call_args_list[0]
assert np.array_equal(mock_invert.call_args_list[0][0][0], arr)
assert args[1]["min_value"] == 1
assert args[1]["max_value"] == 10
assert args[1]["threshold"] == 5
assert args[1]["invert_above_threshold"] is False
def test_uint8(self):
values = np.array([0, 20, 45, 60, 128, 255], dtype=np.uint8)
expected = np.array([
255,
255-20,
255-45,
255-60,
255-128,
255-255
], dtype=np.uint8)
observed = iaa.invert(values)
assert np.array_equal(observed, expected)
assert observed is not values
# most parts of this function are tested via Invert
class Test_invert_(unittest.TestCase):
def test_arr_is_noncontiguous_uint8(self):
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
max_vr_flipped = np.fliplr(np.copy(zeros + 255))
observed = iaa.invert_(max_vr_flipped)
expected = zeros
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_arr_is_view_uint8(self):
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
max_vr_view = np.copy(zeros + 255)[:, :, [0, 2]]
observed = iaa.invert_(max_vr_view)
expected = zeros[:, :, [0, 2]]
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_uint(self):
dtypes = ["uint8", "uint16", "uint32", "uint64"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([0, 20, 45, 60, center_value, max_value],
dtype=dt)
expected = np.array([
max_value - 0,
max_value - 20,
max_value - 45,
max_value - 60,
max_value - center_value,
min_value
], dtype=dt)
observed = iaa.invert_(np.copy(values))
assert np.array_equal(observed, expected)
def test_uint_with_threshold_50_inv_above(self):
threshold = 50
dtypes = ["uint8", "uint16", "uint32", "uint64"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([0, 20, 45, 60, center_value, max_value],
dtype=dt)
expected = np.array([
0,
20,
45,
max_value - 60,
max_value - center_value,
min_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=True)
assert np.array_equal(observed, expected)
def test_uint_with_threshold_0_inv_above(self):
threshold = 0
dtypes = ["uint8", "uint16", "uint32", "uint64"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([0, 20, 45, 60, center_value, max_value],
dtype=dt)
expected = np.array([
max_value - 0,
max_value - 20,
max_value - 45,
max_value - 60,
max_value - center_value,
min_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=True)
assert np.array_equal(observed, expected)
def test_uint8_with_threshold_255_inv_above(self):
threshold = 255
dtypes = ["uint8"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([0, 20, 45, 60, center_value, max_value],
dtype=dt)
expected = np.array([
0,
20,
45,
60,
center_value,
min_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=True)
assert np.array_equal(observed, expected)
def test_uint8_with_threshold_256_inv_above(self):
threshold = 256
dtypes = ["uint8"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([0, 20, 45, 60, center_value, max_value],
dtype=dt)
expected = np.array([
0,
20,
45,
60,
center_value,
max_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=True)
assert np.array_equal(observed, expected)
def test_uint_with_threshold_50_inv_below(self):
threshold = 50
dtypes = ["uint8", "uint16", "uint32", "uint64"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([0, 20, 45, 60, center_value, max_value],
dtype=dt)
expected = np.array([
max_value - 0,
max_value - 20,
max_value - 45,
60,
center_value,
max_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=False)
assert np.array_equal(observed, expected)
def test_uint_with_threshold_50_inv_above_with_min_max(self):
threshold = 50
# uint64 does not support custom min/max, hence removed it here
dtypes = ["uint8", "uint16", "uint32"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([0, 20, 45, 60, center_value, max_value],
dtype=dt)
expected = np.array([
0, # not clipped to 10 as only >thresh affected
20,
45,
100 - 50,
100 - 90,
100 - 90
], dtype=dt)
observed = iaa.invert_(np.copy(values),
min_value=10,
max_value=100,
threshold=threshold,
invert_above_threshold=True)
assert np.array_equal(observed, expected)
def test_int_with_threshold_50_inv_above(self):
threshold = 50
dtypes = ["int8", "int16", "int32", "int64"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([-45, -20, center_value, 20, 45, max_value],
dtype=dt)
expected = np.array([
-45,
-20,
center_value,
20,
45,
min_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=True)
assert np.array_equal(observed, expected)
def test_int_with_threshold_50_inv_below(self):
threshold = 50
dtypes = ["int8", "int16", "int32", "int64"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = int(center_value)
values = np.array([-45, -20, center_value, 20, 45, max_value],
dtype=dt)
expected = np.array([
(-1) * (-45) - 1,
(-1) * (-20) - 1,
(-1) * center_value - 1,
(-1) * 20 - 1,
(-1) * 45 - 1,
max_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=False)
assert np.array_equal(observed, expected)
def test_float_with_threshold_50_inv_above(self):
threshold = 50
try:
_high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
_high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = center_value
values = np.array([-45.5, -20.5, center_value, 20.5, 45.5,
max_value],
dtype=dt)
expected = np.array([
-45.5,
-20.5,
center_value,
20.5,
45.5,
min_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=True)
assert np.allclose(observed, expected, rtol=0, atol=1e-4)
def test_float_with_threshold_50_inv_below(self):
threshold = 50
try:
_high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
_high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dt in dtypes:
with self.subTest(dtype=dt):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dt)
center_value = center_value
values = np.array([-45.5, -20.5, center_value, 20.5, 45.5,
max_value],
dtype=dt)
expected = np.array([
(-1) * (-45.5),
(-1) * (-20.5),
(-1) * center_value,
(-1) * 20.5,
(-1) * 45.5,
max_value
], dtype=dt)
observed = iaa.invert_(np.copy(values),
threshold=threshold,
invert_above_threshold=False)
assert np.allclose(observed, expected, rtol=0, atol=1e-4)
class Test__invert_uint8_subtract_(unittest.TestCase):
def test_fails_with_size_4(self):
# cv2 seems to fail in same cases when input arrays have exactly 4
# components.
# We verify here that this is the case.
# If this test method fails, it means that the code runs merely
# sub-optimally as cv2 could be used for such arrays. The code is then
# not wrong though.
for shape in [(1, 2, 2), (2, 1, 2), (4, 1)]:
with self.subTest(shape=shape):
zeros = np.zeros(shape, dtype=np.uint8)
with self.assertRaises(cv2.error):
_ = _invert_uint8_subtract_(zeros, 255)
def test_0_inverted_to_255_all_small_shapes(self):
# this includes zero-sized axes
for height in np.arange(8):
for width in np.arange(8):
# cv2 fails on area==4, we use a LUT function for that case
if height * width == 4:
continue
for nb_channels in [None, 1, 3]:
channels_tpl = (nb_channels,)
if nb_channels is None:
channels_tpl = tuple()
shape = (height, width) + channels_tpl
with self.subTest(shape=shape):
zeros = np.zeros(shape, dtype=np.uint8)
observed = _invert_uint8_subtract_(np.copy(zeros), 255)
expected = np.full(shape, 255, dtype=np.uint8)
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_0_inverted_to_255(self):
for shape_hw in [(4, 8), (4, 4), (2, 4), (4, 2), (1, 16), (16, 1),
(1, 2), (2, 1), (1, 1), (40, 60)]:
shapes_2d3d = [shape_hw]
for nb_channels in [1, 2, 3, 4, 5, 10, 512, 513]:
shapes_2d3d.append(shape_hw + (nb_channels,))
for shape in shapes_2d3d:
if np.prod(shape) == 4:
continue
with self.subTest(shape=shape):
zeros = np.zeros(shape, dtype=np.uint8)
observed = _invert_uint8_subtract_(np.copy(zeros), 255)
expected = np.full(shape, 255, dtype=np.uint8)
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_nonzero_values(self):
arr = np.array([0, 10, 20, 30, 40, 50], dtype=np.uint8).reshape((3, 2))
observed = _invert_uint8_subtract_(np.copy(arr), 255)
expected = np.array(
[255-0, 255-10, 255-20, 255-30, 255-40, 255-50],
dtype=np.uint8
).reshape((3, 2))
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_noncontiguous(self):
for shape_hw in [(3, 2), (4, 8)]:
shapes_2d3d = [shape_hw]
for nb_channels in [1, 2, 3, 4, 5, 10, 512, 513]:
shapes_2d3d.append(shape_hw + (nb_channels,))
for shape in shapes_2d3d:
with self.subTest(shape=shape):
zeros = np.zeros(shape, dtype=np.uint8, order="F")
assert zeros.flags["C_CONTIGUOUS"] is False
observed = _invert_uint8_subtract_(np.copy(zeros), 255)
expected = np.full(shape, 255, dtype=np.uint8)
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_unusual_base_shapes(self):
for shape in [(5, 10, 514), (5, 1, 514), (1, 5, 514)]:
zeros = np.zeros(shape, dtype=np.uint8)
for nb_selected in [1, 2, 3, 5, 10, 512, 513]:
with self.subTest(shape=shape, nb_selected=nb_selected):
mask = [False] * shape[-1]
for c in np.arange(nb_selected):
mask[c] = True
zeros_view = np.copy(zeros)[:, :, mask]
assert zeros_view.flags["OWNDATA"] is False
assert zeros_view.base is not None
assert (
zeros_view.base.shape
== (nb_selected, shape[0], shape[1])
), zeros_view.base.shape
observed = _invert_uint8_subtract_(zeros_view, 255)
expected = np.full(
(shape[0], shape[1], nb_selected), 255, dtype=np.uint8
)
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_view(self):
for shape_hw in [(1, 1), (1, 2), (2, 1), (4, 8), (40, 60)]:
shapes_2d3d = [shape_hw]
for nb_channels in [1, 2, 3, 4, 5, 10, 512, 513]:
shapes_2d3d.append(shape_hw + (nb_channels,))
for shape in shapes_2d3d:
if np.prod(shape) == 4:
continue
with self.subTest(shape=shape):
shape_pad = (shape[0] + 2,) + shape[1:]
zeros = np.zeros(shape_pad, dtype=np.uint8)
zeros_view = np.copy(zeros)[0:-2, ...]
assert zeros_view.flags["OWNDATA"] is False
observed = _invert_uint8_subtract_(zeros_view, 255)
expected = np.full(shape, 255, dtype=np.uint8)
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
class Test_solarize(unittest.TestCase):
@mock.patch("imgaug.augmenters.arithmetic.solarize_")
def test_mocked_defaults(self, mock_sol):
arr = np.zeros((1,), dtype=np.uint8)
mock_sol.return_value = "foo"
observed = iaa.solarize(arr)
args = mock_sol.call_args_list[0][0]
kwargs = mock_sol.call_args_list[0][1]
assert args[0] is not arr
assert np.array_equal(args[0], arr)
assert kwargs["threshold"] == 128
assert observed == "foo"
@mock.patch("imgaug.augmenters.arithmetic.solarize_")
def test_mocked(self, mock_sol):
arr = np.zeros((1,), dtype=np.uint8)
mock_sol.return_value = "foo"
observed = iaa.solarize(arr, threshold=5)
args = mock_sol.call_args_list[0][0]
kwargs = mock_sol.call_args_list[0][1]
assert args[0] is not arr
assert np.array_equal(args[0], arr)
assert kwargs["threshold"] == 5
assert observed == "foo"
def test_uint8(self):
arr = np.array([0, 10, 50, 150, 200, 255], dtype=np.uint8)
arr = arr.reshape((2, 3, 1))
observed = iaa.solarize(arr)
expected = np.array([0, 10, 50, 255-150, 255-200, 255-255],
dtype=np.uint8).reshape((2, 3, 1))
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
class Test_solarize_(unittest.TestCase):
@mock.patch("imgaug.augmenters.arithmetic.invert_")
def test_mocked_defaults(self, mock_sol):
arr = np.zeros((1,), dtype=np.uint8)
mock_sol.return_value = "foo"
observed = iaa.solarize_(arr)
args = mock_sol.call_args_list[0][0]
kwargs = mock_sol.call_args_list[0][1]
assert args[0] is arr
assert kwargs["threshold"] == 128
assert observed == "foo"
@mock.patch("imgaug.augmenters.arithmetic.invert_")
def test_mocked(self, mock_sol):
arr = np.zeros((1,), dtype=np.uint8)
mock_sol.return_value = "foo"
observed = iaa.solarize_(arr, threshold=5)
args = mock_sol.call_args_list[0][0]
kwargs = mock_sol.call_args_list[0][1]
assert args[0] is arr
assert kwargs["threshold"] == 5
assert observed == "foo"
class TestInvert(unittest.TestCase):
def setUp(self):
reseed()
def test_p_is_one(self):
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
observed = iaa.Invert(p=1.0).augment_image(zeros + 255)
expected = zeros
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_p_is_zero(self):
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
observed = iaa.Invert(p=0.0).augment_image(zeros + 255)
expected = zeros + 255
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_max_value_set(self):
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
observed = iaa.Invert(p=1.0, max_value=200).augment_image(zeros + 200)
expected = zeros
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_min_value_and_max_value_set(self):
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
observed = iaa.Invert(p=1.0, max_value=200, min_value=100).augment_image(zeros + 200)
expected = zeros + 100
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
observed = iaa.Invert(p=1.0, max_value=200, min_value=100).augment_image(zeros + 100)
expected = zeros + 200
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_min_value_and_max_value_set_with_float_image(self):
# with min/max and float inputs
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
zeros_f32 = zeros.astype(np.float32)
observed = iaa.Invert(p=1.0, max_value=200, min_value=100).augment_image(zeros_f32 + 200)
expected = zeros_f32 + 100
assert observed.dtype.name == "float32"
assert np.array_equal(observed, expected)
observed = iaa.Invert(p=1.0, max_value=200, min_value=100).augment_image(zeros_f32 + 100)
expected = zeros_f32 + 200
assert observed.dtype.name == "float32"
assert np.array_equal(observed, expected)
def test_p_is_80_percent(self):
nb_iterations = 1000
nb_inverted = 0
aug = iaa.Invert(p=0.8)
img = np.zeros((1, 1, 1), dtype=np.uint8) + 255
expected = np.zeros((1, 1, 1), dtype=np.uint8)
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(img)
if np.array_equal(observed, expected):
nb_inverted += 1
pinv = nb_inverted / nb_iterations
assert 0.75 <= pinv <= 0.85
nb_iterations = 1000
nb_inverted = 0
aug = iaa.Invert(p=iap.Binomial(0.8))
img = np.zeros((1, 1, 1), dtype=np.uint8) + 255
expected = np.zeros((1, 1, 1), dtype=np.uint8)
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(img)
if np.array_equal(observed, expected):
nb_inverted += 1
pinv = nb_inverted / nb_iterations
assert 0.75 <= pinv <= 0.85
def test_per_channel(self):
aug = iaa.Invert(p=0.5, per_channel=True)
img = np.zeros((1, 1, 100), dtype=np.uint8) + 255
observed = aug.augment_image(img)
assert len(np.unique(observed)) == 2
# TODO split into two tests
def test_p_is_stochastic_parameter_per_channel_is_probability(self):
nb_iterations = 1000
aug = iaa.Invert(p=iap.Binomial(0.8), per_channel=0.7)
img = np.zeros((1, 1, 20), dtype=np.uint8) + 255
seen = [0, 0]
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(img)
uq = np.unique(observed)
if len(uq) == 1:
seen[0] += 1
elif len(uq) == 2:
seen[1] += 1
else:
assert False
assert 300 - 75 < seen[0] < 300 + 75
assert 700 - 75 < seen[1] < 700 + 75
def test_threshold(self):
arr = np.array([0, 10, 50, 150, 200, 255], dtype=np.uint8)
arr = arr.reshape((2, 3, 1))
aug = iaa.Invert(p=1.0, threshold=128, invert_above_threshold=True)
observed = aug.augment_image(arr)
expected = np.array([0, 10, 50, 255-150, 255-200, 255-255],
dtype=np.uint8).reshape((2, 3, 1))
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_threshold_inv_below(self):
arr = np.array([0, 10, 50, 150, 200, 255], dtype=np.uint8)
arr = arr.reshape((2, 3, 1))
aug = iaa.Invert(p=1.0, threshold=128, invert_above_threshold=False)
observed = aug.augment_image(arr)
expected = np.array([255-0, 255-10, 255-50, 150, 200, 255],
dtype=np.uint8).reshape((2, 3, 1))
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_keypoints_dont_change(self):
# keypoints shouldnt be changed
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=zeros.shape)]
aug = iaa.Invert(p=1.0)
aug_det = iaa.Invert(p=1.0).to_deterministic()
observed = aug.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
def test___init___bad_datatypes(self):
# test exceptions for wrong parameter types
got_exception = False
try:
_ = iaa.Invert(p="test")
except Exception:
got_exception = True
assert got_exception
got_exception = False
try:
_ = iaa.Invert(p=0.5, per_channel="test")
except Exception:
got_exception = True
assert got_exception
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Invert(1.0)
image_aug = aug(image=image)
assert np.all(image_aug == 255)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Invert(1.0)
image_aug = aug(image=image)
assert np.all(image_aug == 255)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_get_parameters(self):
# test get_parameters()
aug = iaa.Invert(p=0.5, per_channel=False, min_value=10, max_value=20)
params = aug.get_parameters()
assert params[0] is aug.p
assert params[1] is aug.per_channel
assert params[2] == 10
assert params[3] == 20
assert params[4] is aug.threshold
assert params[5] is aug.invert_above_threshold
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.Invert(p=1.0)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_other_dtypes_p_is_zero(self):
# with p=0.0
aug = iaa.Invert(p=0.0)
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = [
bool,
np.uint8, np.uint16, np.uint32, np.uint64,
np.int8, np.int16, np.int32, np.int64,
np.float16, np.float32, np.float64
] + f128
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
kind = np.dtype(dtype).kind
image_min = np.full((3, 3), min_value, dtype=dtype)
if dtype is not bool:
image_center = (
np.full(
(3, 3),
center_value if kind == "f" else int(center_value),
dtype=dtype
)
)
image_max = np.full((3, 3), max_value, dtype=dtype)
image_min_aug = aug.augment_image(image_min)
image_center_aug = None
if dtype is not bool:
image_center_aug = aug.augment_image(image_center)
image_max_aug = aug.augment_image(image_max)
assert image_min_aug.dtype == np.dtype(dtype)
if image_center_aug is not None:
assert image_center_aug.dtype == np.dtype(dtype)
assert image_max_aug.dtype == np.dtype(dtype)
if dtype is bool:
assert np.all(image_min_aug == image_min)
assert np.all(image_max_aug == image_max)
elif np.dtype(dtype).kind in ["i", "u"]:
assert np.array_equal(image_min_aug, image_min)
assert np.array_equal(image_center_aug, image_center)
assert np.array_equal(image_max_aug, image_max)
else:
assert np.allclose(image_min_aug, image_min)
assert np.allclose(image_center_aug, image_center)
assert np.allclose(image_max_aug, image_max)
def test_other_dtypes_p_is_one(self):
# with p=1.0
aug = iaa.Invert(p=1.0)
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = [
bool,
np.uint8, np.uint16, np.uint32, np.uint64,
np.int8, np.int16, np.int32, np.int64,
np.float16, np.float32, np.float64
] + f128
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
kind = np.dtype(dtype).kind
image_min = np.full((3, 3), min_value, dtype=dtype)
if dtype is not bool:
image_center = (
np.full(
(3, 3),
center_value if kind == "f" else int(center_value),
dtype=dtype
)
)
image_max = np.full((3, 3), max_value, dtype=dtype)
image_min_aug = aug.augment_image(image_min)
image_center_aug = None
if dtype is not bool:
image_center_aug = aug.augment_image(image_center)
image_max_aug = aug.augment_image(image_max)
assert image_min_aug.dtype == np.dtype(dtype)
if image_center_aug is not None:
assert image_center_aug.dtype == np.dtype(dtype)
assert image_max_aug.dtype == np.dtype(dtype)
if dtype is bool:
assert np.all(image_min_aug == image_max)
assert np.all(image_max_aug == image_min)
elif np.dtype(dtype).kind in ["i", "u"]:
assert np.array_equal(image_min_aug, image_max)
assert np.allclose(image_center_aug, image_center, atol=1.0+1e-4, rtol=0)
assert np.array_equal(image_max_aug, image_min)
else:
assert np.allclose(image_min_aug, image_max)
assert np.allclose(image_center_aug, image_center)
assert np.allclose(image_max_aug, image_min)
def test_other_dtypes_p_is_one_with_min_value(self):
# with p=1.0 and min_value
aug = iaa.Invert(p=1.0, min_value=1)
dtypes = [np.uint8, np.uint16, np.uint32,
np.int8, np.int16, np.int32,
np.float16, np.float32]
for dtype in dtypes:
_min_value, _center_value, max_value = iadt.get_value_range_of_dtype(dtype)
min_value = 1
kind = np.dtype(dtype).kind
center_value = min_value + 0.5 * (max_value - min_value)
image_min = np.full((3, 3), min_value, dtype=dtype)
if dtype is not bool:
image_center = np.full((3, 3), center_value if kind == "f" else int(center_value), dtype=dtype)
image_max = np.full((3, 3), max_value, dtype=dtype)
image_min_aug = aug.augment_image(image_min)
image_center_aug = None
if dtype is not bool:
image_center_aug = aug.augment_image(image_center)
image_max_aug = aug.augment_image(image_max)
assert image_min_aug.dtype == np.dtype(dtype)
if image_center_aug is not None:
assert image_center_aug.dtype == np.dtype(dtype)
assert image_max_aug.dtype == np.dtype(dtype)
if dtype is bool:
assert np.all(image_min_aug == 1)
assert np.all(image_max_aug == 1)
elif np.dtype(dtype).kind in ["i", "u"]:
assert np.array_equal(image_min_aug, image_max)
assert np.allclose(image_center_aug, image_center, atol=1.0+1e-4, rtol=0)
assert np.array_equal(image_max_aug, image_min)
else:
assert np.allclose(image_min_aug, image_max)
assert np.allclose(image_center_aug, image_center)
assert np.allclose(image_max_aug, image_min)
def test_other_dtypes_p_is_one_with_max_value(self):
# with p=1.0 and max_value
aug = iaa.Invert(p=1.0, max_value=16)
dtypes = [np.uint8, np.uint16, np.uint32,
np.int8, np.int16, np.int32,
np.float16, np.float32]
for dtype in dtypes:
min_value, _center_value, _max_value = iadt.get_value_range_of_dtype(dtype)
max_value = 16
kind = np.dtype(dtype).kind
center_value = min_value + 0.5 * (max_value - min_value)
image_min = np.full((3, 3), min_value, dtype=dtype)
if dtype is not bool:
image_center = np.full((3, 3), center_value if kind == "f" else int(center_value), dtype=dtype)
image_max = np.full((3, 3), max_value, dtype=dtype)
image_min_aug = aug.augment_image(image_min)
image_center_aug = None
if dtype is not bool:
image_center_aug = aug.augment_image(image_center)
image_max_aug = aug.augment_image(image_max)
assert image_min_aug.dtype == np.dtype(dtype)
if image_center_aug is not None:
assert image_center_aug.dtype == np.dtype(dtype)
assert image_max_aug.dtype == np.dtype(dtype)
if dtype is bool:
assert not np.any(image_min_aug == 1)
assert not np.any(image_max_aug == 1)
elif np.dtype(dtype).kind in ["i", "u"]:
assert np.array_equal(image_min_aug, image_max)
assert np.allclose(image_center_aug, image_center, atol=1.0+1e-4, rtol=0)
assert np.array_equal(image_max_aug, image_min)
else:
assert np.allclose(image_min_aug, image_max)
if dtype is np.float16:
# for float16, this is off by about 10
assert np.allclose(image_center_aug, image_center, atol=0.001*np.finfo(dtype).max)
else:
assert np.allclose(image_center_aug, image_center)
assert np.allclose(image_max_aug, image_min)
def test_pickleable(self):
aug = iaa.Invert(p=0.5, per_channel=True, seed=1)
runtest_pickleable_uint8_img(aug, iterations=20, shape=(2, 2, 5))
class TestSolarize(unittest.TestCase):
def test_p_is_one(self):
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
observed = iaa.Solarize(p=1.0).augment_image(zeros)
expected = zeros
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_p_is_one_some_values_above_threshold(self):
arr = np.array([0, 99, 111, 200]).astype(np.uint8).reshape((2, 2, 1))
observed = iaa.Solarize(p=1.0, threshold=(100, 110))(image=arr)
expected = np.array([0, 99, 255-111, 255-200])\
.astype(np.uint8).reshape((2, 2, 1))
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_pickleable(self):
aug = iaa.pillike.Solarize(p=1.0, threshold=(100, 110))
runtest_pickleable_uint8_img(aug)
class TestContrastNormalization(unittest.TestCase):
@unittest.skipIf(sys.version_info[0] <= 2,
"Warning is not generated in 2.7 on travis, but locally "
"in 2.7 it is?!")
def test_deprecation_warning(self):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
aug = arithmetic_lib.ContrastNormalization((0.9, 1.1))
assert isinstance(aug, contrast_lib._ContrastFuncWrapper)
assert len(caught_warnings) == 1
assert (
"deprecated"
in str(caught_warnings[-1].message)
)
# TODO use this in test_contrast.py or remove it?
"""
def deactivated_test_ContrastNormalization():
reseed()
zeros = np.zeros((4, 4, 3), dtype=np.uint8)
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)], shape=zeros.shape)]
# contrast stays the same
observed = iaa.ContrastNormalization(alpha=1.0).augment_image(zeros + 50)
expected = zeros + 50
assert np.array_equal(observed, expected)
# image with mean intensity (ie 128), contrast cannot be changed
observed = iaa.ContrastNormalization(alpha=2.0).augment_image(zeros + 128)
expected = zeros + 128
assert np.array_equal(observed, expected)
# increase contrast
observed = iaa.ContrastNormalization(alpha=2.0).augment_image(zeros + 128 + 10)
expected = zeros + 128 + 20
assert np.array_equal(observed, expected)
observed = iaa.ContrastNormalization(alpha=2.0).augment_image(zeros + 128 - 10)
expected = zeros + 128 - 20
assert np.array_equal(observed, expected)
# decrease contrast
observed = iaa.ContrastNormalization(alpha=0.5).augment_image(zeros + 128 + 10)
expected = zeros + 128 + 5
assert np.array_equal(observed, expected)
observed = iaa.ContrastNormalization(alpha=0.5).augment_image(zeros + 128 - 10)
expected = zeros + 128 - 5
assert np.array_equal(observed, expected)
# increase contrast by stochastic parameter
observed = iaa.ContrastNormalization(alpha=iap.Choice([2.0, 3.0])).augment_image(zeros + 128 + 10)
expected1 = zeros + 128 + 20
expected2 = zeros + 128 + 30
assert np.array_equal(observed, expected1) or np.array_equal(observed, expected2)
# change contrast by tuple
nb_iterations = 1000
nb_changed = 0
last = None
for i in sm.xrange(nb_iterations):
observed = iaa.ContrastNormalization(alpha=(0.5, 2.0)).augment_image(zeros + 128 + 40)
if last is None:
last = observed
else:
if not np.array_equal(observed, last):
nb_changed += 1
p_changed = nb_changed / (nb_iterations-1)
assert p_changed > 0.5
# per_channel=True
aug = iaa.ContrastNormalization(alpha=(1.0, 6.0), per_channel=True)
img = np.zeros((1, 1, 100), dtype=np.uint8) + 128 + 10
observed = aug.augment_image(img)
uq = np.unique(observed)
assert len(uq) > 5
# per_channel with probability
aug = iaa.ContrastNormalization(alpha=(1.0, 4.0), per_channel=0.7)
img = np.zeros((1, 1, 100), dtype=np.uint8) + 128 + 10
seen = [0, 0]
for _ in sm.xrange(1000):
observed = aug.augment_image(img)
uq = np.unique(observed)
if len(uq) == 1:
seen[0] += 1
elif len(uq) >= 2:
seen[1] += 1
assert 300 - 75 < seen[0] < 300 + 75
assert 700 - 75 < seen[1] < 700 + 75
# keypoints shouldnt be changed
aug = iaa.ContrastNormalization(alpha=2.0)
aug_det = iaa.ContrastNormalization(alpha=2.0).to_deterministic()
observed = aug.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
# test exceptions for wrong parameter types
got_exception = False
try:
_ = iaa.ContrastNormalization(alpha="test")
except Exception:
got_exception = True
assert got_exception
got_exception = False
try:
_ = iaa.ContrastNormalization(alpha=1.5, per_channel="test")
except Exception:
got_exception = True
assert got_exception
# test get_parameters()
aug = iaa.ContrastNormalization(alpha=1, per_channel=False)
params = aug.get_parameters()
assert isinstance(params[0], iap.Deterministic)
assert isinstance(params[1], iap.Deterministic)
assert params[0].value == 1
assert params[1].value == 0
# test heatmaps (not affected by augmenter)
aug = iaa.ContrastNormalization(alpha=2)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
"""
class TestJpegCompression(unittest.TestCase):
def setUp(self):
reseed()
def test_compression_is_zero(self):
# basic test at 0 compression
img = ia.data.quokka(extract="square", size=(64, 64))
aug = iaa.JpegCompression(0)
img_aug = aug.augment_image(img)
diff = np.average(np.abs(img.astype(np.float32) - img_aug.astype(np.float32)))
assert diff < 1.0
def test_compression_is_90(self):
# basic test at 90 compression
img = ia.data.quokka(extract="square", size=(64, 64))
aug = iaa.JpegCompression(90)
img_aug = aug.augment_image(img)
diff = np.average(np.abs(img.astype(np.float32) - img_aug.astype(np.float32)))
assert 1.0 < diff < 50.0
def test___init__(self):
aug = iaa.JpegCompression([0, 100])
assert is_parameter_instance(aug.compression, iap.Choice)
assert len(aug.compression.a) == 2
assert aug.compression.a[0] == 0
assert aug.compression.a[1] == 100
def test_get_parameters(self):
aug = iaa.JpegCompression([0, 100])
assert len(aug.get_parameters()) == 1
assert aug.get_parameters()[0] == aug.compression
def test_compression_is_stochastic_parameter(self):
# test if stochastic parameters are used by augmentation
img = ia.data.quokka(extract="square", size=(64, 64))
class _TwoValueParam(iap.StochasticParameter):
def __init__(self, v1, v2):
super(_TwoValueParam, self).__init__()
self.v1 = v1
self.v2 = v2
def _draw_samples(self, size, random_state):
arr = np.full(size, self.v1, dtype=np.float32)
arr[1::2] = self.v2
return arr
param = _TwoValueParam(0, 100)
aug = iaa.JpegCompression(param)
img_aug_c0 = iaa.JpegCompression(0).augment_image(img)
img_aug_c100 = iaa.JpegCompression(100).augment_image(img)
imgs_aug = aug.augment_images([img] * 4)
assert np.array_equal(imgs_aug[0], img_aug_c0)
assert np.array_equal(imgs_aug[1], img_aug_c100)
assert np.array_equal(imgs_aug[2], img_aug_c0)
assert np.array_equal(imgs_aug[3], img_aug_c100)
def test_keypoints_dont_change(self):
# test keypoints (not affected by augmenter)
aug = iaa.JpegCompression(50)
kps = ia.data.quokka_keypoints()
kps_aug = aug.augment_keypoints([kps])[0]
for kp, kp_aug in zip(kps.keypoints, kps_aug.keypoints):
assert np.allclose([kp.x, kp.y], [kp_aug.x, kp_aug.y])
def test_heatmaps_dont_change(self):
# test heatmaps (not affected by augmenter)
aug = iaa.JpegCompression(50)
hm = ia.data.quokka_heatmap()
hm_aug = aug.augment_heatmaps([hm])[0]
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.JpegCompression(100)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == image.shape
def test_pickleable(self):
aug = iaa.JpegCompression((0, 100), seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
================================================
FILE: test/augmenters/test_artistic.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import random as iarandom
from imgaug.testutils import reseed, runtest_pickleable_uint8_img
import imgaug.augmenters.color as colorlib
import imgaug.augmenters.artistic as artisticlib
class Test_stylize_cartoon(unittest.TestCase):
@classmethod
def _test_integrationtest(cls, size, validate_grads):
image = ia.data.quokka_square((size, size))
image_cartoon = iaa.stylize_cartoon(image, blur_ksize=5,
segmentation_size=2.0)
image_avg = np.average(image.astype(np.float32), axis=2)
image_cartoon_avg = np.average(image_cartoon.astype(np.float32), axis=2)
if validate_grads:
gradx_image = image_avg[:, :-1] - image_avg[:, 1:]
grady_image = image_avg[:-1, :] - image_avg[1:, :]
gradx_cartoon = image_cartoon_avg[:, :-1] - image_cartoon_avg[:, 1:]
grady_cartoon = image_cartoon_avg[:-1, :] - image_cartoon_avg[1:, :]
assert (
(
np.average(np.abs(gradx_cartoon))
+ np.average(np.abs(grady_cartoon))
)
<
(
np.average(np.abs(gradx_image))
+ np.average(np.abs(grady_image))
)
)
# average saturation of cartoon image should be increased
image_hsv = colorlib.change_colorspace_(np.copy(image),
to_colorspace=iaa.CSPACE_HSV)
cartoon_hsv = colorlib.change_colorspace_(np.copy(image_cartoon),
to_colorspace=iaa.CSPACE_HSV)
assert (
np.average(cartoon_hsv[:, :, 1])
> np.average(image_hsv[:, :, 1])
)
# as edges are all drawn in completely black, there should be more
# completely black pixels in the cartoon image
image_black = np.sum(image_avg <= 0.01)
cartoon_black = np.sum(image_cartoon_avg <= 0.01)
assert cartoon_black > image_black
def test_integrationtest(self):
self._test_integrationtest(128, True)
def test_integrationtest_large_image(self):
# TODO the validation of gradients currently doesn't work well
# for the laplacian edge method, but it should
self._test_integrationtest(400, False)
@mock.patch("cv2.medianBlur")
def test_blur_ksize_is_1(self, mock_blur):
def _side_effect(image, ksize):
return image
mock_blur.side_effect = _side_effect
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
_ = iaa.stylize_cartoon(image, blur_ksize=1)
# median blur is called another time in _find_edge_laplacian, but
# that function is only called if the image is larger
assert mock_blur.call_count == 0
@mock.patch("cv2.medianBlur")
def test_blur_ksize_gt_1(self, mock_blur):
def _side_effect(image, ksize):
return image
mock_blur.side_effect = _side_effect
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
_ = iaa.stylize_cartoon(image, blur_ksize=7)
assert mock_blur.call_count == 1
assert mock_blur.call_args_list[0][0][1] == 7
@mock.patch("cv2.pyrMeanShiftFiltering")
def test_segmentation_size_is_0(self, mock_msf):
def _side_effect(image, sp, sr, dst):
dst[...] = image
mock_msf.side_effect = _side_effect
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
_ = iaa.stylize_cartoon(image, segmentation_size=0.0)
assert mock_msf.call_count == 0
@mock.patch("cv2.pyrMeanShiftFiltering")
def test_segmentation_size_gt_0(self, mock_msf):
def _side_effect(image, sp, sr, dst):
dst[...] = image
mock_msf.side_effect = _side_effect
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
_ = iaa.stylize_cartoon(image, segmentation_size=0.5)
assert mock_msf.call_count == 1
assert np.allclose(mock_msf.call_args_list[0][1]["sp"], 10*0.5)
assert np.allclose(mock_msf.call_args_list[0][1]["sr"], 20*0.5)
@mock.patch("imgaug.augmenters.artistic._suppress_edge_blobs")
def test_suppress_edges_true(self, mock_seb):
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
mock_seb.return_value = np.copy(image[..., 0])
_ = iaa.stylize_cartoon(image, suppress_edges=True)
assert mock_seb.call_count == 2
@mock.patch("imgaug.augmenters.artistic._suppress_edge_blobs")
def test_suppress_edges_false(self, mock_seb):
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
_ = iaa.stylize_cartoon(image, suppress_edges=False)
assert mock_seb.call_count == 0
@mock.patch("imgaug.augmenters.artistic._find_edges_laplacian")
def test_large_image(self, mock_fel):
def _side_effect_fel(image, edge_multiplier, from_colorspace):
return image[..., 0]
mock_fel.side_effect = _side_effect_fel
image = np.zeros((10, 401, 3), dtype=np.uint8)
_ = iaa.stylize_cartoon(image, segmentation_size=0)
assert mock_fel.call_count == 1
class Test__saturate(unittest.TestCase):
def _get_avg_saturation(self, image):
hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
return np.average(hsv[..., 1])
def test_saturation_is_1(self):
image = np.array([
[10, 20, 30],
[40, 50, 60],
[70, 80, 90],
[100, 110, 120],
[10, 10, 10],
[100, 0, 0],
[0, 100, 0],
[0, 0, 100]
], dtype=np.uint8).reshape((1, 8, 3))
observed_1 = artisticlib._saturate(image, 1.0, colorlib.CSPACE_RGB)
observed_2 = artisticlib._saturate(image, 2.0, colorlib.CSPACE_RGB)
sat_img = self._get_avg_saturation(image)
sat_1 = self._get_avg_saturation(observed_1)
sat_2 = self._get_avg_saturation(observed_2)
assert sat_img < sat_2
assert sat_1 < sat_2
class TestCartoon(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
aug = iaa.Cartoon()
assert aug.blur_ksize.a.value == 1
assert aug.blur_ksize.b.value == 5
assert np.isclose(aug.segmentation_size.a.value, 0.8)
assert np.isclose(aug.segmentation_size.b.value, 1.2)
assert np.isclose(aug.edge_prevalence.a.value, 0.9)
assert np.isclose(aug.edge_prevalence.b.value, 1.1)
assert np.isclose(aug.saturation.a.value, 1.5)
assert np.isclose(aug.saturation.b.value, 2.5)
assert aug.from_colorspace == iaa.CSPACE_RGB
def test_draw_samples(self):
mock_batch = mock.Mock()
mock_batch.nb_rows = 50
aug = iaa.Cartoon()
rs = iarandom.RNG(0)
samples = aug._draw_samples(mock_batch, rs)
assert len(np.unique(np.round(samples[0]*100, decimals=0))) > 1
assert len(np.unique(np.round(samples[1]*100, decimals=0))) > 1
assert len(np.unique(np.round(samples[2]*100, decimals=0))) > 1
assert len(np.unique(np.round(samples[3]*100, decimals=0))) > 1
@mock.patch("imgaug.augmenters.artistic.stylize_cartoon")
def test_call_of_stylize_cartoon(self, mock_sc):
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
aug = iaa.Cartoon()
mock_sc.return_value = np.copy(image)
_ = aug(images=[image, image])
assert mock_sc.call_count == 2
def test_get_parameters(self):
aug = iaa.Cartoon()
params = aug.get_parameters()
assert params[0] is aug.blur_ksize
assert params[1] is aug.segmentation_size
assert params[2] is aug.saturation
assert params[3] is aug.edge_prevalence
assert params[4] == iaa.CSPACE_RGB
def test_pickleable(self):
aug = iaa.Cartoon(seed=1)
runtest_pickleable_uint8_img(aug, iterations=6)
================================================
FILE: test/augmenters/test_blend.py
================================================
from __future__ import print_function, division, absolute_import
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
try:
import cPickle as pickle
except ImportError:
import pickle
import numpy as np
import six.moves as sm
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug.augmenters import blend
from imgaug.testutils import (
keypoints_equal, reseed, assert_cbaois_equal,
runtest_pickleable_uint8_img, is_parameter_instance)
from imgaug.augmentables.heatmaps import HeatmapsOnImage
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
from imgaug.augmentables.batches import _BatchInAugmentation
class Test_blend_alpha(unittest.TestCase):
def setUp(self):
reseed()
def test_alpha_is_1(self):
for dtype in ["uint8", "float32"]:
with self.subTest(dtype=dtype):
img_fg = np.full((3, 3, 1), 0, dtype=dtype)
img_bg = np.full((3, 3, 1), 255, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 1.0, eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == (3, 3, 1)
assert np.all(img_blend == 0)
def test_alpha_is_1_2d_arrays(self):
for dtype in ["uint8", "float32"]:
with self.subTest(dtype=dtype):
img_fg = np.full((3, 3), 0, dtype=dtype)
img_bg = np.full((3, 3), 255, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 1.0, eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == (3, 3)
assert np.all(img_blend == 0)
def test_alpha_is_0(self):
for dtype in ["uint8", "float32"]:
with self.subTest(dtype=dtype):
img_fg = np.full((3, 3, 1), 0, dtype=dtype)
img_bg = np.full((3, 3, 1), 255, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.0, eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == (3, 3, 1)
assert np.all(img_blend == 255)
def test_alpha_is_0_2d_arrays(self):
for dtype in ["uint8", "float32"]:
with self.subTest(dtype=dtype):
img_fg = np.full((3, 3), 0, dtype=dtype)
img_bg = np.full((3, 3), 255, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.0, eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == (3, 3)
assert np.all(img_blend == 255)
def test_alpha_is_030(self):
for dtype in ["uint8", "float32"]:
with self.subTest(dtype=dtype):
img_fg = np.full((3, 3, 1), 0, dtype=dtype)
img_bg = np.full((3, 3, 1), 255, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.3, eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == (3, 3, 1)
assert np.allclose(img_blend, 0.7*255, atol=1.01, rtol=0)
def test_alpha_is_030_2d_arrays(self):
for dtype in ["uint8", "float32"]:
with self.subTest(dtype=dtype):
img_fg = np.full((3, 3), 0, dtype=dtype)
img_bg = np.full((3, 3), 255, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.3, eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == (3, 3)
assert np.allclose(img_blend, 0.7*255, atol=1.01, rtol=0)
def test_alpha_is_030_with_11c_arrays(self):
for dtype in ["uint8", "float32"]:
for nb_channels in [None, 1, 3]:
with self.subTest(dtype=dtype, nb_channels=nb_channels):
shape = (1, 1, nb_channels)
if nb_channels is None:
shape = shape[0:2]
img_fg = np.full(shape, 0, dtype=dtype)
img_bg = np.full(shape, 255, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.3, eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == shape
assert np.allclose(img_blend, 0.7*255, atol=1.01, rtol=0)
def test_channelwise_alpha(self):
for dtype in ["uint8", "float32"]:
with self.subTest(dtype=dtype):
img_fg = np.full((3, 3, 2), 0, dtype=dtype)
img_bg = np.full((3, 3, 2), 255, dtype=dtype)
img_blend = blend.blend_alpha(
img_fg, img_bg, [1.0, 0.0], eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == (3, 3, 2)
assert np.all(img_blend[:, :, 0] == 0)
assert np.all(img_blend[:, :, 1] == 255)
def test_larger_images(self):
sizes = [(4, 4), (16, 16), (64, 64), (128, 128)]
for dtype in ["uint8", "float32"]:
for size in sizes:
shape = size + (3,)
for alphas_shape in [size, size + (1,), size + (3,)]:
with self.subTest(dtype=dtype, shape=shape,
alphas_shape=alphas_shape):
alphas = np.full(alphas_shape, 0.5, dtype=np.float32)
img_fg = np.full(shape, 0, dtype=dtype)
img_bg = np.full(shape, 255, dtype=dtype)
img_blend = blend.blend_alpha(
img_fg, img_bg, alphas, eps=0)
assert img_blend.dtype.name == dtype
assert img_blend.shape == shape
assert np.allclose(img_blend, 128, rtol=0, atol=1.01)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for dtype in ["uint8", "float32"]:
for shape in shapes:
with self.subTest(dtype=dtype, shape=shape):
image_fg = np.full(shape, 0, dtype=dtype)
image_bg = np.full(shape, 255, dtype=dtype)
image_aug = blend.blend_alpha(image_fg, image_bg, 1.0)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == dtype
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for dtype in ["uint8", "float32"]:
for shape in shapes:
with self.subTest(dtype=dtype, shape=shape):
image_fg = np.full(shape, 0, dtype=dtype)
image_bg = np.full(shape, 255, dtype=dtype)
image_aug = blend.blend_alpha(image_fg, image_bg, 1.0)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == dtype
assert image_aug.shape == shape
def test_other_dtypes_bool(self):
img_fg = np.full((3, 3, 1), 0, dtype=bool)
img_bg = np.full((3, 3, 1), 1, dtype=bool)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.3, eps=0)
assert img_blend.dtype.name == "bool"
assert img_blend.shape == (3, 3, 1)
assert np.all(img_blend == 1)
def test_other_dtypes_bool_2d_arrays(self):
img_fg = np.full((3, 3), 0, dtype=bool)
img_bg = np.full((3, 3), 1, dtype=bool)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.3, eps=0)
assert img_blend.dtype.name == "bool"
assert img_blend.shape == (3, 3)
assert np.all(img_blend == 1)
# TODO split this up into multiple tests
def test_other_dtypes_uint_int(self):
try:
high_res_dt = np.float128
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
except AttributeError:
# uint64 and int64 require float128 in their computation
high_res_dt = np.float64
dtypes = ["uint8", "uint16", "uint32",
"int8", "int16", "int32"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
dtype = np.dtype(dtype)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
values = [
(0, 0),
(0, 10),
(10, 20),
(min_value, min_value),
(max_value, max_value),
(min_value, max_value),
(min_value, int(center_value)),
(int(center_value), max_value),
(int(center_value + 0.20 * max_value), max_value),
(int(center_value + 0.27 * max_value), max_value),
(int(center_value + 0.40 * max_value), max_value),
(min_value, 0),
(0, max_value)
]
values = values + [(v2, v1) for v1, v2 in values]
for v1, v2 in values:
v1_scalar = np.full((), v1, dtype=dtype)
v2_scalar = np.full((), v2, dtype=dtype)
img_fg = np.full((3, 3, 1), v1, dtype=dtype)
img_bg = np.full((3, 3, 1), v2, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 1.0, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, 1)
assert np.all(img_blend == v1_scalar)
img_fg = np.full((3, 3, 1), v1, dtype=dtype)
img_bg = np.full((3, 3, 1), v2, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.99, eps=0.1)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, 1)
assert np.all(img_blend == v1_scalar)
img_fg = np.full((3, 3, 1), v1, dtype=dtype)
img_bg = np.full((3, 3, 1), v2, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.0, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, 1)
assert np.all(img_blend == v2_scalar)
# TODO this test breaks for numpy <1.15 -- why?
for c in sm.xrange(3):
img_fg = np.full((3, 3, c), v1, dtype=dtype)
img_bg = np.full((3, 3, c), v2, dtype=dtype)
img_blend = blend.blend_alpha(
img_fg, img_bg, 0.75, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, c)
for ci in sm.xrange(c):
v_blend = min(
max(
int(
0.75*high_res_dt(v1)
+ 0.25*high_res_dt(v2)
),
min_value
),
max_value)
diff = (
v_blend - img_blend
if v_blend > img_blend[0, 0, ci]
else img_blend - v_blend)
assert np.all(diff < 1.01)
img_fg = np.full((3, 3, 2), v1, dtype=dtype)
img_bg = np.full((3, 3, 2), v2, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.75, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, 2)
v_blend = min(
max(
int(
0.75 * high_res_dt(v1)
+ 0.25 * high_res_dt(v2)
),
min_value
),
max_value)
diff = (
v_blend - img_blend
if v_blend > img_blend[0, 0, 0]
else img_blend - v_blend
)
assert np.all(diff < 1.01)
img_fg = np.full((3, 3, 2), v1, dtype=dtype)
img_bg = np.full((3, 3, 2), v2, dtype=dtype)
img_blend = blend.blend_alpha(
img_fg, img_bg, [1.0, 0.0], eps=0.1)
assert img_blend.dtype.name == np.dtype(dtype).name
assert img_blend.shape == (3, 3, 2)
assert np.all(img_blend[:, :, 0] == v1_scalar)
assert np.all(img_blend[:, :, 1] == v2_scalar)
# elementwise, alphas.shape = (1, 2)
img_fg = np.full((1, 2, 3), v1, dtype=dtype)
img_bg = np.full((1, 2, 3), v2, dtype=dtype)
alphas = np.zeros((1, 2), dtype=np.float64)
alphas[:, :] = [1.0, 0.0]
img_blend = blend.blend_alpha(img_fg, img_bg, alphas, eps=0)
assert img_blend.dtype.name == np.dtype(dtype).name
assert img_blend.shape == (1, 2, 3)
assert np.all(img_blend[0, 0, :] == v1_scalar)
assert np.all(img_blend[0, 1, :] == v2_scalar)
# elementwise, alphas.shape = (1, 2, 1)
img_fg = np.full((1, 2, 3), v1, dtype=dtype)
img_bg = np.full((1, 2, 3), v2, dtype=dtype)
alphas = np.zeros((1, 2, 1), dtype=np.float64)
alphas[:, :, 0] = [1.0, 0.0]
img_blend = blend.blend_alpha(img_fg, img_bg, alphas, eps=0)
assert img_blend.dtype.name == np.dtype(dtype).name
assert img_blend.shape == (1, 2, 3)
assert np.all(img_blend[0, 0, :] == v1_scalar)
assert np.all(img_blend[0, 1, :] == v2_scalar)
# elementwise, alphas.shape = (1, 2, 3)
img_fg = np.full((1, 2, 3), v1, dtype=dtype)
img_bg = np.full((1, 2, 3), v2, dtype=dtype)
alphas = np.zeros((1, 2, 3), dtype=np.float64)
alphas[:, :, 0] = [1.0, 0.0]
alphas[:, :, 1] = [0.0, 1.0]
alphas[:, :, 2] = [1.0, 0.0]
img_blend = blend.blend_alpha(img_fg, img_bg, alphas, eps=0)
assert img_blend.dtype.name == np.dtype(dtype).name
assert img_blend.shape == (1, 2, 3)
assert np.all(img_blend[0, 0, [0, 2]] == v1_scalar)
assert np.all(img_blend[0, 1, [0, 2]] == v2_scalar)
assert np.all(img_blend[0, 0, 1] == v2_scalar)
assert np.all(img_blend[0, 1, 1] == v1_scalar)
# TODO split this up into multiple tests
def test_other_dtypes_float(self):
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64"]
except AttributeError:
# float64 requires float128 in the computation
high_res_dt = np.float64
dtypes = ["float16", "float32"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
dtype = np.dtype(dtype)
def _allclose(a, b):
atol = 1e-4 if dtype == np.float16 else 1e-8
return np.allclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
max_value = 1000 ** (isize - 1)
min_value = -max_value
center_value = 0
values = [
(0, 0),
(0, 10),
(10, 20),
(min_value, min_value),
(max_value, max_value),
(min_value, max_value),
(min_value, center_value),
(center_value, max_value),
(center_value + 0.20 * max_value, max_value),
(center_value + 0.27 * max_value, max_value),
(center_value + 0.40 * max_value, max_value),
(min_value, 0),
(0, max_value)
]
values = values + [(v2, v1) for v1, v2 in values]
max_float_dt = high_res_dt
for v1, v2 in values:
v1_scalar = np.full((), v1, dtype=dtype)
v2_scalar = np.full((), v2, dtype=dtype)
img_fg = np.full((3, 3, 1), v1, dtype=dtype)
img_bg = np.full((3, 3, 1), v2, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 1.0, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, 1)
assert _allclose(img_blend, max_float_dt(v1))
img_fg = np.full((3, 3, 1), v1, dtype=dtype)
img_bg = np.full((3, 3, 1), v2, dtype=dtype)
img_blend = blend.blend_alpha(
img_fg, img_bg, 0.99, eps=0.1)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, 1)
assert _allclose(img_blend, max_float_dt(v1))
img_fg = np.full((3, 3, 1), v1, dtype=dtype)
img_bg = np.full((3, 3, 1), v2, dtype=dtype)
img_blend = blend.blend_alpha(img_fg, img_bg, 0.0, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, 1)
assert _allclose(img_blend, max_float_dt(v2))
for c in sm.xrange(3):
img_fg = np.full((3, 3, c), v1, dtype=dtype)
img_bg = np.full((3, 3, c), v2, dtype=dtype)
img_blend = blend.blend_alpha(
img_fg, img_bg, 0.75, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, c)
assert _allclose(
img_blend,
0.75*max_float_dt(v1) + 0.25*max_float_dt(v2)
)
img_fg = np.full((3, 3, 2), v1, dtype=dtype)
img_bg = np.full((3, 3, 2), v2, dtype=dtype)
img_blend = blend.blend_alpha(
img_fg, img_bg, [1.0, 0.0], eps=0.1)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (3, 3, 2)
assert _allclose(img_blend[:, :, 0], max_float_dt(v1))
assert _allclose(img_blend[:, :, 1], max_float_dt(v2))
# elementwise, alphas.shape = (1, 2)
img_fg = np.full((1, 2, 3), v1, dtype=dtype)
img_bg = np.full((1, 2, 3), v2, dtype=dtype)
alphas = np.zeros((1, 2), dtype=np.float64)
alphas[:, :] = [1.0, 0.0]
img_blend = blend.blend_alpha(
img_fg, img_bg, alphas, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (1, 2, 3)
assert _allclose(img_blend[0, 0, :], v1_scalar)
assert _allclose(img_blend[0, 1, :], v2_scalar)
# elementwise, alphas.shape = (1, 2, 1)
img_fg = np.full((1, 2, 3), v1, dtype=dtype)
img_bg = np.full((1, 2, 3), v2, dtype=dtype)
alphas = np.zeros((1, 2, 1), dtype=np.float64)
alphas[:, :, 0] = [1.0, 0.0]
img_blend = blend.blend_alpha(
img_fg, img_bg, alphas, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (1, 2, 3)
assert _allclose(img_blend[0, 0, :], v1_scalar)
assert _allclose(img_blend[0, 1, :], v2_scalar)
# elementwise, alphas.shape = (1, 2, 3)
img_fg = np.full((1, 2, 3), v1, dtype=dtype)
img_bg = np.full((1, 2, 3), v2, dtype=dtype)
alphas = np.zeros((1, 2, 3), dtype=np.float64)
alphas[:, :, 0] = [1.0, 0.0]
alphas[:, :, 1] = [0.0, 1.0]
alphas[:, :, 2] = [1.0, 0.0]
img_blend = blend.blend_alpha(
img_fg, img_bg, alphas, eps=0)
assert img_blend.dtype.name == np.dtype(dtype)
assert img_blend.shape == (1, 2, 3)
assert _allclose(img_blend[0, 0, [0, 2]], v1_scalar)
assert _allclose(img_blend[0, 1, [0, 2]], v2_scalar)
assert _allclose(img_blend[0, 0, 1], v2_scalar)
assert _allclose(img_blend[0, 1, 1], v1_scalar)
class TestAlpha(unittest.TestCase):
def test_deprecation_warning(self):
aug1 = iaa.Sequential([])
aug2 = iaa.Sequential([])
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
aug = iaa.Alpha(0.75, first=aug1, second=aug2)
assert (
"is deprecated"
in str(caught_warnings[-1].message)
)
assert isinstance(aug, iaa.BlendAlpha)
assert np.isclose(aug.factor.value, 0.75)
assert aug.foreground is aug1
assert aug.background is aug2
class TestBlendAlpha(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
base_img = np.zeros((3, 3, 1), dtype=np.uint8)
return base_img
@property
def heatmaps(self):
heatmaps_arr = np.float32([[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0]])
return HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def heatmaps_r1(self):
heatmaps_arr_r1 = np.float32([[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 1.0]])
return HeatmapsOnImage(heatmaps_arr_r1, shape=(3, 3, 3))
@property
def heatmaps_l1(self):
heatmaps_arr_l1 = np.float32([[0.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0]])
return HeatmapsOnImage(heatmaps_arr_l1, shape=(3, 3, 3))
@property
def segmaps(self):
segmaps_arr = np.int32([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]])
return SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def segmaps_r1(self):
segmaps_arr_r1 = np.int32([[0, 0, 0],
[0, 0, 0],
[0, 0, 1]])
return SegmentationMapsOnImage(segmaps_arr_r1, shape=(3, 3, 3))
@property
def segmaps_l1(self):
segmaps_arr_l1 = np.int32([[0, 1, 0],
[0, 1, 0],
[1, 1, 0]])
return SegmentationMapsOnImage(segmaps_arr_l1, shape=(3, 3, 3))
@property
def kpsoi(self):
kps = [ia.Keypoint(x=5, y=10), ia.Keypoint(x=6, y=11)]
return ia.KeypointsOnImage(kps, shape=(20, 20, 3))
@property
def psoi(self):
ps = [ia.Polygon([(5, 5), (10, 5), (10, 10)])]
return ia.PolygonsOnImage(ps, shape=(20, 20, 3))
@property
def lsoi(self):
lss = [ia.LineString([(5, 5), (10, 5), (10, 10)])]
return ia.LineStringsOnImage(lss, shape=(20, 20, 3))
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)]
return ia.BoundingBoxesOnImage(bbs, shape=(20, 20, 3))
def test_images_factor_is_1(self):
aug = iaa.BlendAlpha(1, iaa.Add(10), iaa.Add(20))
observed = aug.augment_image(self.image)
expected = np.round(self.image + 10).astype(np.uint8)
assert np.allclose(observed, expected)
def test_heatmaps_factor_is_1_with_affines_and_per_channel(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
aug = iaa.BlendAlpha(
1,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}),
per_channel=per_channel)
observed = aug.augment_heatmaps([self.heatmaps])[0]
assert observed.shape == self.heatmaps.shape
assert 0 - 1e-6 < self.heatmaps.min_value < 0 + 1e-6
assert 1 - 1e-6 < self.heatmaps.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(),
self.heatmaps_r1.get_arr())
def test_segmaps_factor_is_1_with_affines_and_per_channel(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
aug = iaa.BlendAlpha(
1,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}),
per_channel=per_channel)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
assert observed.shape == self.segmaps.shape
assert np.array_equal(observed.get_arr(),
self.segmaps_r1.get_arr())
def test_images_factor_is_0(self):
aug = iaa.BlendAlpha(0, iaa.Add(10), iaa.Add(20))
observed = aug.augment_image(self.image)
expected = np.round(self.image + 20).astype(np.uint8)
assert np.allclose(observed, expected)
def test_heatmaps_factor_is_0_with_affines_and_per_channel(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
aug = iaa.BlendAlpha(
0,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}),
per_channel=per_channel)
observed = aug.augment_heatmaps([self.heatmaps])[0]
assert observed.shape == self.heatmaps.shape
assert 0 - 1e-6 < self.heatmaps.min_value < 0 + 1e-6
assert 1 - 1e-6 < self.heatmaps.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(),
self.heatmaps_l1.get_arr())
def test_segmaps_factor_is_0_with_affines_and_per_channel(self):
for per_channel in [False, True]:
with self.subTest(per_channel=per_channel):
aug = iaa.BlendAlpha(
0,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}),
per_channel=per_channel)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
assert observed.shape == self.segmaps.shape
assert np.array_equal(observed.get_arr(),
self.segmaps_l1.get_arr())
def test_images_factor_is_075(self):
aug = iaa.BlendAlpha(0.75, iaa.Add(10), iaa.Add(20))
observed = aug.augment_image(self.image)
expected = np.round(
self.image
+ 0.75 * 10
+ 0.25 * 20
).astype(np.uint8)
assert np.allclose(observed, expected)
def test_images_factor_is_075_fg_branch_is_none(self):
aug = iaa.BlendAlpha(0.75, None, iaa.Add(20))
observed = aug.augment_image(self.image + 10)
expected = np.round(
self.image
+ 0.75 * 10
+ 0.25 * (10 + 20)
).astype(np.uint8)
assert np.allclose(observed, expected)
def test_images_factor_is_075_bg_branch_is_none(self):
aug = iaa.BlendAlpha(0.75, iaa.Add(10), None)
observed = aug.augment_image(self.image + 10)
expected = np.round(
self.image
+ 0.75 * (10 + 10)
+ 0.25 * 10
).astype(np.uint8)
assert np.allclose(observed, expected)
def test_images_factor_is_tuple(self):
image = np.zeros((1, 2, 1), dtype=np.uint8)
nb_iterations = 1000
aug = iaa.BlendAlpha((0.0, 1.0), iaa.Add(10), iaa.Add(110))
values = []
for _ in sm.xrange(nb_iterations):
observed = aug.augment_image(image)
observed_val = np.round(np.average(observed)) - 10
values.append(observed_val / 100)
nb_bins = 5
hist, _ = np.histogram(values, bins=nb_bins, range=(0.0, 1.0),
density=False)
density_expected = 1.0/nb_bins
density_tolerance = 0.05
for nb_samples in hist:
density = nb_samples / nb_iterations
assert np.isclose(density, density_expected,
rtol=0, atol=density_tolerance)
def test_bad_datatype_for_factor_fails(self):
got_exception = False
try:
_ = iaa.BlendAlpha(False, iaa.Add(10), None)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_images_with_per_channel_in_both_alpha_and_child(self):
image = np.zeros((1, 1, 1000), dtype=np.uint8)
aug = iaa.BlendAlpha(
1.0,
iaa.Add((0, 100), per_channel=True),
None,
per_channel=True)
observed = aug.augment_image(image)
uq = np.unique(observed)
assert len(uq) > 1
assert np.max(observed) > 80
assert np.min(observed) < 20
def test_images_with_per_channel_in_alpha_and_tuple_as_factor(self):
image = np.zeros((1, 1, 1000), dtype=np.uint8)
aug = iaa.BlendAlpha(
(0.0, 1.0),
iaa.Add(100),
None,
per_channel=True)
observed = aug.augment_image(image)
uq = np.unique(observed)
assert len(uq) > 1
assert np.max(observed) > 80
assert np.min(observed) < 20
def test_images_float_as_per_channel_tuple_as_factor_two_branches(self):
aug = iaa.BlendAlpha(
(0.0, 1.0),
iaa.Add(100),
iaa.Add(0),
per_channel=0.5)
seen = [0, 0]
for _ in sm.xrange(200):
observed = aug.augment_image(np.zeros((1, 1, 100), dtype=np.uint8))
uq = np.unique(observed)
if len(uq) == 1:
seen[0] += 1
elif len(uq) > 1:
seen[1] += 1
else:
assert False
assert 100 - 50 < seen[0] < 100 + 50
assert 100 - 50 < seen[1] < 100 + 50
def test_bad_datatype_for_per_channel_fails(self):
# bad datatype for per_channel
got_exception = False
try:
_ = iaa.BlendAlpha(0.5, iaa.Add(10), None, per_channel="test")
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_hooks_limiting_propagation(self):
aug = iaa.BlendAlpha(0.5, iaa.Add(100), iaa.Add(50), name="AlphaTest")
def propagator(images, augmenter, parents, default):
if "Alpha" in augmenter.name:
return False
else:
return default
hooks = ia.HooksImages(propagator=propagator)
image = np.zeros((10, 10, 3), dtype=np.uint8) + 1
observed = aug.augment_image(image, hooks=hooks)
assert np.array_equal(observed, image)
def test_keypoints_factor_is_1(self):
self._test_cba_factor_is_1("augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_0501(self):
self._test_cba_factor_is_0501("augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_0(self):
self._test_cba_factor_is_0("augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_0499(self):
self._test_cba_factor_is_0499("augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_1_with_per_channel(self):
self._test_cba_factor_is_1_and_per_channel(
"augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_0_with_per_channel(self):
self._test_cba_factor_is_0_and_per_channel(
"augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_choice_of_vals_close_to_050_per_channel(self):
self._test_cba_factor_is_choice_around_050_and_per_channel(
"augment_keypoints", self.kpsoi)
def test_keypoints_are_empty(self):
self._test_empty_cba(
"augment_keypoints", ia.KeypointsOnImage([], shape=(1, 2, 3)))
def test_keypoints_hooks_limit_propagation(self):
self._test_cba_hooks_limit_propagation(
"augment_keypoints", self.kpsoi)
def test_polygons_factor_is_1(self):
self._test_cba_factor_is_1("augment_polygons", self.psoi)
def test_polygons_factor_is_0501(self):
self._test_cba_factor_is_0501("augment_polygons", self.psoi)
def test_polygons_factor_is_0(self):
self._test_cba_factor_is_0("augment_polygons", self.psoi)
def test_polygons_factor_is_0499(self):
self._test_cba_factor_is_0499("augment_polygons", self.psoi)
def test_polygons_factor_is_1_and_per_channel(self):
self._test_cba_factor_is_1_and_per_channel(
"augment_polygons", self.psoi)
def test_polygons_factor_is_0_and_per_channel(self):
self._test_cba_factor_is_0_and_per_channel(
"augment_polygons", self.psoi)
def test_polygons_factor_is_choice_around_050_and_per_channel(self):
self._test_cba_factor_is_choice_around_050_and_per_channel(
"augment_polygons", self.psoi
)
def test_empty_polygons(self):
return self._test_empty_cba(
"augment_polygons", ia.PolygonsOnImage([], shape=(1, 2, 3)))
def test_polygons_hooks_limit_propagation(self):
return self._test_cba_hooks_limit_propagation(
"augment_polygons", self.psoi)
def test_line_strings_factor_is_1(self):
self._test_cba_factor_is_1("augment_line_strings", self.lsoi)
def test_line_strings_factor_is_0501(self):
self._test_cba_factor_is_0501("augment_line_strings", self.lsoi)
def test_line_strings_factor_is_0(self):
self._test_cba_factor_is_0("augment_line_strings", self.lsoi)
def test_line_strings_factor_is_0499(self):
self._test_cba_factor_is_0499("augment_line_strings", self.lsoi)
def test_line_strings_factor_is_1_and_per_channel(self):
self._test_cba_factor_is_1_and_per_channel(
"augment_line_strings", self.lsoi)
def test_line_strings_factor_is_0_and_per_channel(self):
self._test_cba_factor_is_0_and_per_channel(
"augment_line_strings", self.lsoi)
def test_line_strings_factor_is_choice_around_050_and_per_channel(self):
self._test_cba_factor_is_choice_around_050_and_per_channel(
"augment_line_strings", self.lsoi
)
def test_empty_line_strings(self):
return self._test_empty_cba(
"augment_line_strings",
ia.LineStringsOnImage([], shape=(1, 2, 3)))
def test_line_strings_hooks_limit_propagation(self):
return self._test_cba_hooks_limit_propagation(
"augment_line_strings", self.lsoi)
def test_bounding_boxes_factor_is_1(self):
self._test_cba_factor_is_1("augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_0501(self):
self._test_cba_factor_is_0501("augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_0(self):
self._test_cba_factor_is_0("augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_0499(self):
self._test_cba_factor_is_0499("augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_1_and_per_channel(self):
self._test_cba_factor_is_1_and_per_channel(
"augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_0_and_per_channel(self):
self._test_cba_factor_is_0_and_per_channel(
"augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_choice_around_050_and_per_channel(self):
self._test_cba_factor_is_choice_around_050_and_per_channel(
"augment_bounding_boxes", self.bbsoi
)
def test_empty_bounding_boxes(self):
return self._test_empty_cba(
"augment_bounding_boxes",
ia.BoundingBoxesOnImage([], shape=(1, 2, 3)))
def test_bounding_boxes_hooks_limit_propagation(self):
return self._test_cba_hooks_limit_propagation(
"augment_bounding_boxes", self.bbsoi)
# Tests for CBA (=coordinate based augmentable) below. This currently
# covers keypoints, polygons and bounding boxes.
@classmethod
def _test_cba_factor_is_1(cls, augf_name, cbaoi):
aug = iaa.BlendAlpha(
1.0, iaa.Identity(), iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)([cbaoi])
assert_cbaois_equal(observed[0], cbaoi)
@classmethod
def _test_cba_factor_is_0501(cls, augf_name, cbaoi):
aug = iaa.BlendAlpha(0.501,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)([cbaoi])
assert_cbaois_equal(observed[0], cbaoi)
@classmethod
def _test_cba_factor_is_0(cls, augf_name, cbaoi):
aug = iaa.BlendAlpha(
0.0, iaa.Identity(), iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)([cbaoi])
expected = cbaoi.shift(x=1)
assert_cbaois_equal(observed[0], expected)
@classmethod
def _test_cba_factor_is_0499(cls, augf_name, cbaoi):
aug = iaa.BlendAlpha(0.499,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)([cbaoi])
expected = cbaoi.shift(x=1)
assert_cbaois_equal(observed[0], expected)
@classmethod
def _test_cba_factor_is_1_and_per_channel(cls, augf_name, cbaoi):
aug = iaa.BlendAlpha(
1.0,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}),
per_channel=True)
observed = getattr(aug, augf_name)([cbaoi])
assert_cbaois_equal(observed[0], cbaoi)
@classmethod
def _test_cba_factor_is_0_and_per_channel(cls, augf_name, cbaoi):
aug = iaa.BlendAlpha(
0.0,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}),
per_channel=True)
observed = getattr(aug, augf_name)([cbaoi])
expected = cbaoi.shift(x=1)
assert_cbaois_equal(observed[0], expected)
@classmethod
def _test_cba_factor_is_choice_around_050_and_per_channel(
cls, augf_name, cbaoi):
aug = iaa.BlendAlpha(
iap.Choice([0.49, 0.51]),
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}),
per_channel=True)
expected_same = cbaoi.deepcopy()
expected_shifted = cbaoi.shift(x=1)
seen = [0, 0, 0]
for _ in sm.xrange(200):
observed = getattr(aug, augf_name)([cbaoi])[0]
assert len(observed.items) == len(expected_same.items)
assert len(observed.items) == len(expected_shifted.items)
# We use here allclose() instead of coords_almost_equals()
# as the latter one is much slower for polygons and we don't have
# to deal with tricky geometry changes here, just naive shifting.
if np.allclose(observed.items[0].coords,
expected_same.items[0].coords,
rtol=0, atol=0.1):
seen[0] += 1
elif np.allclose(observed.items[0].coords,
expected_shifted.items[0].coords,
rtol=0, atol=0.1):
seen[1] += 1
else:
seen[2] += 1
assert 100 - 50 < seen[0] < 100 + 50
assert 100 - 50 < seen[1] < 100 + 50
assert seen[2] == 0
@classmethod
def _test_empty_cba(cls, augf_name, cbaoi):
# empty CBAs
aug = iaa.BlendAlpha(0.501,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)(cbaoi)
assert len(observed.items) == 0
assert observed.shape == cbaoi.shape
@classmethod
def _test_cba_hooks_limit_propagation(cls, augf_name, cbaoi):
aug = iaa.BlendAlpha(
0.0,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"y": 1}),
name="AlphaTest")
def propagator(cbaoi_to_aug, augmenter, parents, default):
if "Alpha" in augmenter.name:
return False
else:
return default
# no hooks for polygons yet, so we use HooksKeypoints
hooks = ia.HooksKeypoints(propagator=propagator)
observed = getattr(aug, augf_name)([cbaoi], hooks=hooks)[0]
assert observed.items[0].coords_almost_equals(cbaoi.items[0])
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlpha(1.0, iaa.Add(1), iaa.Add(100))
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlpha(1.0, iaa.Add(1), iaa.Add(100))
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
fg = iaa.Identity()
bg = iaa.Sequential([iaa.Add(1)])
aug = iaa.BlendAlpha(0.65, fg, bg, per_channel=1)
params = aug.get_parameters()
assert params[0] is aug.factor
assert params[1] is aug.per_channel
assert 0.65 - 1e-6 < params[0].value < 0.65 + 1e-6
assert params[1].value == 1
def test_get_children_lists(self):
fg = iaa.Identity()
bg = iaa.Sequential([iaa.Add(1)])
aug = iaa.BlendAlpha(0.65, fg, bg, per_channel=1)
children_lsts = aug.get_children_lists()
assert len(children_lsts) == 2
assert ia.is_iterable([lst for lst in children_lsts])
assert fg in children_lsts[0]
assert bg == children_lsts[1]
def test_to_deterministic(self):
class _DummyAugmenter(iaa.Identity):
def __init__(self, *args, **kwargs):
super(_DummyAugmenter, self).__init__(*args, **kwargs)
self.deterministic_called = False
def _to_deterministic(self):
self.deterministic_called = True
return self
identity1 = _DummyAugmenter()
identity2 = _DummyAugmenter()
aug = iaa.BlendAlpha(0.5, identity1, identity2)
aug_det = aug.to_deterministic()
assert aug_det.deterministic
assert aug_det.random_state is not aug.random_state
assert aug_det.foreground.deterministic
assert aug_det.background.deterministic
assert identity1.deterministic_called is True
assert identity2.deterministic_called is True
def test_pickleable(self):
aug = iaa.BlendAlpha(
(0.1, 0.9),
iaa.Add((1, 10), seed=1),
iaa.Add((11, 20), seed=2),
per_channel=True,
seed=3)
runtest_pickleable_uint8_img(aug, iterations=10)
class _DummyMaskParameter(iap.StochasticParameter):
def __init__(self, inverted=False):
super(_DummyMaskParameter, self).__init__()
self.inverted = inverted
def _draw_samples(self, size, random_state):
h, w = size[0:2]
nb_channels = 1 if len(size) == 2 else size[2]
assert nb_channels <= 3
result = []
for i in np.arange(nb_channels):
if i == 0:
result.append(np.zeros((h, w), dtype=np.float32))
else:
result.append(np.ones((h, w), dtype=np.float32))
result = np.stack(result, axis=-1)
if len(size) == 2:
result = result[:, :, 0]
if self.inverted:
result = 1.0 - result
return result
class TestAlphaElementwise(unittest.TestCase):
def test_deprecation_warning(self):
aug1 = iaa.Sequential([])
aug2 = iaa.Sequential([])
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
aug = iaa.AlphaElementwise(factor=0.5, first=aug1, second=aug2)
assert (
"is deprecated"
in str(caught_warnings[-1].message)
)
assert isinstance(aug, iaa.BlendAlphaElementwise)
assert np.isclose(aug.factor.value, 0.5)
assert aug.foreground is aug1
assert aug.background is aug2
# TODO add tests for heatmaps and segmaps that differ from the image size
class TestBlendAlphaElementwise(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
base_img = np.zeros((3, 3, 1), dtype=np.uint8)
return base_img
@property
def heatmaps(self):
heatmaps_arr = np.float32([[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0]])
return HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def heatmaps_r1(self):
heatmaps_arr_r1 = np.float32([[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 1.0]])
return HeatmapsOnImage(heatmaps_arr_r1, shape=(3, 3, 3))
@property
def heatmaps_l1(self):
heatmaps_arr_l1 = np.float32([[0.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[1.0, 1.0, 0.0]])
return HeatmapsOnImage(heatmaps_arr_l1, shape=(3, 3, 3))
@property
def segmaps(self):
segmaps_arr = np.int32([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]])
return SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def segmaps_r1(self):
segmaps_arr_r1 = np.int32([[0, 0, 0],
[0, 0, 0],
[0, 0, 1]])
return SegmentationMapsOnImage(segmaps_arr_r1, shape=(3, 3, 3))
@property
def segmaps_l1(self):
segmaps_arr_l1 = np.int32([[0, 1, 0],
[0, 1, 0],
[1, 1, 0]])
return SegmentationMapsOnImage(segmaps_arr_l1, shape=(3, 3, 3))
@property
def kpsoi(self):
kps = [ia.Keypoint(x=5, y=10), ia.Keypoint(x=6, y=11)]
return ia.KeypointsOnImage(kps, shape=(20, 20, 3))
@property
def psoi(self):
ps = [ia.Polygon([(5, 5), (10, 5), (10, 10)])]
return ia.PolygonsOnImage(ps, shape=(20, 20, 3))
@property
def lsoi(self):
lss = [ia.LineString([(5, 5), (10, 5), (10, 10)])]
return ia.LineStringsOnImage(lss, shape=(20, 20, 3))
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=5, y1=6, x2=7, y2=8)]
return ia.BoundingBoxesOnImage(bbs, shape=(20, 20, 3))
def test_images_factor_is_1(self):
aug = iaa.BlendAlphaElementwise(1, iaa.Add(10), iaa.Add(20))
observed = aug.augment_image(self.image)
expected = self.image + 10
assert np.allclose(observed, expected)
def test_heatmaps_factor_is_1_with_affines(self):
aug = iaa.BlendAlphaElementwise(
1,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}))
observed = aug.augment_heatmaps([self.heatmaps])[0]
assert observed.shape == (3, 3, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps_r1.get_arr())
def test_segmaps_factor_is_1_with_affines(self):
aug = iaa.BlendAlphaElementwise(
1,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}))
observed = aug.augment_segmentation_maps([self.segmaps])[0]
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.get_arr(), self.segmaps_r1.get_arr())
def test_images_factor_is_0(self):
aug = iaa.BlendAlphaElementwise(0, iaa.Add(10), iaa.Add(20))
observed = aug.augment_image(self.image)
expected = self.image + 20
assert np.allclose(observed, expected)
def test_heatmaps_factor_is_0_with_affines(self):
aug = iaa.BlendAlphaElementwise(
0,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}))
observed = aug.augment_heatmaps([self.heatmaps])[0]
assert observed.shape == (3, 3, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps_l1.get_arr())
def test_segmaps_factor_is_0_with_affines(self):
aug = iaa.BlendAlphaElementwise(
0,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}))
observed = aug.augment_segmentation_maps([self.segmaps])[0]
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.get_arr(), self.segmaps_l1.get_arr())
def test_images_factor_is_075(self):
aug = iaa.BlendAlphaElementwise(0.75, iaa.Add(10), iaa.Add(20))
observed = aug.augment_image(self.image)
expected = np.round(
self.image + 0.75 * 10 + 0.25 * 20
).astype(np.uint8)
assert np.allclose(observed, expected, atol=1.01)
def test_images_factor_is_075_fg_branch_is_none(self):
aug = iaa.BlendAlphaElementwise(0.75, None, iaa.Add(20))
observed = aug.augment_image(self.image + 10)
expected = np.round(
self.image + 0.75 * 10 + 0.25 * (10 + 20)
).astype(np.uint8)
assert np.allclose(observed, expected, atol=1.01)
def test_images_factor_is_075_bg_branch_is_none(self):
aug = iaa.BlendAlphaElementwise(0.75, iaa.Add(10), None)
observed = aug.augment_image(self.image + 10)
expected = np.round(
self.image + 0.75 * (10 + 10) + 0.25 * 10
).astype(np.uint8)
assert np.allclose(observed, expected, atol=1.01)
def test_images_factor_is_tuple(self):
image = np.zeros((100, 100), dtype=np.uint8)
aug = iaa.BlendAlphaElementwise((0.0, 1.0), iaa.Add(10), iaa.Add(110))
observed = (aug.augment_image(image) - 10) / 100
nb_bins = 10
hist, _ = np.histogram(
observed.flatten(), bins=nb_bins, range=(0.0, 1.0), density=False)
density_expected = 1.0/nb_bins
density_tolerance = 0.05
for nb_samples in hist:
density = nb_samples / observed.size
assert np.isclose(density, density_expected,
rtol=0, atol=density_tolerance)
def test_bad_datatype_for_factor_fails(self):
got_exception = False
try:
_ = iaa.BlendAlphaElementwise(False, iaa.Add(10), None)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_images_with_per_channel_in_alpha_and_tuple_as_factor(self):
image = np.zeros((1, 1, 100), dtype=np.uint8)
aug = iaa.BlendAlphaElementwise(
(0.0, 1.0),
iaa.Add(10),
iaa.Add(110),
per_channel=True)
observed = aug.augment_image(image)
assert len(set(observed.flatten())) > 1
def test_bad_datatype_for_per_channel_fails(self):
got_exception = False
try:
_ = iaa.BlendAlphaElementwise(
0.5,
iaa.Add(10),
None,
per_channel="test")
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_hooks_limiting_propagation(self):
aug = iaa.BlendAlphaElementwise(
0.5,
iaa.Add(100),
iaa.Add(50),
name="AlphaElementwiseTest")
def propagator(images, augmenter, parents, default):
if "AlphaElementwise" in augmenter.name:
return False
else:
return default
hooks = ia.HooksImages(propagator=propagator)
image = np.zeros((10, 10, 3), dtype=np.uint8) + 10
observed = aug.augment_image(image, hooks=hooks)
assert np.array_equal(observed, image)
def test_heatmaps_and_per_channel_factor_is_zeros(self):
aug = iaa.BlendAlphaElementwise(
_DummyMaskParameter(inverted=False),
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}),
per_channel=True)
observed = aug.augment_heatmaps([self.heatmaps])[0]
assert observed.shape == (3, 3, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps_r1.get_arr())
def test_heatmaps_and_per_channel_factor_is_ones(self):
aug = iaa.BlendAlphaElementwise(
_DummyMaskParameter(inverted=True),
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}),
per_channel=True)
observed = aug.augment_heatmaps([self.heatmaps])[0]
assert observed.shape == (3, 3, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps_l1.get_arr())
def test_segmaps_and_per_channel_factor_is_zeros(self):
aug = iaa.BlendAlphaElementwise(
_DummyMaskParameter(inverted=False),
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}),
per_channel=True)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.get_arr(), self.segmaps_r1.get_arr())
def test_segmaps_and_per_channel_factor_is_ones(self):
aug = iaa.BlendAlphaElementwise(
_DummyMaskParameter(inverted=True),
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": -1}),
per_channel=True)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.get_arr(), self.segmaps_l1.get_arr())
def test_keypoints_factor_is_1(self):
self._test_cba_factor_is_1("augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_0501(self):
self._test_cba_factor_is_0501("augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_0(self):
self._test_cba_factor_is_0("augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_0499(self):
self._test_cba_factor_is_0499("augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_1_with_per_channel(self):
self._test_cba_factor_is_1_and_per_channel(
"augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_0_with_per_channel(self):
self._test_cba_factor_is_0_and_per_channel(
"augment_keypoints", self.kpsoi)
def test_keypoints_factor_is_choice_of_vals_close_050_per_channel(self):
# TODO can this somehow be integrated into the CBA functions below?
aug = iaa.BlendAlpha(
iap.Choice([0.49, 0.51]),
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}),
per_channel=True)
kpsoi = self.kpsoi
expected_same = kpsoi.deepcopy()
expected_both_shifted = kpsoi.shift(x=1)
expected_fg_shifted = ia.KeypointsOnImage(
[kpsoi.keypoints[0].shift(x=1), kpsoi.keypoints[1]],
shape=self.kpsoi.shape)
expected_bg_shifted = ia.KeypointsOnImage(
[kpsoi.keypoints[0], kpsoi.keypoints[1].shift(x=1)],
shape=self.kpsoi.shape)
seen = [0, 0]
for _ in sm.xrange(200):
observed = aug.augment_keypoints([kpsoi])[0]
if keypoints_equal([observed], [expected_same]):
seen[0] += 1
elif keypoints_equal([observed], [expected_both_shifted]):
seen[1] += 1
elif keypoints_equal([observed], [expected_fg_shifted]):
seen[2] += 1
elif keypoints_equal([observed], [expected_bg_shifted]):
seen[3] += 1
else:
assert False
assert 100 - 50 < seen[0] < 100 + 50
assert 100 - 50 < seen[1] < 100 + 50
def test_keypoints_are_empty(self):
kpsoi = ia.KeypointsOnImage([], shape=(1, 2, 3))
self._test_empty_cba("augment_keypoints", kpsoi)
def test_keypoints_hooks_limit_propagation(self):
self._test_cba_hooks_limit_propagation("augment_keypoints", self.kpsoi)
def test_polygons_factor_is_1(self):
self._test_cba_factor_is_1("augment_polygons", self.psoi)
def test_polygons_factor_is_0501(self):
self._test_cba_factor_is_0501("augment_polygons", self.psoi)
def test_polygons_factor_is_0(self):
self._test_cba_factor_is_0("augment_polygons", self.psoi)
def test_polygons_factor_is_0499(self):
self._test_cba_factor_is_0499("augment_polygons", self.psoi)
def test_polygons_factor_is_1_and_per_channel(self):
self._test_cba_factor_is_1_and_per_channel(
"augment_polygons", self.psoi)
def test_polygons_factor_is_0_and_per_channel(self):
self._test_cba_factor_is_0_and_per_channel(
"augment_polygons", self.psoi)
def test_polygons_factor_is_choice_around_050_and_per_channel(self):
# We use more points here to verify the
# either-or-mode (pointwise=False). The probability that all points
# move in the same way be coincidence is extremely low for so many.
ps = [ia.Polygon([(0, 0), (15, 0), (10, 0), (10, 5), (10, 10),
(5, 10), (5, 5), (0, 10), (0, 5), (0, 0)])]
psoi = ia.PolygonsOnImage(ps, shape=(15, 15, 3))
self._test_cba_factor_is_choice_around_050_and_per_channel(
"augment_polygons", psoi, pointwise=False
)
def test_empty_polygons(self):
psoi = ia.PolygonsOnImage([], shape=(1, 2, 3))
self._test_empty_cba("augment_polygons", psoi)
def test_polygons_hooks_limit_propagation(self):
self._test_cba_hooks_limit_propagation("augment_polygons", self.psoi)
def test_line_strings_factor_is_1(self):
self._test_cba_factor_is_1("augment_line_strings", self.lsoi)
def test_line_strings_factor_is_0501(self):
self._test_cba_factor_is_0501("augment_line_strings", self.lsoi)
def test_line_strings_factor_is_0(self):
self._test_cba_factor_is_0("augment_line_strings", self.lsoi)
def test_line_strings_factor_is_0499(self):
self._test_cba_factor_is_0499("augment_line_strings", self.lsoi)
def test_line_strings_factor_is_1_and_per_channel(self):
self._test_cba_factor_is_1_and_per_channel(
"augment_line_strings", self.lsoi)
def test_line_strings_factor_is_0_and_per_channel(self):
self._test_cba_factor_is_0_and_per_channel(
"augment_line_strings", self.lsoi)
def test_line_strings_factor_is_choice_around_050_and_per_channel(self):
# see same polygons test for why self.lsoi is not used here
lss = [ia.LineString([(0, 0), (15, 0), (10, 0), (10, 5), (10, 10),
(5, 10), (5, 5), (0, 10), (0, 5), (0, 0)])]
lsoi = ia.LineStringsOnImage(lss, shape=(15, 15, 3))
self._test_cba_factor_is_choice_around_050_and_per_channel(
"augment_line_strings", lsoi, pointwise=False
)
def test_empty_line_strings(self):
lsoi = ia.LineStringsOnImage([], shape=(1, 2, 3))
self._test_empty_cba("augment_line_strings", lsoi)
def test_line_strings_hooks_limit_propagation(self):
self._test_cba_hooks_limit_propagation(
"augment_line_strings", self.lsoi)
def test_bounding_boxes_factor_is_1(self):
self._test_cba_factor_is_1("augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_0501(self):
self._test_cba_factor_is_0501("augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_0(self):
self._test_cba_factor_is_0("augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_0499(self):
self._test_cba_factor_is_0499("augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_1_and_per_channel(self):
self._test_cba_factor_is_1_and_per_channel(
"augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_0_and_per_channel(self):
self._test_cba_factor_is_0_and_per_channel(
"augment_bounding_boxes", self.bbsoi)
def test_bounding_boxes_factor_is_choice_around_050_and_per_channel(self):
# TODO pointwise=True or False makes no difference here, because
# there aren't enough points (see corresponding polygon test)
self._test_cba_factor_is_choice_around_050_and_per_channel(
"augment_bounding_boxes", self.bbsoi, pointwise=False
)
def test_empty_bounding_boxes(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(1, 2, 3))
self._test_empty_cba("augment_bounding_boxes", bbsoi)
def test_bounding_boxes_hooks_limit_propagation(self):
self._test_cba_hooks_limit_propagation(
"augment_bounding_boxes", self.bbsoi)
@classmethod
def _test_cba_factor_is_1(cls, augf_name, cbaoi):
aug = iaa.BlendAlphaElementwise(
1.0,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)([cbaoi])
assert_cbaois_equal(observed[0], cbaoi)
@classmethod
def _test_cba_factor_is_0501(cls, augf_name, cbaoi):
aug = iaa.BlendAlphaElementwise(
0.501,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)([cbaoi])
assert_cbaois_equal(observed[0], cbaoi)
@classmethod
def _test_cba_factor_is_0(cls, augf_name, cbaoi):
aug = iaa.BlendAlphaElementwise(
0.0,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)([cbaoi])
expected = cbaoi.shift(x=1)
assert_cbaois_equal(observed[0], expected)
@classmethod
def _test_cba_factor_is_0499(cls, augf_name, cbaoi):
aug = iaa.BlendAlphaElementwise(
0.499,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)([cbaoi])
expected = cbaoi.shift(x=1)
assert_cbaois_equal(observed[0], expected)
@classmethod
def _test_cba_factor_is_1_and_per_channel(cls, augf_name, cbaoi):
aug = iaa.BlendAlphaElementwise(
1.0,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}),
per_channel=True)
observed = getattr(aug, augf_name)([cbaoi])
assert_cbaois_equal(observed[0], cbaoi)
@classmethod
def _test_cba_factor_is_0_and_per_channel(cls, augf_name, cbaoi):
aug = iaa.BlendAlphaElementwise(
0.0,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}),
per_channel=True)
observed = getattr(aug, augf_name)([cbaoi])
expected = cbaoi.shift(x=1)
assert_cbaois_equal(observed[0], expected)
@classmethod
def _test_cba_factor_is_choice_around_050_and_per_channel(
cls, augf_name, cbaoi, pointwise):
aug = iaa.BlendAlphaElementwise(
iap.Choice([0.49, 0.51]),
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}),
per_channel=True)
expected_same = cbaoi.deepcopy()
expected_shifted = cbaoi.shift(x=1)
nb_iterations = 400
seen = [0, 0, 0]
for _ in sm.xrange(nb_iterations):
observed = getattr(aug, augf_name)([cbaoi])[0]
# We use here allclose() instead of coords_almost_equals()
# as the latter one is much slower for polygons and we don't have
# to deal with tricky geometry changes here, just naive shifting.
if np.allclose(observed.items[0].coords,
expected_same.items[0].coords,
rtol=0, atol=0.1):
seen[0] += 1
elif np.allclose(observed.items[0].coords,
expected_shifted.items[0].coords,
rtol=0, atol=0.1):
seen[1] += 1
else:
seen[2] += 1
if pointwise:
# This code can be used if the polygon augmentation mode is
# AlphaElementwise._MODE_POINTWISE. Currently it is _MODE_EITHER_OR.
nb_points = len(cbaoi.items[0].coords)
p_all_same = 2 * ((1/2)**nb_points) # all points moved in same way
expected_iter = nb_iterations*p_all_same
expected_iter_notsame = nb_iterations*(1-p_all_same)
atol = nb_iterations * (5*p_all_same)
assert np.isclose(seen[0], expected_iter, rtol=0, atol=atol)
assert np.isclose(seen[1], expected_iter, rtol=0, atol=atol)
assert np.isclose(seen[2], expected_iter_notsame, rtol=0, atol=atol)
else:
expected_iter = nb_iterations*0.5
atol = nb_iterations*0.15
assert np.isclose(seen[0], expected_iter, rtol=0, atol=atol)
assert np.isclose(seen[1], expected_iter, rtol=0, atol=atol)
assert seen[2] == 0
@classmethod
def _test_empty_cba(cls, augf_name, cbaoi):
aug = iaa.BlendAlphaElementwise(
0.501,
iaa.Identity(),
iaa.Affine(translate_px={"x": 1}))
observed = getattr(aug, augf_name)(cbaoi)
assert len(observed.items) == 0
assert observed.shape == (1, 2, 3)
@classmethod
def _test_cba_hooks_limit_propagation(cls, augf_name, cbaoi):
aug = iaa.BlendAlphaElementwise(
0.0,
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"y": 1}),
name="AlphaTest")
def propagator(cbaoi_to_aug, augmenter, parents, default):
if "Alpha" in augmenter.name:
return False
else:
return default
# no hooks for polygons yet, so we use HooksKeypoints
hooks = ia.HooksKeypoints(propagator=propagator)
observed = getattr(aug, augf_name)([cbaoi], hooks=hooks)[0]
assert observed.items[0].coords_almost_equals(cbaoi.items[0])
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlpha(1.0, iaa.Add(1), iaa.Add(100))
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlpha(1.0, iaa.Add(1), iaa.Add(100))
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.BlendAlphaElementwise(
(0.1, 0.9),
iaa.Add((1, 10), seed=1),
iaa.Add((11, 20), seed=2),
per_channel=True,
seed=3)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestBlendAlphaSomeColors(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child1 = iaa.Sequential([])
child2 = iaa.Sequential([])
aug = iaa.BlendAlphaSomeColors(child1, child2)
assert aug.foreground is child1
assert aug.background is child2
assert isinstance(aug.mask_generator, iaa.SomeColorsMaskGen)
def test_grayscale_drops_different_colors(self):
image = np.uint8([
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[255, 0, 255],
[0, 255, 255],
[255, 128, 128],
[128, 255, 128],
[128, 128, 255]
]).reshape((1, 9, 3))
image_gray = iaa.Grayscale(1.0)(image=image)
aug = iaa.BlendAlphaSomeColors(iaa.Grayscale(1.0),
nb_bins=256, smoothness=0)
nb_grayscaled = []
for _ in sm.xrange(50):
image_aug = aug(image=image)
grayscaled = np.sum((image_aug == image_gray).astype(np.int32),
axis=2)
assert np.all(np.logical_or(grayscaled == 0, grayscaled == 3))
nb_grayscaled.append(np.sum(grayscaled == 3))
assert len(set(nb_grayscaled)) >= 5
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlphaSomeColors(iaa.Add(1), iaa.Add(100))
image_aug = aug(image=image)
assert np.all(image_aug == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.BlendAlphaSomeColors(
iaa.Add((1, 10), seed=1),
iaa.Add((11, 20), seed=2),
seed=3)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestBlendAlphaHorizontalLinearGradient(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child1 = iaa.Sequential([])
child2 = iaa.Sequential([])
aug = iaa.BlendAlphaHorizontalLinearGradient(child1, child2)
assert aug.foreground is child1
assert aug.background is child2
assert isinstance(aug.mask_generator,
iaa.HorizontalLinearGradientMaskGen)
def test_single_image(self):
image = np.full((2, 100, 3), 255, dtype=np.uint8)
image_drop = iaa.TotalDropout(1.0)(image=image)
aug = iaa.BlendAlphaHorizontalLinearGradient(iaa.TotalDropout(1.0),
min_value=0.0,
max_value=1.0,
start_at=0.2,
end_at=0.8)
image_aug = aug(image=image)
assert np.array_equal(image_aug[0, :, :], image_aug[1, :, :])
assert np.array_equal(image_aug[:, :20, :], image[:, :20, :])
assert np.array_equal(image_aug[:, 80:, :], image_drop[:, 80:, :])
assert not np.array_equal(image_aug[:, 20:80, :], image[:, 20:80, :])
assert not np.array_equal(image_aug[:, 20:80, :],
image_drop[:, 20:80, :])
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlphaHorizontalLinearGradient(
iaa.TotalDropout(1.0))
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.BlendAlphaHorizontalLinearGradient(
iaa.Add((1, 10), seed=1),
iaa.Add((11, 20), seed=2),
seed=3)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestBlendAlphaVerticalLinearGradient(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child1 = iaa.Sequential([])
child2 = iaa.Sequential([])
aug = iaa.BlendAlphaVerticalLinearGradient(child1, child2)
assert aug.foreground is child1
assert aug.background is child2
assert isinstance(aug.mask_generator,
iaa.VerticalLinearGradientMaskGen)
def test_single_image(self):
image = np.full((100, 2, 3), 255, dtype=np.uint8)
image_drop = iaa.TotalDropout(1.0)(image=image)
aug = iaa.BlendAlphaVerticalLinearGradient(iaa.TotalDropout(1.0),
min_value=0.0,
max_value=1.0,
start_at=0.2,
end_at=0.8)
image_aug = aug(image=image)
assert np.array_equal(image_aug[:, 0, :], image_aug[:, 0, :])
assert np.array_equal(image_aug[:20, :, :], image[:20, :, :])
assert np.array_equal(image_aug[80:, :, :], image_drop[80:, :, :])
assert not np.array_equal(image_aug[20:80, :, :], image[20:80, :, :])
assert not np.array_equal(image_aug[20:80, :, :],
image_drop[20:80, :, :])
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlphaVerticalLinearGradient(
iaa.TotalDropout(1.0))
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.BlendAlphaVerticalLinearGradient(
iaa.Add((1, 10), seed=1),
iaa.Add((11, 20), seed=2),
seed=3)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestBlendAlphaRegularGrid(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child1 = iaa.Sequential([])
child2 = iaa.Sequential([])
aug = iaa.BlendAlphaRegularGrid(2, 3, child1, child2, alpha=0.7)
assert aug.mask_generator.nb_rows.value == 2
assert aug.mask_generator.nb_cols.value == 3
assert aug.foreground is child1
assert aug.background is child2
assert isinstance(aug.mask_generator, iaa.RegularGridMaskGen)
assert np.isclose(aug.mask_generator.alpha.value, 0.7)
def test_single_image(self):
image = np.full((2, 6, 3), 255, dtype=np.uint8)
aug = iaa.BlendAlphaRegularGrid(
nb_rows=1, nb_cols=3,
foreground=iaa.TotalDropout(1.0),
alpha=iap.DeterministicList([1.0, 0.0, 1.0]))
image_aug = aug(image=image)
expected = np.copy(image)
expected[:, 0:2, :] = 0
expected[:, 4:6, :] = 0
assert np.array_equal(image_aug, expected)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlphaRegularGrid(
nb_rows=2, nb_cols=3, foreground=iaa.TotalDropout(1.0))
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.BlendAlphaRegularGrid(
2, 3,
iaa.Add((1, 10), seed=1),
iaa.Add((11, 20), seed=2),
seed=3)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestBlendAlphaCheckerboard(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child1 = iaa.Sequential([])
child2 = iaa.Sequential([])
aug = iaa.BlendAlphaCheckerboard(2, 3, child1, child2)
assert aug.mask_generator.nb_rows.value == 2
assert aug.mask_generator.nb_cols.value == 3
assert aug.foreground is child1
assert aug.background is child2
assert isinstance(aug.mask_generator, iaa.CheckerboardMaskGen)
def test_single_image(self):
image = np.full((2, 6, 3), 255, dtype=np.uint8)
aug = iaa.BlendAlphaCheckerboard(nb_rows=1, nb_cols=3,
foreground=iaa.TotalDropout(1.0))
image_aug = aug(image=image)
expected = np.copy(image)
expected[:, 0:2, :] = 0
expected[:, 4:6, :] = 0
assert np.array_equal(image_aug, expected)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 0, dtype=np.uint8)
aug = iaa.BlendAlphaCheckerboard(
nb_rows=2, nb_cols=3, foreground=iaa.TotalDropout(1.0))
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.BlendAlphaCheckerboard(
2, 3,
iaa.Add((1, 10), seed=1),
iaa.Add((11, 20), seed=2),
seed=3)
runtest_pickleable_uint8_img(aug, iterations=3)
class TestBlendAlphaSegMapClassIds(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child1 = iaa.Sequential([])
child2 = iaa.Sequential([])
aug = iaa.BlendAlphaSegMapClassIds(
2,
nb_sample_classes=1,
foreground=child1,
background=child2
)
assert aug.foreground is child1
assert aug.background is child2
assert isinstance(aug.mask_generator,
iaa.SegMapClassIdsMaskGen)
assert aug.mask_generator.class_ids.value == 2
assert aug.mask_generator.nb_sample_classes.value == 1
def test_single_image(self):
image = np.full((10, 10, 3), 255, dtype=np.uint8)
segmap_arr = np.zeros((5, 10, 1), dtype=np.int32)
segmap_arr[0:2, :] = 1
aug = iaa.BlendAlphaSegMapClassIds(
1,
nb_sample_classes=1,
foreground=iaa.TotalDropout(1.0)
)
image_aug, segmap_aug = aug(image=image,
segmentation_maps=[segmap_arr])
assert np.allclose(image_aug[0:4, :, :], 0, rtol=0, atol=1.01)
assert np.allclose(image_aug[4:, :, :], 255, rtol=0, atol=1.01)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 255, dtype=np.uint8)
segmap_arr = np.zeros((2, 2, 1), dtype=np.int32)
segmap_arr[0, 0] = 2
aug = iaa.BlendAlphaSegMapClassIds(
2,
foreground=iaa.TotalDropout(1.0))
image_aug, segmap_aug = aug(
image=image, segmentation_maps=[segmap_arr])
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
shape = (15, 15, 3)
iterations = 3
augmenter = iaa.BlendAlphaSegMapClassIds(
[1, 2],
foreground=iaa.Add((1, 10), seed=1),
background=iaa.Add((11, 20), seed=2),
nb_sample_classes=1,
seed=3)
image = np.mod(np.arange(int(np.prod(shape))), 256).astype(np.uint8)
image = image.reshape(shape)
segmap_arr = np.zeros((15, 15, 1), dtype=np.int32)
segmap_arr[0:2, 0:2] = 1
segmap_arr[4:6, 5:8] = 2
augmenter_pkl = pickle.loads(pickle.dumps(augmenter, protocol=-1))
for _ in np.arange(iterations):
image_aug, sm_aug = augmenter(
image=image, segmentation_maps=[segmap_arr])
image_aug_pkl, sm_aug_pkl = augmenter_pkl(
image=image, segmentation_maps=[segmap_arr])
assert np.array_equal(image_aug, image_aug_pkl)
assert np.array_equal(sm_aug, sm_aug_pkl)
class TestBlendAlphaBoundingBoxes(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child1 = iaa.Sequential([])
child2 = iaa.Sequential([])
aug = iaa.BlendAlphaBoundingBoxes(
"person",
nb_sample_labels=1,
foreground=child1,
background=child2
)
assert aug.foreground is child1
assert aug.background is child2
assert isinstance(aug.mask_generator,
iaa.BoundingBoxesMaskGen)
assert aug.mask_generator.labels.value == "person"
assert aug.mask_generator.nb_sample_labels.value == 1
def test_single_image(self):
image = np.full((10, 10, 3), 255, dtype=np.uint8)
bbs = [ia.BoundingBox(x1=1, y1=1, x2=5, y2=5, label="bb1"),
ia.BoundingBox(x1=-3, y1=4, x2=20, y2=8, label="bb2")]
aug = iaa.BlendAlphaBoundingBoxes(
["bb1"],
nb_sample_labels=1,
foreground=iaa.Multiply(0.0)
)
image_aug, segmap_aug = aug(image=image,
bounding_boxes=[bbs])
assert np.allclose(image_aug[1:5, 1:5, :], 0, rtol=0, atol=1.01)
assert np.allclose(image_aug[0:1, 0:1, :], 255, rtol=0, atol=1.01)
assert np.allclose(image_aug[5:10, 5:10, :], 255, rtol=0, atol=1.01)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 255, dtype=np.uint8)
bbs = [ia.BoundingBox(x1=1, y1=1, x2=5, y2=5, label="bb1"),
ia.BoundingBox(x1=-3, y1=4, x2=20, y2=8, label="bb2")]
aug = iaa.BlendAlphaBoundingBoxes(
["bb1"],
foreground=iaa.Multiply(0.0))
image_aug, segmap_aug = aug(
image=image, bounding_boxes=[bbs])
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
shape = (15, 15, 3)
iterations = 3
augmenter = iaa.BlendAlphaBoundingBoxes(
["bb1", "bb2", "bb3"],
foreground=iaa.Add((1, 10), seed=1),
background=iaa.Add((11, 20), seed=2),
nb_sample_labels=1,
seed=3)
image = np.mod(np.arange(int(np.prod(shape))), 256).astype(np.uint8)
image = image.reshape(shape)
bbs = [ia.BoundingBox(x1=1, y1=1, x2=5, y2=5, label="bb1"),
ia.BoundingBox(x1=-3, y1=4, x2=20, y2=8, label="bb2")]
augmenter_pkl = pickle.loads(pickle.dumps(augmenter, protocol=-1))
for _ in np.arange(iterations):
image_aug, bbs_aug = augmenter(
image=image, bounding_boxes=[bbs])
image_aug_pkl, bbs_aug_pkl = augmenter_pkl(
image=image, bounding_boxes=[bbs])
assert np.array_equal(image_aug, image_aug_pkl)
class TestStochasticParameterMaskGen(unittest.TestCase):
@classmethod
def _test_draw_masks_nhwc(cls, shape):
batch = _BatchInAugmentation(
images=np.zeros(shape, dtype=np.uint8)
)
values = np.float32([
[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6]
])
param = iap.DeterministicList(values.flatten())
gen = iaa.StochasticParameterMaskGen(param, per_channel=False)
masks = gen.draw_masks(batch, random_state=0)
for i in np.arange(shape[0]):
assert np.allclose(masks[i], values)
def test_draw_masks_hw3_images(self):
self._test_draw_masks_nhwc((2, 2, 3, 3))
def test_draw_masks_hw1_images(self):
self._test_draw_masks_nhwc((2, 2, 3, 1))
def test_draw_masks_hw_images(self):
self._test_draw_masks_nhwc((2, 2, 3))
def test_draw_masks_batch_without_images(self):
bb = ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)
bbsoi1 = ia.BoundingBoxesOnImage([bb], shape=(2, 3, 3))
bbsoi2 = ia.BoundingBoxesOnImage([], shape=(3, 3, 3))
batch = _BatchInAugmentation(
bounding_boxes=[bbsoi1, bbsoi2]
)
# sampling for shape of bbsoi1 will cover row1 and row2, then
# sampling for bbsoi2 will cover row1, row2, row3
# masks are sampled independently per row/image, so it starts over
# again for bbsoi2
values = np.float32([
[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9]
])
param = iap.DeterministicList(values.flatten())
gen = iaa.StochasticParameterMaskGen(param, per_channel=False)
masks = gen.draw_masks(batch, random_state=0)
expected1 = values[0:2]
expected2 = values[0:3]
assert np.allclose(masks[0], expected1)
assert np.allclose(masks[1], expected2)
def test_per_channel(self):
for per_channel in [True, iap.Deterministic(0.51)]:
batch = _BatchInAugmentation(
images=np.zeros((1, 2, 3, 2), dtype=np.uint8)
)
values = np.float32([
[0.1, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, 0.8, 0.9],
[0.10, 0.11, 0.12]
])
param = iap.DeterministicList(values.flatten())
gen = iaa.StochasticParameterMaskGen(param,
per_channel=per_channel)
masks = gen.draw_masks(batch, random_state=0)
assert np.allclose(masks[0], values.reshape((2, 3, 2)))
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for per_channel in [False, True]:
for shape in shapes:
with self.subTest(per_channel=per_channel, shape=shape):
batch = _BatchInAugmentation(
images=[np.zeros(shape, dtype=np.uint8)]
)
param = iap.Deterministic(1.0)
gen = iaa.StochasticParameterMaskGen(
param, per_channel=per_channel)
masks = gen.draw_masks(batch, random_state=0)
assert len(masks) == 1
if not per_channel:
assert masks[0].shape == shape[0:2]
else:
assert masks[0].shape == shape
class TestSomeColorsMaskGen(unittest.TestCase):
def test___init___defaults(self):
gen = iaa.SomeColorsMaskGen()
assert np.isclose(gen.nb_bins.a.value, 5)
assert np.isclose(gen.nb_bins.b.value, 15)
assert np.isclose(gen.smoothness.a.value, 0.1)
assert np.isclose(gen.smoothness.b.value, 0.3)
assert np.isclose(gen.alpha.a[0], 0.0)
assert np.isclose(gen.alpha.a[1], 1.0)
assert np.isclose(gen.rotation_deg.a.value, 0)
assert np.isclose(gen.rotation_deg.b.value, 360)
assert gen.from_colorspace == iaa.CSPACE_RGB
def test___init___custom_settings(self):
gen = iaa.SomeColorsMaskGen(
nb_bins=100,
smoothness=0.5,
alpha=0.7,
rotation_deg=123,
from_colorspace=iaa.CSPACE_HSV
)
assert gen.nb_bins.value == 100
assert np.isclose(gen.smoothness.value, 0.5)
assert np.isclose(gen.alpha.value, 0.7)
assert np.isclose(gen.rotation_deg.value, 123)
assert gen.from_colorspace == iaa.CSPACE_HSV
def test_draw_masks_marks_different_colors(self):
image = np.uint8([
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[255, 0, 255],
[0, 255, 255],
[255, 128, 128],
[128, 255, 128],
[128, 128, 255]
]).reshape((9, 1, 3))
image = np.tile(image, (9, 50, 1))
batch = _BatchInAugmentation(images=[image])
gen = iaa.SomeColorsMaskGen(nb_bins=256, smoothness=0,
alpha=[0, 1])
expected_mask_sums = np.arange(1 + image.shape[0]) * image.shape[1]
expected_mask_sums = expected_mask_sums.astype(np.float32)
mask_sums = []
for i in sm.xrange(50):
mask = gen.draw_masks(batch, random_state=i)[0]
mask_sum = int(np.sum(mask))
mask_sums.append(mask_sum)
assert np.any(
np.isclose(
np.min(np.abs(expected_mask_sums - mask_sum)),
0.0,
rtol=0,
atol=0.01)
)
assert mask.shape == image.shape[0:2]
assert mask.dtype.name == "float32"
assert len(np.unique(mask_sums)) >= 4
def test_draw_masks_marks_alpha_is_0(self):
image = np.uint8([
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[255, 0, 255],
[0, 255, 255],
[255, 128, 128],
[128, 255, 128],
[128, 128, 255]
]).reshape((1, 9, 3))
batch = _BatchInAugmentation(images=[image])
gen = iaa.SomeColorsMaskGen(alpha=0.0)
mask = gen.draw_masks(batch)[0]
assert np.allclose(mask, 0.0)
def test_draw_masks_alpha_is_1(self):
image = np.uint8([
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[255, 0, 255],
[0, 255, 255],
[255, 128, 128],
[128, 255, 128],
[128, 128, 255]
]).reshape((1, 9, 3))
batch = _BatchInAugmentation(images=[image])
gen = iaa.SomeColorsMaskGen(alpha=1.0)
mask = gen.draw_masks(batch)[0]
assert np.allclose(mask, 1.0)
@mock.patch("imgaug.augmenters.color.change_colorspace_")
def test_from_colorspace(self, mock_cc):
image = np.uint8([
[255, 0, 0],
[0, 255, 0],
[0, 0, 255],
[255, 255, 0],
[255, 0, 255],
[0, 255, 255],
[255, 128, 128],
[128, 255, 128],
[128, 128, 255]
]).reshape((1, 9, 3))
batch = _BatchInAugmentation(images=[image])
mock_cc.return_value = np.copy(image)
gen = iaa.SomeColorsMaskGen(alpha=1.0, from_colorspace=iaa.CSPACE_BGR)
_ = gen.draw_masks(batch)
assert mock_cc.call_count == 1
assert np.array_equal(mock_cc.call_args_list[0][0][0], image)
assert (mock_cc.call_args_list[0][1]["to_colorspace"]
== iaa.CSPACE_HSV)
assert (mock_cc.call_args_list[0][1]["from_colorspace"]
== iaa.CSPACE_BGR)
def test__upscale_to_256_alpha_bins__1_to_256(self):
alphas = np.float32([0.5])
alphas_up = iaa.SomeColorsMaskGen._upscale_to_256_alpha_bins(alphas)
assert alphas_up.shape == (256,)
assert np.allclose(alphas_up, 0.5)
def test__upscale_to_256_alpha_bins__2_to_256(self):
alphas = np.float32([1.0, 0.5])
alphas_up = iaa.SomeColorsMaskGen._upscale_to_256_alpha_bins(alphas)
assert alphas_up.shape == (256,)
assert np.allclose(alphas_up[0:128], 1.0)
assert np.allclose(alphas_up[128:], 0.5)
def test__upscale_to_256_alpha_bins__255_to_256(self):
alphas = np.zeros((255,), dtype=np.float32)
alphas[0] = 0.25
alphas[1:254] = 0.5
alphas[254] = 1.0
alphas_up = iaa.SomeColorsMaskGen._upscale_to_256_alpha_bins(alphas)
assert alphas_up.shape == (256,)
assert np.allclose(alphas_up[0:2], 0.25)
assert np.allclose(alphas_up[2:], 0.5)
def test__upscale_to_256_alpha_bins__256_to_256(self):
alphas = np.full((256,), 0.5, dtype=np.float32)
alphas_up = iaa.SomeColorsMaskGen._upscale_to_256_alpha_bins(alphas)
assert alphas_up.shape == (256,)
assert np.allclose(alphas, 0.5)
def test__rotate_alpha_bins__by_0(self):
alphas = np.linspace(0.0, 1.0, 256)
alphas_rot = iaa.SomeColorsMaskGen._rotate_alpha_bins(alphas, 0)
assert np.allclose(alphas_rot, alphas)
def test__rotate_alpha_bins__by_1(self):
alphas = np.linspace(0.0, 1.0, 256)
alphas_rot = iaa.SomeColorsMaskGen._rotate_alpha_bins(alphas, 1)
assert np.allclose(alphas_rot[:-1], alphas[1:])
assert np.allclose(alphas_rot[-1:], alphas[:1])
def test__rotate_alpha_bins__by_255(self):
alphas = np.linspace(0.0, 1.0, 256)
alphas_rot = iaa.SomeColorsMaskGen._rotate_alpha_bins(alphas, 255)
assert np.allclose(alphas_rot[:-255], alphas[255:])
assert np.allclose(alphas_rot[-255:], alphas[:255])
def test__rotate_alpha_bins__by_256(self):
alphas = np.linspace(0.0, 1.0, 256)
alphas_rot = iaa.SomeColorsMaskGen._rotate_alpha_bins(alphas, 256)
assert np.allclose(alphas_rot, alphas)
def test__smoothen_alphas__0(self):
alphas = np.zeros((11,), dtype=np.float32)
alphas[5-3:5+3+1] = 1.0
alphas_smooth = iaa.SomeColorsMaskGen._smoothen_alphas(alphas, 0.0)
assert np.allclose(alphas_smooth, alphas)
def test__smoothen_alphas__002(self):
alphas = np.zeros((11,), dtype=np.float32)
alphas[5-3:5+3+1] = 1.0
alphas_smooth = iaa.SomeColorsMaskGen._smoothen_alphas(alphas, 0.02)
assert np.allclose(alphas_smooth, alphas, atol=0.02)
def test__smoothen_alphas__1(self):
alphas = np.zeros((11,), dtype=np.float32)
alphas[5-3:5+3+1] = 1.0
alphas_smooth = iaa.SomeColorsMaskGen._smoothen_alphas(alphas, 1.0)
assert np.isclose(alphas_smooth[0], 0.0, atol=0.01)
assert not np.isclose(alphas_smooth[2], 1.0, atol=0.1)
assert np.isclose(alphas_smooth[5], 1.0, atol=0.01)
def test__generate_pixelwise_alpha_map(self):
image_hsv = np.uint8([
[0, 0, 0],
[50, 0, 0],
[100, 0, 0],
[150, 0, 0],
[200, 0, 0],
[250, 0, 0],
[255, 0, 0]
]).reshape((1, 7, 3))
hue_to_alpha = np.zeros((256,), dtype=np.float32)
hue_to_alpha[0] = 0.1
hue_to_alpha[50] = 0.2
hue_to_alpha[100] = 0.3
hue_to_alpha[150] = 0.4
hue_to_alpha[200] = 0.5
hue_to_alpha[250] = 0.6
hue_to_alpha[255] = 0.7
mask = iaa.SomeColorsMaskGen._generate_pixelwise_alpha_mask(
image_hsv, hue_to_alpha)
# a bit of tolerance here due to the mask being converted from
# [0, 255] to [0.0, 1.0]
assert np.allclose(
mask.flatten(),
[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7],
atol=0.05)
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
batch = _BatchInAugmentation(images=[image])
gen = iaa.SomeColorsMaskGen()
mask = gen.draw_masks(batch)[0]
assert mask.shape == shape[0:2]
assert mask.dtype.name == "float32"
def test_batch_contains_no_images(self):
hms = ia.HeatmapsOnImage(np.zeros((5, 5), dtype=np.float32),
shape=(10, 10, 3))
batch = _BatchInAugmentation(heatmaps=[hms])
gen = iaa.SomeColorsMaskGen()
with self.assertRaises(AssertionError):
_masks = gen.draw_masks(batch)
class TestHorizontalLinearGradientMaskGen(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
gen = iaa.HorizontalLinearGradientMaskGen(min_value=0.1,
max_value=1.0,
start_at=0.1,
end_at=0.9)
assert gen.axis == 1
assert np.isclose(gen.min_value.value, 0.1)
assert np.isclose(gen.max_value.value, 1.0)
assert np.isclose(gen.start_at.value, 0.1)
assert np.isclose(gen.end_at.value, 0.9)
def test_draw_masks(self):
image1 = np.zeros((5, 100, 3), dtype=np.uint8)
image2 = np.zeros((7, 200, 3), dtype=np.uint8)
batch = _BatchInAugmentation(images=[image1, image2])
gen = iaa.HorizontalLinearGradientMaskGen(min_value=0.1,
max_value=0.75,
start_at=0.1,
end_at=0.9)
masks = gen.draw_masks(batch, random_state=1)
assert masks[0].shape == image1.shape[0:2]
assert masks[1].shape == image2.shape[0:2]
assert masks[0].dtype.name == "float32"
assert masks[1].dtype.name == "float32"
assert np.allclose(masks[0][:, 0:10], 0.1)
assert np.allclose(masks[1][:, 0:20], 0.1)
assert np.allclose(masks[0][:, 90:], 0.75)
assert np.allclose(masks[1][:, 180:], 0.75)
assert np.allclose(masks[0][:, 10+40], 0.1 + 0.5 * (0.75 - 0.1),
rtol=0, atol=0.05)
assert np.allclose(masks[1][:, 20+80], 0.1 + 0.5 * (0.75 - 0.1),
rtol=0, atol=0.025)
def test_generate_mask__min_value_below_max_value(self):
mask = iaa.HorizontalLinearGradientMaskGen.generate_mask(
(1, 100, 3), min_value=0.75, max_value=0.25,
start_at=0.0, end_at=1.0)
assert mask.shape == (1, 100)
assert np.isclose(mask[0, 0], 0.75)
assert np.isclose(mask[0, -1], 0.25)
assert np.isclose(mask[0, 50], 0.25 + 0.5 * (0.75 - 0.25),
rtol=0, atol=0.05)
def test_generate_mask__end_at_is_before_start_at(self):
mask = iaa.HorizontalLinearGradientMaskGen.generate_mask(
(1, 100, 3), min_value=0.25, max_value=0.75,
start_at=1.0, end_at=0.0)
# like test_generate_mask__min_value_below_max_value(),
# because end < start leads to inversion and we also inverted the
# min and max value above
assert mask.shape == (1, 100)
assert np.isclose(mask[0, 0], 0.75)
assert np.isclose(mask[0, -1], 0.25)
assert np.isclose(mask[0, 50], 0.25 + 0.5 * (0.75 - 0.25),
rtol=0, atol=0.05)
def test_generate_mask__start_at_is_end_at(self):
mask = iaa.HorizontalLinearGradientMaskGen.generate_mask(
(1, 100, 3), min_value=0.0, max_value=1.0,
start_at=0.5, end_at=0.5)
assert mask.shape == (1, 100)
assert np.allclose(mask[:, 0:50], 0.0)
assert np.allclose(mask[:, 50:], 1.0)
def test_generate_mask__min_value_is_max_value(self):
mask = iaa.HorizontalLinearGradientMaskGen.generate_mask(
(1, 100, 3), min_value=0.5, max_value=0.5,
start_at=0.1, end_at=0.8)
assert mask.shape == (1, 100)
assert np.allclose(mask, 0.5)
def test_generate_mask__start_at_and_end_at_are_outside_of_image(self):
mask = iaa.HorizontalLinearGradientMaskGen.generate_mask(
(1, 100, 3), min_value=0.25, max_value=0.75,
start_at=-0.5, end_at=-0.1)
assert mask.shape == (1, 100)
assert np.allclose(mask, 0.75)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 0, 0),
(1, 0, 0),
(0, 1, 0),
(0, 0, 1),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
batch = _BatchInAugmentation(images=[image])
gen = iaa.HorizontalLinearGradientMaskGen()
mask = gen.draw_masks(batch)[0]
assert mask.shape == shape[0:2]
assert mask.dtype.name == "float32"
def test_batch_contains_no_images(self):
hms = ia.HeatmapsOnImage(np.zeros((5, 5), dtype=np.float32),
shape=(10, 10, 3))
batch = _BatchInAugmentation(heatmaps=[hms])
gen = iaa.HorizontalLinearGradientMaskGen(min_value=0.25,
max_value=0.75,
start_at=0.5,
end_at=0.5)
mask = gen.draw_masks(batch)[0]
assert np.allclose(mask[:, 0:5], 0.25)
assert np.allclose(mask[:, 5:], 0.75)
class TestVerticalLinearGradientMaskGen(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
gen = iaa.VerticalLinearGradientMaskGen(min_value=0.1,
max_value=1.0,
start_at=0.1,
end_at=0.9)
assert gen.axis == 0
assert np.isclose(gen.min_value.value, 0.1)
assert np.isclose(gen.max_value.value, 1.0)
assert np.isclose(gen.start_at.value, 0.1)
assert np.isclose(gen.end_at.value, 0.9)
def test_draw_masks(self):
# we transpose the axes in this test, because that way the test is
# essentially identical to the one for HorizontalLinearGradientMaskGen
image1 = np.zeros((5, 100, 3), dtype=np.uint8)
image2 = np.zeros((7, 200, 3), dtype=np.uint8)
image1 = image1.transpose((1, 0, 2))
image2 = image2.transpose((1, 0, 2))
batch = _BatchInAugmentation(images=[image1, image2])
gen = iaa.VerticalLinearGradientMaskGen(min_value=0.1,
max_value=0.75,
start_at=0.1,
end_at=0.9)
masks = gen.draw_masks(batch, random_state=1)
image1 = image1.transpose((1, 0, 2))
image2 = image2.transpose((1, 0, 2))
masks[0] = masks[0].transpose((1, 0))
masks[1] = masks[1].transpose((1, 0))
assert masks[0].shape == image1.shape[0:2]
assert masks[1].shape == image2.shape[0:2]
assert masks[0].dtype.name == "float32"
assert masks[1].dtype.name == "float32"
assert np.allclose(masks[0][:, 0:10], 0.1)
assert np.allclose(masks[1][:, 0:20], 0.1)
assert np.allclose(masks[0][:, 90:], 0.75)
assert np.allclose(masks[1][:, 180:], 0.75)
assert np.allclose(masks[0][:, 10+40], 0.1 + 0.5 * (0.75 - 0.1),
rtol=0, atol=0.05)
assert np.allclose(masks[1][:, 20+80], 0.1 + 0.5 * (0.75 - 0.1),
rtol=0, atol=0.025)
class TestRegularGridMaskGen(unittest.TestCase):
def test___init__(self):
gen = iaa.RegularGridMaskGen(nb_rows=2, nb_cols=[1, 3], alpha=0.6)
assert gen.nb_rows.value == 2
assert gen.nb_cols.a == [1, 3]
assert np.isclose(gen.alpha.value, 0.6)
def test_draw_masks(self):
gen = iaa.RegularGridMaskGen(
nb_rows=2,
nb_cols=iap.DeterministicList([1, 4]),
alpha=iap.DeterministicList([0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7,
0.8, 0.9, 1.0]))
image = np.zeros((6, 8, 3), dtype=np.uint8)
batch = _BatchInAugmentation(images=[image, image])
masks = gen.draw_masks(batch, random_state=1)
expected1 = np.full((6, 8), 1.0, dtype=np.float32)
expected1[0:3, :] = 0.1
expected1[3:6, :] = 0.2
expected2 = np.full((6, 8), 1.0, dtype=np.float32)
expected2[0:3, 0:2] = 0.3
expected2[0:3, 2:4] = 0.4
expected2[0:3, 4:6] = 0.5
expected2[0:3, 6:8] = 0.6
expected2[3:6, 0:2] = 0.7
expected2[3:6, 2:4] = 0.8
expected2[3:6, 4:6] = 0.9
expected2[3:6, 6:8] = 1.0
assert np.allclose(masks[0], expected1)
assert np.allclose(masks[1], expected2)
def test_draw_masks__random_alphas(self):
gen = iaa.RegularGridMaskGen(
nb_rows=1,
nb_cols=2,
alpha=[0.1, 0.9])
image = np.zeros((2, 4, 3), dtype=np.uint8)
batch = _BatchInAugmentation(images=[image, image])
expected1 = np.full((2, 4), 0.1, dtype=np.float32)
expected2 = np.full((2, 4), 0.1, dtype=np.float32)
expected3 = np.full((2, 4), 0.1, dtype=np.float32)
expected4 = np.full((2, 4), 0.9, dtype=np.float32)
expected1[:, 0:2] = 0.9
expected2[:, 2:4] = 0.9
seen = [False, False, False, False]
for i in np.arange(50):
masks = gen.draw_masks(batch, random_state=i)
for mask in masks:
if np.allclose(mask, expected1):
seen[0] = True
elif np.allclose(mask, expected2):
seen[1] = True
elif np.allclose(mask, expected3):
seen[2] = True
elif np.allclose(mask, expected4):
seen[3] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_generate_mask_rows_1_cols_1(self):
mask = iaa.RegularGridMaskGen.generate_mask(
(5, 7),
nb_rows=1, nb_cols=1,
alphas=np.float32([1, 0]))
assert np.allclose(mask, 1.0)
def test_generate_mask_rows_1_cols_n(self):
mask = iaa.RegularGridMaskGen.generate_mask(
(5, 8),
nb_rows=1, nb_cols=4,
alphas=np.float32([[1, 0, 1, 0]]))
expected = np.full((5, 8), 1.0, dtype=np.float32)
expected[:, 2:4] = 0.0
expected[:, 6:8] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask_rows_n_cols_1(self):
mask = iaa.RegularGridMaskGen.generate_mask(
(8, 5),
nb_rows=4, nb_cols=1,
alphas=np.float32([[1],
[0],
[1],
[0]]))
expected = np.full((8, 5), 1.0, dtype=np.float32)
expected[2:4, :] = 0.0
expected[6:8, :] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask_rows_n_cols_n(self):
mask = iaa.RegularGridMaskGen.generate_mask(
(6, 8),
nb_rows=3, nb_cols=2,
alphas=np.float32([[1, 0],
[0, 1],
[1, 0]]))
expected = np.full((6, 8), 1.0, dtype=np.float32)
expected[0:2, 0:4] = 1.0
expected[0:2, 4:8] = 0.0
expected[2:4, 0:4] = 0.0
expected[2:4, 4:8] = 1.0
expected[4:6, 0:4] = 1.0
expected[4:6, 4:8] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask_with_leftover_pixels(self):
mask = iaa.RegularGridMaskGen.generate_mask(
(15, 15),
nb_rows=4, nb_cols=4,
alphas=np.float32([[1, 0, 1, 0],
[0, 1, 0, 1],
[1, 0, 1, 0],
[0, 1, 0, 1]]))
expected = np.full((12, 12), 0.0, dtype=np.float32)
expected[0:3, 0:3] = 1.0
expected[0:3, 3:6] = 0.0
expected[0:3, 6:9] = 1.0
expected[0:3, 9:12] = 0.0
expected[3:6, 0:3] = 0.0
expected[3:6, 3:6] = 1.0
expected[3:6, 6:9] = 0.0
expected[3:6, 9:12] = 1.0
expected[6:9, 0:3] = 1.0
expected[6:9, 3:6] = 0.0
expected[6:9, 6:9] = 1.0
expected[6:9, 9:12] = 0.0
expected[9:12, 0:3] = 0.0
expected[9:12, 3:6] = 1.0
expected[9:12, 6:9] = 0.0
expected[9:12, 9:12] = 1.0
expected = np.pad(expected, ((1, 2), (1, 2)), mode="reflect")
assert np.allclose(mask, expected)
def test_generate_mask_with_more_columns_than_pixels(self):
mask = iaa.RegularGridMaskGen.generate_mask(
(5, 4),
nb_rows=1, nb_cols=10,
alphas=np.float32([[1, 0, 1, 0, 1, 0, 1, 0, 1, 0]]))
expected = np.full((5, 4), 1.0, dtype=np.float32)
expected[:, 1:2] = 0.0
expected[:, 3:4] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask_with_more_rows_than_pixels(self):
mask = iaa.RegularGridMaskGen.generate_mask(
(4, 5),
nb_rows=6, nb_cols=1,
alphas=np.float32([[1],
[0],
[1],
[0],
[1],
[0]]))
expected = np.full((4, 5), 1.0, dtype=np.float32)
expected[1:2, :] = 0.0
expected[3:4, :] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask__alphas_is_1d_array(self):
mask = iaa.RegularGridMaskGen.generate_mask(
(5, 8),
nb_rows=1, nb_cols=4,
alphas=np.float32([1, 0, 1, 0]))
expected = np.full((5, 8), 1.0, dtype=np.float32)
expected[:, 2:4] = 0.0
expected[:, 6:8] = 0.0
assert np.allclose(mask, expected)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 0, 0),
(1, 0, 0),
(0, 1, 0),
(0, 0, 1),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
batch = _BatchInAugmentation(images=[image])
gen = iaa.RegularGridMaskGen(2, 2)
mask = gen.draw_masks(batch)[0]
assert mask.shape == shape[0:2]
assert mask.dtype.name == "float32"
def test_batch_contains_no_images(self):
hms = ia.HeatmapsOnImage(np.zeros((5, 5), dtype=np.float32),
shape=(6, 8, 3))
batch = _BatchInAugmentation(heatmaps=[hms])
gen = iaa.CheckerboardMaskGen(nb_rows=3, nb_cols=2)
mask = gen.draw_masks(batch, random_state=1)[0]
expected = np.full((6, 8), 1.0, dtype=np.float32)
expected[0:2, 0:4] = 1.0
expected[0:2, 4:8] = 0.0
expected[2:4, 0:4] = 0.0
expected[2:4, 4:8] = 1.0
expected[4:6, 0:4] = 1.0
expected[4:6, 4:8] = 0.0
assert np.allclose(mask, expected)
class TestCheckerboardMaskGen(unittest.TestCase):
def test___init__(self):
gen = iaa.CheckerboardMaskGen(nb_rows=2, nb_cols=[1, 3])
assert gen.nb_rows.value == 2
assert gen.nb_cols.a == [1, 3]
def test_draw_masks(self):
gen = iaa.CheckerboardMaskGen(nb_rows=2,
nb_cols=iap.DeterministicList([1, 4]))
image = np.zeros((6, 8, 3), dtype=np.uint8)
batch = _BatchInAugmentation(images=[image, image])
masks = gen.draw_masks(batch, random_state=1)
expected1 = np.full((6, 8), 1.0, dtype=np.float32)
expected1[3:6, :] = 0.0
expected2 = np.full((6, 8), 1.0, dtype=np.float32)
expected2[0:3, 2:4] = 0.0
expected2[0:3, 6:8] = 0.0
expected2[3:6, 0:2] = 0.0
expected2[3:6, 4:6] = 0.0
assert np.allclose(masks[0], expected1)
assert np.allclose(masks[1], expected2)
def test_generate_mask_rows_1_cols_1(self):
mask = iaa.CheckerboardMaskGen.generate_mask((5, 7),
nb_rows=1, nb_cols=1)
assert np.allclose(mask, 1.0)
def test_generate_mask_rows_1_cols_n(self):
mask = iaa.CheckerboardMaskGen.generate_mask((5, 8),
nb_rows=1, nb_cols=4)
expected = np.full((5, 8), 1.0, dtype=np.float32)
expected[:, 2:4] = 0.0
expected[:, 6:8] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask_rows_n_cols_1(self):
mask = iaa.CheckerboardMaskGen.generate_mask((8, 5),
nb_rows=4, nb_cols=1)
expected = np.full((8, 5), 1.0, dtype=np.float32)
expected[2:4, :] = 0.0
expected[6:8, :] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask_rows_n_cols_n(self):
mask = iaa.CheckerboardMaskGen.generate_mask((6, 8),
nb_rows=3, nb_cols=2)
expected = np.full((6, 8), 1.0, dtype=np.float32)
expected[0:2, 0:4] = 1.0
expected[0:2, 4:8] = 0.0
expected[2:4, 0:4] = 0.0
expected[2:4, 4:8] = 1.0
expected[4:6, 0:4] = 1.0
expected[4:6, 4:8] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask_with_leftover_pixels(self):
mask = iaa.CheckerboardMaskGen.generate_mask((15, 15),
nb_rows=4, nb_cols=4)
expected = np.full((12, 12), 0.0, dtype=np.float32)
expected[0:3, 0:3] = 1.0
expected[0:3, 3:6] = 0.0
expected[0:3, 6:9] = 1.0
expected[0:3, 9:12] = 0.0
expected[3:6, 0:3] = 0.0
expected[3:6, 3:6] = 1.0
expected[3:6, 6:9] = 0.0
expected[3:6, 9:12] = 1.0
expected[6:9, 0:3] = 1.0
expected[6:9, 3:6] = 0.0
expected[6:9, 6:9] = 1.0
expected[6:9, 9:12] = 0.0
expected[9:12, 0:3] = 0.0
expected[9:12, 3:6] = 1.0
expected[9:12, 6:9] = 0.0
expected[9:12, 9:12] = 1.0
expected = np.pad(expected, ((1, 2), (1, 2)), mode="reflect")
assert np.allclose(mask, expected)
def test_generate_mask_with_more_columns_than_pixels(self):
mask = iaa.CheckerboardMaskGen.generate_mask((5, 4),
nb_rows=1, nb_cols=10)
expected = np.full((5, 4), 1.0, dtype=np.float32)
expected[:, 1:2] = 0.0
expected[:, 3:4] = 0.0
assert np.allclose(mask, expected)
def test_generate_mask_with_more_rows_than_pixels(self):
mask = iaa.CheckerboardMaskGen.generate_mask((4, 5),
nb_rows=6, nb_cols=1)
expected = np.full((4, 5), 1.0, dtype=np.float32)
expected[1:2, :] = 0.0
expected[3:4, :] = 0.0
assert np.allclose(mask, expected)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 0, 0),
(1, 0, 0),
(0, 1, 0),
(0, 0, 1),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
batch = _BatchInAugmentation(images=[image])
gen = iaa.CheckerboardMaskGen(2, 2)
mask = gen.draw_masks(batch)[0]
assert mask.shape == shape[0:2]
assert mask.dtype.name == "float32"
def test_batch_contains_no_images(self):
hms = ia.HeatmapsOnImage(np.zeros((5, 5), dtype=np.float32),
shape=(6, 8, 3))
batch = _BatchInAugmentation(heatmaps=[hms])
gen = iaa.CheckerboardMaskGen(nb_rows=3, nb_cols=2)
mask = gen.draw_masks(batch, random_state=1)[0]
expected = np.full((6, 8), 1.0, dtype=np.float32)
expected[0:2, 0:4] = 1.0
expected[0:2, 4:8] = 0.0
expected[2:4, 0:4] = 0.0
expected[2:4, 4:8] = 1.0
expected[4:6, 0:4] = 1.0
expected[4:6, 4:8] = 0.0
assert np.allclose(mask, expected)
class TestSegMapClassIdsMaskGen(unittest.TestCase):
def setUp(self):
reseed()
def test___init___fixed_class_ids_int(self):
gen = iaa.SegMapClassIdsMaskGen(0)
assert gen.class_ids == [0]
assert gen.nb_sample_classes is None
def test___init___fixed_class_ids_list(self):
gen = iaa.SegMapClassIdsMaskGen([0, 1, 3])
assert gen.class_ids == [0, 1, 3]
assert gen.nb_sample_classes is None
def test___init___class_ids_stochastic(self):
gen = iaa.SegMapClassIdsMaskGen([0, 1, 3], nb_sample_classes=2)
assert is_parameter_instance(gen.class_ids, iap.Choice)
assert is_parameter_instance(gen.nb_sample_classes, iap.Deterministic)
def test_draw_masks__fixed_class_ids(self):
segmap_arr = np.zeros((3, 2, 2), dtype=np.int32)
segmap_arr[0, 0, 0] = 1
segmap_arr[0, 1, 0] = 2
segmap_arr[1, 0, 0] = 1
segmap_arr[0, 0, 1] = 3
segmap_arr[1, 1, 1] = 3
segmap = ia.SegmentationMapsOnImage(segmap_arr, shape=(3, 2, 3))
batch = _BatchInAugmentation(segmentation_maps=[segmap])
gen = iaa.SegMapClassIdsMaskGen([2, 3])
mask = gen.draw_masks(batch, random_state=1)[0]
assert mask.shape == segmap_arr.shape[0:2]
assert mask.dtype.name == "float32"
assert np.isclose(mask[0, 0], 1.0) # class id 1 and 3
assert np.isclose(mask[0, 1], 1.0) # class id 2 and 0
assert np.isclose(mask[1, 1], 1.0) # class id 0 and 3
assert np.isclose(mask[1, 0], 0.0) # class id 1 and 0
assert np.allclose(mask[2, :], 0.0) # class id 0 in whole row
def test_draw_masks__stochastic_class_ids(self):
segmap_arr = np.zeros((3, 2, 2), dtype=np.int32)
segmap_arr[0, 0, 0] = 1
segmap_arr[0, 1, 0] = 2
segmap_arr[1, 0, 0] = 1
segmap_arr[0, 0, 1] = 3
segmap_arr[1, 1, 1] = 3
segmap = ia.SegmentationMapsOnImage(segmap_arr, shape=(3, 2, 3))
batch = _BatchInAugmentation(segmentation_maps=[segmap])
gen = iaa.SegMapClassIdsMaskGen([2, 3], nb_sample_classes=1)
expected_class_2 = np.float32([
[0, 1],
[0, 0],
[0, 0]
])
expected_class_3 = np.float32([
[1, 0],
[0, 1],
[0, 0]
])
seen = [False, False]
for i in np.arange(50):
mask = gen.draw_masks(batch, random_state=i)[0]
if np.allclose(mask, expected_class_2):
seen[0] = True
elif np.allclose(mask, expected_class_3):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_generate_mask(self):
segmap_arr = np.zeros((3, 2, 2), dtype=np.int32)
segmap_arr[0, 0, 0] = 1
segmap_arr[0, 1, 0] = 2
segmap_arr[1, 0, 0] = 1
segmap_arr[0, 0, 1] = 3
segmap_arr[1, 1, 1] = 3
segmap = ia.SegmentationMapsOnImage(segmap_arr, shape=(3, 2, 3))
mask = iaa.SegMapClassIdsMaskGen.generate_mask(segmap, [1, 2])
expected = np.float32([
[1.0, 1.0],
[1.0, 0.0],
[0.0, 0.0]
])
assert np.allclose(mask, expected)
def test_generate_mask__smaller_than_image(self):
segmap_arr = np.zeros((3, 2, 2), dtype=np.int32)
segmap_arr[0, 0, 0] = 1
segmap_arr[0, 1, 0] = 2
segmap_arr[1, 0, 0] = 1
segmap_arr[0, 0, 1] = 3
segmap_arr[1, 1, 1] = 3
segmap = ia.SegmentationMapsOnImage(segmap_arr, shape=(3, 4))
mask = iaa.SegMapClassIdsMaskGen.generate_mask(segmap, [1, 2])
expected = np.float32([
[1.0, 1.0, 1.0, 1.0],
[1.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0]
])
assert np.allclose(mask, expected, rtol=0.0, atol=0.1)
def test_zero_sized_axes(self):
# zero-sized segmap arrays currently crash when creating
# SegmentationMapsOnImage and that's probably better that way
segmap_shapes = [
(2, 2, 1)
]
image_shapes = [
(2, 3, 3),
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for segmap_shape in segmap_shapes:
for image_shape in image_shapes:
with self.subTest(segmap_shape=segmap_shape,
image_shape=image_shape):
segmap_arr = np.zeros(segmap_shape, dtype=np.int32)
segmap = ia.SegmentationMapsOnImage(segmap_arr,
shape=image_shape)
batch = _BatchInAugmentation(segmentation_maps=[segmap])
gen = iaa.SegMapClassIdsMaskGen(1)
mask = gen.draw_masks(batch)[0]
assert mask.shape == image_shape[0:2]
assert mask.dtype.name == "float32"
assert np.allclose(mask, 0.0)
def test_batch_contains_no_segmaps(self):
hms = ia.HeatmapsOnImage(np.zeros((5, 5), dtype=np.float32),
shape=(10, 10, 3))
batch = _BatchInAugmentation(heatmaps=[hms])
gen = iaa.SegMapClassIdsMaskGen(class_ids=[1])
with self.assertRaises(AssertionError):
_mask = gen.draw_masks(batch)[0]
class TestBoundingBoxesMaskGen(unittest.TestCase):
def setUp(self):
reseed()
def test___init___no_labels(self):
gen = iaa.BoundingBoxesMaskGen()
assert gen.labels is None
assert gen.nb_sample_labels is None
def test___init___fixed_labels_single_str(self):
gen = iaa.BoundingBoxesMaskGen("person")
assert gen.labels == ["person"]
assert gen.nb_sample_labels is None
def test___init___fixed_labels_list(self):
gen = iaa.BoundingBoxesMaskGen(["person", "car"])
assert gen.labels == ["person", "car"]
assert gen.nb_sample_labels is None
def test___init___labels_stochastic(self):
gen = iaa.BoundingBoxesMaskGen(["person", "car"], nb_sample_labels=2)
assert is_parameter_instance(gen.labels, iap.Choice)
assert is_parameter_instance(gen.nb_sample_labels, iap.Deterministic)
def test_draw_masks__labels_is_none(self):
bbs = [ia.BoundingBox(x1=1, y1=1, x2=5, y2=5, label="bb1"),
ia.BoundingBox(x1=-3, y1=4, x2=20, y2=8, label="bb2"),
ia.BoundingBox(x1=2, y1=2, x2=10, y2=10, label="bb3")]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 14, 3))
batch = _BatchInAugmentation(bounding_boxes=[bbsoi])
gen = iaa.BoundingBoxesMaskGen()
mask = gen.draw_masks(batch, random_state=1)[0]
expected = np.zeros((10, 14), dtype=np.float32)
expected[1:5, 1:5] = 1.0 # bb1
expected[4:8, 0:14] = 1.0 # bb2 clipped to image shape
expected[2:10, 2:10] = 1.0 # bb3
assert mask.shape == (10, 14)
assert mask.dtype.name == "float32"
assert np.allclose(mask, expected)
def test_draw_masks__fixed_labels(self):
bbs = [ia.BoundingBox(x1=1, y1=1, x2=5, y2=5, label="bb1"),
ia.BoundingBox(x1=-3, y1=4, x2=20, y2=8, label="bb2"),
ia.BoundingBox(x1=2, y1=2, x2=10, y2=10, label="bb3")]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 14, 3))
batch = _BatchInAugmentation(bounding_boxes=[bbsoi])
gen = iaa.BoundingBoxesMaskGen(["bb1", "bb2"])
mask = gen.draw_masks(batch, random_state=1)[0]
expected = np.zeros((10, 14), dtype=np.float32)
expected[1:5, 1:5] = 1.0 # bb1
expected[4:8, 0:14] = 1.0 # bb2 clipped to image shape
assert mask.shape == (10, 14)
assert mask.dtype.name == "float32"
assert np.allclose(mask, expected)
def test_draw_masks__stochastic_labels(self):
bbs = [ia.BoundingBox(x1=1, y1=1, x2=5, y2=5, label="bb1"),
ia.BoundingBox(x1=-3, y1=4, x2=20, y2=8, label="bb2"),
ia.BoundingBox(x1=2, y1=2, x2=10, y2=10, label="bb3")]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 14, 3))
batch = _BatchInAugmentation(bounding_boxes=[bbsoi])
gen = iaa.BoundingBoxesMaskGen(
iap.DeterministicList(["bb1", "bb2"]),
nb_sample_labels=3)
mask = gen.draw_masks(batch, random_state=1)[0]
expected = np.zeros((10, 14), dtype=np.float32)
expected[1:5, 1:5] = 1.0 # bb1
expected[4:8, 0:14] = 1.0 # bb2 clipped to image shape
assert mask.shape == (10, 14)
assert mask.dtype.name == "float32"
assert np.allclose(mask, expected)
def test_generate_mask(self):
bbs = [ia.BoundingBox(x1=1, y1=1, x2=5, y2=5, label="bb1"),
ia.BoundingBox(x1=-3, y1=4, x2=20, y2=8, label="bb2"),
ia.BoundingBox(x1=2, y1=2, x2=10, y2=10, label="bb3")]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 14, 3))
mask = iaa.BoundingBoxesMaskGen.generate_mask(bbsoi, ["bb1", "bb2"])
expected = np.zeros((10, 14), dtype=np.float32)
expected[1:5, 1:5] = 1.0 # bb1
expected[4:8, 0:14] = 1.0 # bb2 clipped to image shape
assert mask.shape == (10, 14)
assert mask.dtype.name == "float32"
assert np.allclose(mask, expected)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
bbs = [ia.BoundingBox(x1=1, y1=1, x2=5, y2=5, label="bb1"),
ia.BoundingBox(x1=-3, y1=4, x2=20, y2=8, label="bb2"),
ia.BoundingBox(x1=2, y1=2, x2=10, y2=10, label="bb3")]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=shape)
batch = _BatchInAugmentation(bounding_boxes=[bbsoi])
gen = iaa.BoundingBoxesMaskGen("bb1")
mask = gen.draw_masks(batch)[0]
assert mask.shape == shape[0:2]
assert mask.dtype.name == "float32"
assert np.allclose(mask, 0.0)
def test_batch_contains_no_bounding_boxes(self):
hms = ia.HeatmapsOnImage(np.zeros((5, 5), dtype=np.float32),
shape=(10, 10, 3))
batch = _BatchInAugmentation(heatmaps=[hms])
gen = iaa.SegMapClassIdsMaskGen(class_ids=[1])
with self.assertRaises(AssertionError):
_mask = gen.draw_masks(batch)[0]
class InvertMaskGen(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child = iaa.HorizontalLinearGradientMaskGen()
gen = iaa.InvertMaskGen(0.5, child)
assert np.isclose(gen.p.p.value, 0.5)
assert gen.child is child
def test_draw_masks(self):
image = np.zeros((1, 20), dtype=np.uint8)
batch = _BatchInAugmentation(images=[image] * 200)
child = iaa.HorizontalLinearGradientMaskGen(min_value=0.0,
max_value=1.0,
start_at=0.0,
end_at=1.0)
gen = iaa.InvertMaskGen(0.5, child)
masks = gen.draw_masks(batch, random_state=1)
hgrad = iaa.HorizontalLinearGradientMaskGen.generate_mask(
(1, 20), min_value=0.0, max_value=1.0, start_at=0.0, end_at=1.0)
expected1 = hgrad
expected2 = 1.0 - hgrad
seen = [0, 0]
for mask in masks:
if np.allclose(mask, expected1):
seen[0] += 1
elif np.allclose(mask, expected2):
seen[1] += 1
else:
assert False
assert np.allclose(seen, 0.5*200, rtol=0, atol=20)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 0, 0),
(1, 0, 0),
(0, 1, 0),
(0, 0, 1),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
batch = _BatchInAugmentation(images=[image])
child = iaa.HorizontalLinearGradientMaskGen()
gen = iaa.InvertMaskGen(0.5, child)
mask = gen.draw_masks(batch)[0]
assert mask.shape == shape[0:2]
assert mask.dtype.name == "float32"
class TestSimplexNoiseAlpha(unittest.TestCase):
def test_deprecation_warning(self):
aug1 = iaa.Sequential([])
aug2 = iaa.Sequential([])
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
aug = iaa.SimplexNoiseAlpha(first=aug1, second=aug2)
assert (
"is deprecated"
in str(caught_warnings[-1].message)
)
assert isinstance(aug, iaa.BlendAlphaSimplexNoise)
assert aug.foreground is aug1
assert aug.background is aug2
class TestFrequencyNoiseAlpha(unittest.TestCase):
def test_deprecation_warning(self):
aug1 = iaa.Sequential([])
aug2 = iaa.Sequential([])
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
aug = iaa.FrequencyNoiseAlpha(first=aug1, second=aug2)
assert (
"is deprecated"
in str(caught_warnings[-1].message)
)
assert isinstance(aug, iaa.BlendAlphaFrequencyNoise)
assert aug.foreground is aug1
assert aug.background is aug2
================================================
FILE: test/augmenters/test_blur.py
================================================
from __future__ import print_function, division, absolute_import
import warnings
import sys
import itertools
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug import random as iarandom
from imgaug.testutils import keypoints_equal, reseed, runtest_pickleable_uint8_img
class Test_blur_gaussian_(unittest.TestCase):
def setUp(self):
reseed()
def test_integration(self):
backends = ["auto", "scipy", "cv2"]
nb_channels_lst = [None, 1, 3, 4, 5, 10]
gen = itertools.product(backends, nb_channels_lst)
for backend, nb_channels in gen:
with self.subTest(backend=backend, nb_channels=nb_channels):
image = np.zeros((5, 5), dtype=np.uint8)
if nb_channels is not None:
image = np.tile(image[..., np.newaxis], (1, 1, nb_channels))
image[2, 2] = 255
mask = image < 255
observed = iaa.blur_gaussian_(
np.copy(image), sigma=5.0, backend=backend)
assert observed.shape == image.shape
assert observed.dtype.name == "uint8"
assert np.all(observed[2, 2] < 255)
assert np.sum(observed[mask]) > (5*5-1)
if nb_channels is not None and nb_channels > 1:
for c in sm.xrange(1, observed.shape[2]):
assert np.array_equal(observed[..., c],
observed[..., 0])
def test_sigma_zero(self):
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
observed = iaa.blur_gaussian_(np.copy(image), 0)
assert np.array_equal(observed, image)
image = np.arange(4*4).astype(np.uint8).reshape((4, 4, 1))
observed = iaa.blur_gaussian_(np.copy(image), 0)
assert np.array_equal(observed, image)
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
observed = iaa.blur_gaussian_(np.copy(image), 0)
assert np.array_equal(observed, image)
def test_eps(self):
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
observed_no_eps = iaa.blur_gaussian_(np.copy(image), 1.0, eps=0)
observed_with_eps = iaa.blur_gaussian_(np.copy(image), 1.0, eps=1e10)
assert not np.array_equal(observed_no_eps, observed_with_eps)
assert np.array_equal(observed_with_eps, image)
def test_ksize(self):
def side_effect(image, ksize, sigmaX, sigmaY, borderType):
return image + 1
sigmas = [5.0, 5.0]
ksizes = [None, 3]
ksizes_expected = [2.6*5.0, 3]
gen = zip(sigmas, ksizes, ksizes_expected)
for (sigma, ksize, ksize_expected) in gen:
with self.subTest(sigma=sigma, ksize=ksize):
mock_GaussianBlur = mock.Mock(side_effect=side_effect)
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
with mock.patch('cv2.GaussianBlur', mock_GaussianBlur):
observed = iaa.blur_gaussian_(
np.copy(image),
sigma=sigma,
ksize=ksize,
backend="cv2")
assert np.array_equal(observed, image+1)
cargs = mock_GaussianBlur.call_args
assert mock_GaussianBlur.call_count == 1
assert np.array_equal(cargs[0][0], image)
assert isinstance(cargs[0][1], tuple)
assert np.allclose(
np.float32(cargs[0][1]),
np.float32([ksize_expected, ksize_expected]))
assert np.isclose(cargs[1]["sigmaX"], sigma)
assert np.isclose(cargs[1]["sigmaY"], sigma)
assert cargs[1]["borderType"] == cv2.BORDER_REFLECT_101
def test_more_than_four_channels(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.blur_gaussian_(np.copy(image), 1.0)
assert image_aug.shape == image.shape
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.blur_gaussian_(np.copy(image), 1.0)
assert image_aug.shape == image.shape
def test_backends_called(self):
def side_effect_cv2(image, ksize, sigmaX, sigmaY, borderType):
return image + 1
def side_effect_scipy(image, sigma, mode):
return image + 1
mock_GaussianBlur = mock.Mock(side_effect=side_effect_cv2)
mock_gaussian_filter = mock.Mock(side_effect=side_effect_scipy)
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
with mock.patch('cv2.GaussianBlur', mock_GaussianBlur):
_observed = iaa.blur_gaussian_(
np.copy(image), sigma=1.0, eps=0, backend="cv2")
assert mock_GaussianBlur.call_count == 1
with mock.patch('scipy.ndimage.gaussian_filter', mock_gaussian_filter):
_observed = iaa.blur_gaussian_(
np.copy(image), sigma=1.0, eps=0, backend="scipy")
assert mock_gaussian_filter.call_count == 1
def test_backends_similar(self):
with self.subTest(nb_channels=None):
size = 10
image = np.arange(
0, size*size).astype(np.uint8).reshape((size, size))
image_cv2 = iaa.blur_gaussian_(
np.copy(image), sigma=3.0, ksize=20, backend="cv2")
image_scipy = iaa.blur_gaussian_(
np.copy(image), sigma=3.0, backend="scipy")
diff = np.abs(image_cv2.astype(np.int32)
- image_scipy.astype(np.int32))
assert np.average(diff) < 0.05 * (size * size)
with self.subTest(nb_channels=3):
size = 10
image = np.arange(
0, size*size).astype(np.uint8).reshape((size, size))
image = np.tile(image[..., np.newaxis], (1, 1, 3))
image[1] += 1
image[2] += 2
image_cv2 = iaa.blur_gaussian_(
np.copy(image), sigma=3.0, ksize=20, backend="cv2")
image_scipy = iaa.blur_gaussian_(
np.copy(image), sigma=3.0, backend="scipy")
diff = np.abs(image_cv2.astype(np.int32)
- image_scipy.astype(np.int32))
assert np.average(diff) < 0.05 * (size * size)
for c in sm.xrange(3):
diff = np.abs(image_cv2[..., c].astype(np.int32)
- image_scipy[..., c].astype(np.int32))
assert np.average(diff) < 0.05 * (size * size)
def test_view(self):
for backend in ["auto", "scipy", "cv2"]:
image = np.array([
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 255, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[1, 1, 1, 1, 1]
], dtype=np.uint8)
image_cp = np.copy(image[0:5, :])
image_aug = iaa.blur_gaussian_(image[0:5, :], 3.0, backend=backend)
assert image_aug.shape == (5, 5)
assert image_aug.dtype.name == "uint8"
assert np.all(image_aug[image_cp == 0] > 0)
assert np.all(image_aug[image_cp == 255] < 255)
def test_non_contiguous(self):
for backend in ["auto", "scipy", "cv2"]:
image = np.array([
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 255, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]
], dtype=np.uint8, order="F")
image_cp = np.copy(image)
image_aug = iaa.blur_gaussian_(image, 3.0, backend=backend)
assert image_aug.shape == (5, 5)
assert image_aug.dtype.name == "uint8"
assert np.all(image_aug[image_cp == 0] > 0)
assert np.all(image_aug[image_cp == 255] < 255)
def test_warnings(self):
# note that self.assertWarningRegex does not exist in python 2.7
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = iaa.blur_gaussian_(
np.zeros((1, 1), dtype=np.uint32),
sigma=3.0,
ksize=11,
backend="scipy")
assert len(caught_warnings) == 1
assert (
"but also provided 'ksize' argument"
in str(caught_warnings[-1].message))
def test_other_dtypes_sigma_0(self):
try:
f128 = [np.dtype("float128").name]
except TypeError:
f128 = []
dtypes_to_test_list = [
["bool",
"uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64",
"float16", "float32", "float64"] + f128,
["bool",
"uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64",
"float16", "float32", "float64"] + f128
]
gen = zip(["scipy", "cv2"], dtypes_to_test_list)
for backend, dtypes_to_test in gen:
# bool
if "bool" in dtypes_to_test:
with self.subTest(backend=backend, dtype="bool"):
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = iaa.blur_gaussian_(
np.copy(image), sigma=0, backend=backend)
assert image_aug.dtype.name == "bool"
assert np.all(image_aug == image)
# uint, int
uint_dts = [np.uint8, np.uint16, np.uint32, np.uint64]
int_dts = [np.int8, np.int16, np.int32, np.int64]
for dtype in uint_dts + int_dts:
dtype = np.dtype(dtype)
if dtype.name in dtypes_to_test:
with self.subTest(backend=backend, dtype=dtype.name):
_min_value, center_value, _max_value = \
iadt.get_value_range_of_dtype(dtype)
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = int(center_value)
image_aug = iaa.blur_gaussian_(
np.copy(image), sigma=0, backend=backend)
assert image_aug.dtype.name == dtype.name
assert np.all(image_aug == image)
# float
float_dts = [np.float16, np.float32, np.float64] + f128
for dtype in float_dts:
dtype = np.dtype(dtype)
if dtype.name in dtypes_to_test:
with self.subTest(backend=backend, dtype=dtype.name):
_min_value, center_value, _max_value = \
iadt.get_value_range_of_dtype(dtype)
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = center_value
image_aug = iaa.blur_gaussian_(
np.copy(image), sigma=0, backend=backend)
assert image_aug.dtype.name == dtype.name
assert np.allclose(image_aug, image)
def test_other_dtypes_sigma_075(self):
# prototype kernel, generated via:
# mask = np.zeros((5, 5), dtype=np.int32)
# mask[2, 2] = 1000 * 1000
# kernel = ndimage.gaussian_filter(mask, 0.75)
mask = np.float64([
[ 923, 6650, 16163, 6650, 923],
[ 6650, 47896, 116408, 47896, 6650],
[ 16163, 116408, 282925, 116408, 16163],
[ 6650, 47896, 116408, 47896, 6650],
[ 923, 6650, 16163, 6650, 923]
]) / (1000.0 * 1000.0)
dtypes_to_test_list = [
# scipy
["bool",
"uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64",
"float16", "float32", "float64"],
# cv2
["bool",
"uint8", "uint16",
"int8", "int16", "int32",
"float16", "float32", "float64"]
]
gen = zip(["scipy", "cv2"], dtypes_to_test_list)
for backend, dtypes_to_test in gen:
# bool
if "bool" in dtypes_to_test:
with self.subTest(backend=backend, dtype="bool"):
image = np.zeros((5, 5), dtype=bool)
image[2, 2] = True
image_aug = iaa.blur_gaussian_(
np.copy(image), sigma=0.75, backend=backend)
assert image_aug.dtype.name == "bool"
assert np.all(image_aug == (mask > 0.5))
# uint, int
uint_dts = [np.uint8, np.uint16, np.uint32, np.uint64]
int_dts = [np.int8, np.int16, np.int32, np.int64]
for dtype in uint_dts + int_dts:
dtype = np.dtype(dtype)
if dtype.name in dtypes_to_test:
with self.subTest(backend=backend, dtype=dtype.name):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
dynamic_range = max_value - min_value
value = int(center_value + 0.4 * max_value)
image = np.zeros((5, 5), dtype=dtype)
image[2, 2] = value
image_aug = iaa.blur_gaussian_(
image, sigma=0.75, backend=backend)
expected = (mask * value).astype(dtype)
diff = np.abs(image_aug.astype(np.int64)
- expected.astype(np.int64))
assert image_aug.shape == mask.shape
assert image_aug.dtype.type == dtype
if dtype.itemsize <= 1:
assert np.max(diff) <= 4
else:
assert np.max(diff) <= 0.01 * dynamic_range
# float
float_dts = [np.float16, np.float32, np.float64]
values = [5000, 1000**1, 1000**2, 1000**3]
for dtype, value in zip(float_dts, values):
dtype = np.dtype(dtype)
if dtype.name in dtypes_to_test:
with self.subTest(backend=backend, dtype=dtype.name):
image = np.zeros((5, 5), dtype=dtype)
image[2, 2] = value
image_aug = iaa.blur_gaussian_(
image, sigma=0.75, backend=backend)
expected = (mask * value).astype(dtype)
diff = np.abs(image_aug.astype(np.float64)
- expected.astype(np.float64))
assert image_aug.shape == mask.shape
assert image_aug.dtype.type == dtype
# accepts difference of 2.0, 4.0, 8.0, 16.0 (at 1,
# 2, 4, 8 bytes, i.e. 8, 16, 32, 64 bit)
max_diff = (
np.dtype(dtype).itemsize
* 0.01
* np.float64(value)
)
assert np.max(diff) < max_diff
def test_other_dtypes_bool_at_sigma_06(self):
# --
# blur of bool input at sigma=0.6
# --
# here we use a special mask and sigma as otherwise the only values
# ending up with >0.5 would be the ones that
# were before the blur already at >0.5
# prototype kernel, generated via:
# mask = np.zeros((5, 5), dtype=np.float64)
# mask[1, 0] = 255
# mask[2, 0] = 255
# mask[2, 2] = 255
# mask[2, 4] = 255
# mask[3, 0] = 255
# mask = ndimage.gaussian_filter(mask, 1.0, mode="mirror")
mask_bool = np.float64([
[ 57, 14, 2, 1, 1],
[142, 42, 29, 14, 28],
[169, 69, 114, 56, 114],
[142, 42, 29, 14, 28],
[ 57, 14, 2, 1, 1]
]) / 255.0
image = np.zeros((5, 5), dtype=bool)
image[1, 0] = True
image[2, 0] = True
image[2, 2] = True
image[2, 4] = True
image[3, 0] = True
for backend in ["scipy", "cv2"]:
image_aug = iaa.blur_gaussian_(
np.copy(image), sigma=0.6, backend=backend)
expected = mask_bool > 0.5
assert image_aug.shape == mask_bool.shape
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == expected)
class Test_blur_avg_(unittest.TestCase):
@classmethod
def _avg(cls, values):
return int(np.round(np.average(values)))
def test_kernel_size_is_int(self):
# reflection padded:
# [6, 5, 6, 7, 8, 7],
# [2, 1, 2, 3, 4, 3],
# [6, 5, 6, 7, 8, 7],
# [10, 9, 10, 11, 12, 11],
# [14, 13, 14, 15, 16, 15]
# [10, 9, 10, 11, 12, 11],
image = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
], dtype=np.uint8)
image_aug = iaa.blur_avg_(np.copy(image), 3)
assert image_aug[0, 0] == self._avg([6, 5, 6, 2, 1, 2, 6, 5, 6])
assert image_aug[0, 1] == self._avg([5, 6, 7, 1, 2, 3, 5, 6, 7])
assert image_aug[3, 3] == self._avg([11, 12, 11, 15, 16, 15, 11, 12,
11])
def test_kernel_size_is_tuple(self):
# reflection padded:
# [6, 5, 6, 7, 8, 7],
# [2, 1, 2, 3, 4, 3],
# [6, 5, 6, 7, 8, 7],
# [10, 9, 10, 11, 12, 11],
# [14, 13, 14, 15, 16, 15]
# [10, 9, 10, 11, 12, 11],
image = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
], dtype=np.uint8)
image_aug = iaa.blur_avg_(np.copy(image), (3, 1))
assert image_aug[0, 0] == self._avg([5, 1, 5])
assert image_aug[0, 1] == self._avg([6, 2, 6])
assert image_aug[3, 3] == self._avg([12, 16, 12])
def test_view(self):
# reflection padded (after crop):
# [6, 5, 6, 7, 8, 7],
# [2, 1, 2, 3, 4, 3],
# [6, 5, 6, 7, 8, 7],
# [10, 9, 10, 11, 12, 11],
# [14, 13, 14, 15, 16, 15]
# [10, 9, 10, 11, 12, 11],
image = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16],
[0, 0, 0, 0]
], dtype=np.uint8)
image_aug = iaa.blur_avg_(np.copy(image)[0:4, :], 3)
assert image_aug[0, 0] == self._avg([6, 5, 6, 2, 1, 2, 6, 5, 6])
assert image_aug[0, 1] == self._avg([5, 6, 7, 1, 2, 3, 5, 6, 7])
assert image_aug[3, 3] == self._avg([11, 12, 11, 15, 16, 15, 11, 12,
11])
def test_noncontiguous(self):
# reflection padded:
# [6, 5, 6, 7, 8, 7],
# [2, 1, 2, 3, 4, 3],
# [6, 5, 6, 7, 8, 7],
# [10, 9, 10, 11, 12, 11],
# [14, 13, 14, 15, 16, 15]
# [10, 9, 10, 11, 12, 11],
image = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
], dtype=np.uint8, order="F")
image_aug = iaa.blur_avg_(image, 3)
assert image_aug[0, 0] == self._avg([6, 5, 6, 2, 1, 2, 6, 5, 6])
assert image_aug[0, 1] == self._avg([5, 6, 7, 1, 2, 3, 5, 6, 7])
assert image_aug[3, 3] == self._avg([11, 12, 11, 15, 16, 15, 11, 12,
11])
class Test_blur_mean_shift_(unittest.TestCase):
@property
def image(self):
image = [
[1, 2, 3, 4, 200, 201, 202, 203],
[1, 2, 3, 4, 200, 201, 202, 203],
[1, 2, 3, 4, 200, 201, 202, 203],
[1, 2, 3, 4, 200, 201, 202, 203]
]
image = np.array(image, dtype=np.uint8).reshape((4, 2*4, 1))
image = np.tile(image, (1, 1, 3))
return image
def test_simple_image(self):
image = self.image
image_blurred = iaa.blur_mean_shift_(np.copy(image), 0.5, 0.5)
assert image_blurred.shape == image.shape
assert image_blurred.dtype.name == "uint8"
assert not np.array_equal(image_blurred, image)
assert 0 <= np.average(image[:, 0:4, :]) <= 5
assert 199 <= np.average(image[:, 4:, :]) <= 203
def test_hw_image(self):
image = self.image[:, :, 0]
image_blurred = iaa.blur_mean_shift_(np.copy(image), 0.5, 0.5)
assert image_blurred.shape == image.shape
assert image_blurred.dtype.name == "uint8"
assert not np.array_equal(image_blurred, image)
def test_hw1_image(self):
image = self.image[:, :, 0:1]
image_blurred = iaa.blur_mean_shift_(np.copy(image), 0.5, 0.5)
assert image_blurred.ndim == 3
assert image_blurred.shape == image.shape
assert image_blurred.dtype.name == "uint8"
assert not np.array_equal(image_blurred, image)
def test_non_contiguous_image(self):
image = self.image
image_cp = np.copy(np.fliplr(image))
image = np.fliplr(image)
assert image.flags["C_CONTIGUOUS"] is False
image_blurred = iaa.blur_mean_shift_(image, 0.5, 0.5)
assert image_blurred.shape == image_cp.shape
assert image_blurred.dtype.name == "uint8"
assert not np.array_equal(image_blurred, image_cp)
def test_both_parameters_are_zero(self):
image = self.image[:, :, 0]
image_blurred = iaa.blur_mean_shift_(np.copy(image), 0, 0)
assert image_blurred.shape == image.shape
assert image_blurred.dtype.name == "uint8"
assert not np.array_equal(image_blurred, image)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.blur_mean_shift_(np.copy(image), 1.0, 1.0)
assert image_aug.shape == image.shape
class TestGaussianBlur(unittest.TestCase):
def setUp(self):
reseed()
def test_sigma_is_zero(self):
# no blur, shouldnt change anything
base_img = np.array([[0, 0, 0],
[0, 255, 0],
[0, 0, 0]], dtype=np.uint8)
base_img = base_img[:, :, np.newaxis]
images = np.array([base_img])
aug = iaa.GaussianBlur(sigma=0)
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
def test_low_sigma(self):
base_img = np.array([[0, 0, 0],
[0, 255, 0],
[0, 0, 0]], dtype=np.uint8)
base_img = base_img[:, :, np.newaxis]
images = np.array([base_img])
images_list = [base_img]
outer_pixels = ([], [])
for i in sm.xrange(base_img.shape[0]):
for j in sm.xrange(base_img.shape[1]):
if i != j:
outer_pixels[0].append(i)
outer_pixels[1].append(j)
# weak blur of center pixel
aug = iaa.GaussianBlur(sigma=0.5)
aug_det = aug.to_deterministic()
# images as numpy array
observed = aug.augment_images(images)
assert 100 < observed[0][1, 1] < 255
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 0).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 50).all()
observed = aug_det.augment_images(images)
assert 100 < observed[0][1, 1] < 255
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 0).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 50).all()
# images as list
observed = aug.augment_images(images_list)
assert 100 < observed[0][1, 1] < 255
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 0).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 50).all()
observed = aug_det.augment_images(images_list)
assert 100 < observed[0][1, 1] < 255
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 0).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 50).all()
def test_keypoints_dont_change(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
kpsoi = [ia.KeypointsOnImage(kps, shape=(3, 3, 1))]
aug = iaa.GaussianBlur(sigma=0.5)
aug_det = aug.to_deterministic()
observed = aug.augment_keypoints(kpsoi)
expected = kpsoi
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(kpsoi)
expected = kpsoi
assert keypoints_equal(observed, expected)
def test_sigma_is_tuple(self):
# varying blur sigmas
base_img = np.array([[0, 0, 0],
[0, 255, 0],
[0, 0, 0]], dtype=np.uint8)
base_img = base_img[:, :, np.newaxis]
images = np.array([base_img])
aug = iaa.GaussianBlur(sigma=(0, 1))
aug_det = aug.to_deterministic()
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.8)
assert nb_changed_aug_det == 0
def test_other_dtypes_bool_at_sigma_0(self):
# bool
aug = iaa.GaussianBlur(sigma=0)
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == image)
def test_other_dtypes_uint_int_at_sigma_0(self):
aug = iaa.GaussianBlur(sigma=0)
dts = [np.uint8, np.uint16, np.uint32,
np.int8, np.int16, np.int32]
for dtype in dts:
_min_value, center_value, _max_value = \
iadt.get_value_range_of_dtype(dtype)
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = int(center_value)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == image)
def test_other_dtypes_float_at_sigma_0(self):
aug = iaa.GaussianBlur(sigma=0)
dts = [np.float16, np.float32, np.float64]
for dtype in dts:
_min_value, center_value, _max_value = \
iadt.get_value_range_of_dtype(dtype)
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = center_value
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.allclose(image_aug, image)
def test_other_dtypes_bool_at_sigma_060(self):
# --
# blur of bool input at sigma=0.6
# --
# here we use a special mask and sigma as otherwise the only values
# ending up with >0.5 would be the ones that
# were before the blur already at >0.5
# prototype kernel, generated via:
# mask = np.zeros((5, 5), dtype=np.float64)
# mask[1, 0] = 255
# mask[2, 0] = 255
# mask[2, 2] = 255
# mask[2, 4] = 255
# mask[3, 0] = 255
# mask = ndimage.gaussian_filter(mask, 1.0, mode="mirror")
aug = iaa.GaussianBlur(sigma=0.6)
mask_bool = np.float64([
[ 57, 14, 2, 1, 1],
[142, 42, 29, 14, 28],
[169, 69, 114, 56, 114],
[142, 42, 29, 14, 28],
[ 57, 14, 2, 1, 1]
]) / 255.0
image = np.zeros((5, 5), dtype=bool)
image[1, 0] = True
image[2, 0] = True
image[2, 2] = True
image[2, 4] = True
image[3, 0] = True
image_aug = aug.augment_image(image)
expected = mask_bool > 0.5
assert image_aug.shape == mask_bool.shape
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == expected)
def test_other_dtypes_at_sigma_1(self):
# --
# blur of various dtypes at sigma=1.0
# and using an example value of 100 for int/uint/float and True for
# bool
# --
# prototype kernel, generated via:
# mask = np.zeros((5, 5), dtype=np.float64)
# mask[2, 2] = 100
# mask = ndimage.gaussian_filter(mask, 1.0, mode="mirror")
aug = iaa.GaussianBlur(sigma=1.0)
mask = np.float64([
[1, 2, 3, 2, 1],
[2, 5, 9, 5, 2],
[4, 9, 15, 9, 4],
[2, 5, 9, 5, 2],
[1, 2, 3, 2, 1]
])
# uint, int
uint_dts = [np.uint8, np.uint16, np.uint32]
int_dts = [np.int8, np.int16, np.int32]
for dtype in uint_dts + int_dts:
image = np.zeros((5, 5), dtype=dtype)
image[2, 2] = 100
image_aug = aug.augment_image(image)
expected = mask.astype(dtype)
diff = np.abs(image_aug.astype(np.int64)
- expected.astype(np.int64))
assert image_aug.shape == mask.shape
assert image_aug.dtype.type == dtype
assert np.max(diff) <= 4
assert np.average(diff) <= 2
# float
float_dts = [np.float16, np.float32, np.float64]
for dtype in float_dts:
image = np.zeros((5, 5), dtype=dtype)
image[2, 2] = 100.0
image_aug = aug.augment_image(image)
expected = mask.astype(dtype)
diff = np.abs(image_aug.astype(np.float64)
- expected.astype(np.float64))
assert image_aug.shape == mask.shape
assert image_aug.dtype.type == dtype
assert np.max(diff) < 4
assert np.average(diff) < 2.0
def test_other_dtypes_at_sigma_040(self):
# --
# blur of various dtypes at sigma=0.4
# and using an example value of 100 for int/uint/float and True for
# bool
# --
aug = iaa.GaussianBlur(sigma=0.4)
# prototype kernel, generated via:
# mask = np.zeros((5, 5), dtype=np.uint8)
# mask[2, 2] = 100
# kernel = ndimage.gaussian_filter(mask, 0.4, mode="mirror")
mask = np.float64([
[0, 0, 0, 0, 0],
[0, 0, 3, 0, 0],
[0, 3, 83, 3, 0],
[0, 0, 3, 0, 0],
[0, 0, 0, 0, 0]
])
# uint, int
uint_dts = [np.uint8, np.uint16, np.uint32]
int_dts = [np.int8, np.int16, np.int32]
for dtype in uint_dts + int_dts:
image = np.zeros((5, 5), dtype=dtype)
image[2, 2] = 100
image_aug = aug.augment_image(image)
expected = mask.astype(dtype)
diff = np.abs(image_aug.astype(np.int64)
- expected.astype(np.int64))
assert image_aug.shape == mask.shape
assert image_aug.dtype.type == dtype
assert np.max(diff) <= 4
# float
float_dts = [np.float16, np.float32, np.float64]
for dtype in float_dts:
image = np.zeros((5, 5), dtype=dtype)
image[2, 2] = 100.0
image_aug = aug.augment_image(image)
expected = mask.astype(dtype)
diff = np.abs(image_aug.astype(np.float64)
- expected.astype(np.float64))
assert image_aug.shape == mask.shape
assert image_aug.dtype.type == dtype
assert np.max(diff) < 4.0
def test_other_dtypes_at_sigma_075(self):
# --
# blur of various dtypes at sigma=0.75
# and values being half-way between center and maximum for each dtype
# The goal of this test is to verify that no major loss of resolution
# happens for large dtypes.
# Such inaccuracies appear for float64 if used.
# --
aug = iaa.GaussianBlur(sigma=0.75)
# prototype kernel, generated via:
# mask = np.zeros((5, 5), dtype=np.int32)
# mask[2, 2] = 1000 * 1000
# kernel = ndimage.gaussian_filter(mask, 0.75)
mask = np.float64([
[ 923, 6650, 16163, 6650, 923],
[ 6650, 47896, 116408, 47896, 6650],
[ 16163, 116408, 282925, 116408, 16163],
[ 6650, 47896, 116408, 47896, 6650],
[ 923, 6650, 16163, 6650, 923]
]) / (1000.0 * 1000.0)
# uint, int
uint_dts = [np.uint8, np.uint16, np.uint32]
int_dts = [np.int8, np.int16, np.int32]
for dtype in uint_dts + int_dts:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
dynamic_range = max_value - min_value
value = int(center_value + 0.4 * max_value)
image = np.zeros((5, 5), dtype=dtype)
image[2, 2] = value
image_aug = aug.augment_image(image)
expected = (mask * value).astype(dtype)
diff = np.abs(image_aug.astype(np.int64)
- expected.astype(np.int64))
assert image_aug.shape == mask.shape
assert image_aug.dtype.type == dtype
if np.dtype(dtype).itemsize <= 1:
assert np.max(diff) <= 4
else:
assert np.max(diff) <= 0.01 * dynamic_range
# float
float_dts = [np.float16, np.float32, np.float64]
values = [5000, 1000*1000, 1000*1000*1000]
for dtype, value in zip(float_dts, values):
image = np.zeros((5, 5), dtype=dtype)
image[2, 2] = value
image_aug = aug.augment_image(image)
expected = (mask * value).astype(dtype)
diff = np.abs(image_aug.astype(np.float64)
- expected.astype(np.float64))
assert image_aug.shape == mask.shape
assert image_aug.dtype.type == dtype
# accepts difference of 2.0, 4.0, 8.0, 16.0 (at 1, 2, 4, 8 bytes,
# i.e. 8, 16, 32, 64 bit)
max_diff = np.dtype(dtype).itemsize * 0.01 * np.float64(value)
assert np.max(diff) < max_diff
# float128 is the only unsupported dtype (excluding non-numerics and
# complex dtypes)
@unittest.skipIf(
not hasattr(np, "float128"),
"Test can only be executed on systems that know numpy.float128"
)
def test_failure_on_invalid_dtypes(self):
# assert failure on invalid dtypes
aug = iaa.GaussianBlur(sigma=1.0)
for dt in [np.float128]:
got_exception = False
try:
_ = aug.augment_image(np.zeros((1, 1), dtype=dt))
except Exception as exc:
assert "forbidden dtype" in str(exc)
got_exception = True
assert got_exception
def test_pickleable(self):
aug = iaa.GaussianBlur((0.1, 3.0), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
class TestAverageBlur(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(TestAverageBlur, self).__init__(*args, **kwargs)
base_img = np.zeros((11, 11, 1), dtype=np.uint8)
base_img[5, 5, 0] = 200
base_img[4, 5, 0] = 100
base_img[6, 5, 0] = 100
base_img[5, 4, 0] = 100
base_img[5, 6, 0] = 100
blur3x3 = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 11, 11, 11, 0, 0, 0, 0],
[0, 0, 0, 11, 44, 56, 44, 11, 0, 0, 0],
[0, 0, 0, 11, 56, 67, 56, 11, 0, 0, 0],
[0, 0, 0, 11, 44, 56, 44, 11, 0, 0, 0],
[0, 0, 0, 0, 11, 11, 11, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
blur3x3 = np.array(blur3x3, dtype=np.uint8)[..., np.newaxis]
blur4x4 = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0],
[0, 0, 0, 6, 25, 31, 31, 25, 6, 0, 0],
[0, 0, 0, 6, 31, 38, 38, 31, 6, 0, 0],
[0, 0, 0, 6, 31, 38, 38, 31, 6, 0, 0],
[0, 0, 0, 6, 25, 31, 31, 25, 6, 0, 0],
[0, 0, 0, 0, 6, 6, 6, 6, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
blur4x4 = np.array(blur4x4, dtype=np.uint8)[..., np.newaxis]
blur5x5 = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 4, 4, 4, 4, 4, 0, 0, 0],
[0, 0, 4, 16, 20, 20, 20, 16, 4, 0, 0],
[0, 0, 4, 20, 24, 24, 24, 20, 4, 0, 0],
[0, 0, 4, 20, 24, 24, 24, 20, 4, 0, 0],
[0, 0, 4, 20, 24, 24, 24, 20, 4, 0, 0],
[0, 0, 4, 16, 20, 20, 20, 16, 4, 0, 0],
[0, 0, 0, 4, 4, 4, 4, 4, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]
blur5x5 = np.array(blur5x5, dtype=np.uint8)[..., np.newaxis]
self.base_img = base_img
self.blur3x3 = blur3x3
self.blur4x4 = blur4x4
self.blur5x5 = blur5x5
def setUp(self):
reseed()
def test_kernel_size_0(self):
# no blur, shouldnt change anything
aug = iaa.AverageBlur(k=0)
observed = aug.augment_image(self.base_img)
assert np.array_equal(observed, self.base_img)
def test_kernel_size_3(self):
# k=3
aug = iaa.AverageBlur(k=3)
observed = aug.augment_image(self.base_img)
assert np.array_equal(observed, self.blur3x3)
def test_kernel_size_5(self):
# k=5
aug = iaa.AverageBlur(k=5)
observed = aug.augment_image(self.base_img)
assert np.array_equal(observed, self.blur5x5)
def test_kernel_size_is_tuple(self):
# k as (3, 4)
aug = iaa.AverageBlur(k=(3, 4))
nb_iterations = 100
nb_seen = [0, 0]
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(self.base_img)
if np.array_equal(observed, self.blur3x3):
nb_seen[0] += 1
elif np.array_equal(observed, self.blur4x4):
nb_seen[1] += 1
else:
raise Exception("Unexpected result in AverageBlur@1")
p_seen = [v/nb_iterations for v in nb_seen]
assert 0.4 <= p_seen[0] <= 0.6
assert 0.4 <= p_seen[1] <= 0.6
def test_kernel_size_is_tuple_with_wider_range(self):
# k as (3, 5)
aug = iaa.AverageBlur(k=(3, 5))
nb_iterations = 200
nb_seen = [0, 0, 0]
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(self.base_img)
if np.array_equal(observed, self.blur3x3):
nb_seen[0] += 1
elif np.array_equal(observed, self.blur4x4):
nb_seen[1] += 1
elif np.array_equal(observed, self.blur5x5):
nb_seen[2] += 1
else:
raise Exception("Unexpected result in AverageBlur@2")
p_seen = [v/nb_iterations for v in nb_seen]
assert 0.23 <= p_seen[0] <= 0.43
assert 0.23 <= p_seen[1] <= 0.43
assert 0.23 <= p_seen[2] <= 0.43
def test_kernel_size_is_stochastic_parameter(self):
# k as stochastic parameter
aug = iaa.AverageBlur(k=iap.Choice([3, 5]))
nb_iterations = 100
nb_seen = [0, 0]
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(self.base_img)
if np.array_equal(observed, self.blur3x3):
nb_seen[0] += 1
elif np.array_equal(observed, self.blur5x5):
nb_seen[1] += 1
else:
raise Exception("Unexpected result in AverageBlur@3")
p_seen = [v/nb_iterations for v in nb_seen]
assert 0.4 <= p_seen[0] <= 0.6
assert 0.4 <= p_seen[1] <= 0.6
def test_kernel_size_is_tuple_of_tuples(self):
# k as ((3, 5), (3, 5))
aug = iaa.AverageBlur(k=((3, 5), (3, 5)))
possible = dict()
for kh in [3, 4, 5]:
for kw in [3, 4, 5]:
key = (kh, kw)
if kh == 0 or kw == 0:
possible[key] = np.copy(self.base_img)
else:
possible[key] = cv2.blur(
self.base_img, (kh, kw))[..., np.newaxis]
nb_iterations = 250
nb_seen = dict([(key, 0) for key, val in possible.items()])
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(self.base_img)
for key, img_aug in possible.items():
if np.array_equal(observed, img_aug):
nb_seen[key] += 1
# dont check sum here, because 0xX and Xx0 are all the same, i.e. much
# higher sum than nb_iterations
assert np.all([v > 0 for v in nb_seen.values()])
def test_more_than_four_channels(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.AverageBlur(k=3)(image=image)
assert image_aug.shape == image.shape
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.AverageBlur(k=3)(image=image)
assert image_aug.shape == image.shape
def test_keypoints_dont_change(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
kpsoi = [ia.KeypointsOnImage(kps, shape=(11, 11, 1))]
aug = iaa.AverageBlur(k=3)
aug_det = aug.to_deterministic()
observed = aug.augment_keypoints(kpsoi)
expected = kpsoi
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(kpsoi)
expected = kpsoi
assert keypoints_equal(observed, expected)
def test_other_dtypes_k0(self):
aug = iaa.AverageBlur(k=0)
# bool
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image[2, 2] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == image)
# uint, int
uint_dts = [np.uint8, np.uint16]
int_dts = [np.int8, np.int16]
for dtype in uint_dts + int_dts:
_min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = int(center_value + 0.4 * max_value)
image[2, 2] = int(center_value + 0.4 * max_value)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.all(image_aug == image)
# float
float_dts = [np.float16, np.float32, np.float64]
values = [5000, 1000*1000, 1000*1000*1000]
for dtype, value in zip(float_dts, values):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image[2, 2] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == dtype
assert np.allclose(image_aug, image)
def test_other_dtypes_k3_value_100(self):
# --
# blur of various dtypes at k=3
# and using an example value of 100 for int/uint/float and True for
# bool
# --
aug = iaa.AverageBlur(k=3)
# prototype mask
# we place values in a 3x3 grid at positions (row=1, col=1) and
# (row=2, col=2) (beginning with 0)
# AverageBlur uses cv2.blur(), which uses BORDER_REFLECT_101 as its
# default padding mode,
# see https://docs.opencv.org/3.1.0/d2/de8/group__core__array.html
# the matrix below shows the 3x3 grid and the padded row/col values
# around it
# [1, 0, 1, 0, 1]
# [0, 0, 0, 0, 0]
# [1, 0, 1, 0, 1]
# [0, 0, 0, 1, 0]
# [1, 0, 1, 0, 1]
mask = np.float64([
[4/9, 2/9, 4/9],
[2/9, 2/9, 3/9],
[4/9, 3/9, 5/9]
])
# bool
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image[2, 2] = True
image_aug = aug.augment_image(image)
expected = mask > 0.5
assert image_aug.dtype.type == np.bool_
assert np.all(image_aug == expected)
# uint, int
uint_dts = [np.uint8, np.uint16]
int_dts = [np.int8, np.int16]
for dtype in uint_dts + int_dts:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = 100
image[2, 2] = 100
image_aug = aug.augment_image(image)
# cv2.blur() applies rounding for int/uint dtypes
expected = np.round(mask * 100).astype(dtype)
diff = np.abs(image_aug.astype(np.int64)
- expected.astype(np.int64))
assert image_aug.dtype.type == dtype
assert np.max(diff) <= 2
# float
float_dts = [np.float16, np.float32, np.float64]
for dtype in float_dts:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = 100.0
image[2, 2] = 100.0
image_aug = aug.augment_image(image)
expected = (mask * 100.0).astype(dtype)
diff = np.abs(image_aug.astype(np.float64)
- expected.astype(np.float64))
assert image_aug.dtype.type == dtype
assert np.max(diff) < 1.0
def test_other_dtypes_k3_dynamic_value(self):
# --
# blur of various dtypes at k=3
# and values being half-way between center and maximum for each
# dtype (bool is skipped as it doesnt make any sense here)
# The goal of this test is to verify that no major loss of resolution
# happens for large dtypes.
# --
aug = iaa.AverageBlur(k=3)
# prototype mask (see above)
mask = np.float64([
[4/9, 2/9, 4/9],
[2/9, 2/9, 3/9],
[4/9, 3/9, 5/9]
])
# uint, int
uint_dts = [np.uint8, np.uint16]
int_dts = [np.int8, np.int16]
for dtype in uint_dts + int_dts:
_min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = int(center_value + 0.4 * max_value)
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image[2, 2] = value
image_aug = aug.augment_image(image)
expected = (mask * value).astype(dtype)
diff = np.abs(image_aug.astype(np.int64)
- expected.astype(np.int64))
assert image_aug.dtype.type == dtype
# accepts difference of 4, 8, 16 (at 1, 2, 4 bytes, i.e. 8, 16,
# 32 bit)
assert np.max(diff) <= 2**(1 + np.dtype(dtype).itemsize)
# float
float_dts = [np.float16, np.float32, np.float64]
values = [5000, 1000*1000, 1000*1000*1000]
for dtype, value in zip(float_dts, values):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image[2, 2] = value
image_aug = aug.augment_image(image)
expected = (mask * value).astype(dtype)
diff = np.abs(image_aug.astype(np.float64)
- expected.astype(np.float64))
assert image_aug.dtype.type == dtype
# accepts difference of 2.0, 4.0, 8.0, 16.0 (at 1, 2, 4, 8 bytes,
# i.e. 8, 16, 32, 64 bit)
assert np.max(diff) < 2**(1 + np.dtype(dtype).itemsize)
def test_failure_on_invalid_dtypes(self):
# assert failure on invalid dtypes
aug = iaa.AverageBlur(k=3)
for dt in [np.uint32, np.uint64, np.int32, np.int64]:
got_exception = False
try:
_ = aug.augment_image(np.zeros((1, 1), dtype=dt))
except Exception as exc:
assert "forbidden dtype" in str(exc)
got_exception = True
assert got_exception
def test_pickleable(self):
aug = iaa.AverageBlur((1, 11), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
class TestMedianBlur(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(TestMedianBlur, self).__init__(*args, **kwargs)
base_img = np.zeros((11, 11, 1), dtype=np.uint8)
base_img[3:8, 3:8, 0] = 1
base_img[4:7, 4:7, 0] = 2
base_img[5:6, 5:6, 0] = 3
blur3x3 = np.zeros_like(base_img)
blur3x3[3:8, 3:8, 0] = 1
blur3x3[4:7, 4:7, 0] = 2
blur3x3[4, 4, 0] = 1
blur3x3[4, 6, 0] = 1
blur3x3[6, 4, 0] = 1
blur3x3[6, 6, 0] = 1
blur3x3[3, 3, 0] = 0
blur3x3[3, 7, 0] = 0
blur3x3[7, 3, 0] = 0
blur3x3[7, 7, 0] = 0
blur5x5 = np.copy(blur3x3)
blur5x5[4, 3, 0] = 0
blur5x5[3, 4, 0] = 0
blur5x5[6, 3, 0] = 0
blur5x5[7, 4, 0] = 0
blur5x5[4, 7, 0] = 0
blur5x5[3, 6, 0] = 0
blur5x5[6, 7, 0] = 0
blur5x5[7, 6, 0] = 0
blur5x5[blur5x5 > 1] = 1
self.base_img = base_img
self.blur3x3 = blur3x3
self.blur5x5 = blur5x5
def setUp(self):
reseed()
def test_k_is_1(self):
# no blur, shouldnt change anything
aug = iaa.MedianBlur(k=1)
observed = aug.augment_image(self.base_img)
assert np.array_equal(observed, self.base_img)
def test_k_is_3(self):
# k=3
aug = iaa.MedianBlur(k=3)
observed = aug.augment_image(self.base_img)
assert np.array_equal(observed, self.blur3x3)
def test_k_is_5(self):
# k=5
aug = iaa.MedianBlur(k=5)
observed = aug.augment_image(self.base_img)
assert np.array_equal(observed, self.blur5x5)
def test_k_is_tuple(self):
# k as (3, 5)
aug = iaa.MedianBlur(k=(3, 5))
seen = [False, False]
for i in sm.xrange(100):
observed = aug.augment_image(self.base_img)
if np.array_equal(observed, self.blur3x3):
seen[0] = True
elif np.array_equal(observed, self.blur5x5):
seen[1] = True
else:
raise Exception("Unexpected result in MedianBlur@1")
if all(seen):
break
assert np.all(seen)
def test_k_is_stochastic_parameter(self):
# k as stochastic parameter
aug = iaa.MedianBlur(k=iap.Choice([3, 5]))
seen = [False, False]
for i in sm.xrange(100):
observed = aug.augment_image(self.base_img)
if np.array_equal(observed, self.blur3x3):
seen[0] += True
elif np.array_equal(observed, self.blur5x5):
seen[1] += True
else:
raise Exception("Unexpected result in MedianBlur@2")
if all(seen):
break
assert np.all(seen)
def test_more_than_four_channels(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.MedianBlur(k=3)(image=image)
assert image_aug.shape == image.shape
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.MedianBlur(k=3)(image=image)
assert image_aug.shape == image.shape
def test_keypoints_not_changed(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
kpsoi = [ia.KeypointsOnImage(kps, shape=(11, 11, 1))]
aug = iaa.MedianBlur(k=3)
aug_det = aug.to_deterministic()
observed = aug.augment_keypoints(kpsoi)
expected = kpsoi
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(kpsoi)
expected = kpsoi
assert keypoints_equal(observed, expected)
def test_pickleable(self):
aug = iaa.MedianBlur((1, 11), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
# TODO extend these tests
class TestBilateralBlur(unittest.TestCase):
def setUp(self):
reseed()
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.BilateralBlur(3)(image=image)
assert image_aug.shape == image.shape
def test_pickleable(self):
aug = iaa.BilateralBlur((1, 11), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
class TestMotionBlur(unittest.TestCase):
def setUp(self):
reseed()
def test_simple_parameters(self):
# simple scenario
aug = iaa.MotionBlur(k=3, angle=0, direction=0.0)
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(10)
]
expected = np.float32([
[0, 1.0/3, 0],
[0, 1.0/3, 0],
[0, 1.0/3, 0]
])
for matrices_image in matrices:
for matrix_channel in matrices_image:
assert np.allclose(matrix_channel, expected)
def test_simple_parameters_angle_is_90(self):
# 90deg angle
aug = iaa.MotionBlur(k=3, angle=90, direction=0.0)
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(10)
]
expected = np.float32([
[0, 0, 0],
[1.0/3, 1.0/3, 1.0/3],
[0, 0, 0]
])
for matrices_image in matrices:
for matrix_channel in matrices_image:
assert np.allclose(matrix_channel, expected)
def test_simple_parameters_angle_is_45(self):
# 45deg angle
aug = iaa.MotionBlur(k=3, angle=45, direction=0.0, order=0)
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(10)
]
expected = np.float32([
[0, 0, 1.0/3],
[0, 1.0/3, 0],
[1.0/3, 0, 0]
])
for matrices_image in matrices:
for matrix_channel in matrices_image:
assert np.allclose(matrix_channel, expected)
def test_simple_parameters_angle_is_list(self):
# random angle
aug = iaa.MotionBlur(k=3, angle=[0, 90], direction=0.0)
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(50)
]
expected1 = np.float32([
[0, 1.0/3, 0],
[0, 1.0/3, 0],
[0, 1.0/3, 0]
])
expected2 = np.float32([
[0, 0, 0],
[1.0/3, 1.0/3, 1.0/3],
[0, 0, 0],
])
nb_seen = [0, 0]
for matrices_image in matrices:
assert np.allclose(matrices_image[0], matrices_image[1])
assert np.allclose(matrices_image[1], matrices_image[2])
for matrix_channel in matrices_image:
if np.allclose(matrix_channel, expected1):
nb_seen[0] += 1
elif np.allclose(matrix_channel, expected2):
nb_seen[1] += 1
assert nb_seen[0] > 0
assert nb_seen[1] > 0
def test_k_is_5_angle_90(self):
# 5x5
aug = iaa.MotionBlur(k=5, angle=90, direction=0.0)
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(10)
]
expected = np.float32([
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[1.0/5, 1.0/5, 1.0/5, 1.0/5, 1.0/5],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
])
for matrices_image in matrices:
for matrix_channel in matrices_image:
assert np.allclose(matrix_channel, expected)
def test_k_is_list_angle_90(self):
# random k
aug = iaa.MotionBlur(k=[3, 5], angle=90, direction=0.0)
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(50)
]
expected1 = np.float32([
[0, 0, 0],
[1.0/3, 1.0/3, 1.0/3],
[0, 0, 0],
])
expected2 = np.float32([
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[1.0/5, 1.0/5, 1.0/5, 1.0/5, 1.0/5],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
])
nb_seen = [0, 0]
for matrices_image in matrices:
assert np.allclose(matrices_image[0], matrices_image[1])
assert np.allclose(matrices_image[1], matrices_image[2])
for matrix_channel in matrices_image:
if (matrix_channel.shape == expected1.shape
and np.allclose(matrix_channel, expected1)):
nb_seen[0] += 1
elif (matrix_channel.shape == expected2.shape
and np.allclose(matrix_channel, expected2)):
nb_seen[1] += 1
assert nb_seen[0] > 0
assert nb_seen[1] > 0
def test_failure_on_continuous_kernel_sizes(self):
# k with choice [a, b, c, ...] must error in case of non-discrete
# values
got_exception = False
try:
_ = iaa.MotionBlur(k=[3, 3.5, 4])
except Exception as exc:
assert "to only contain integer" in str(exc)
got_exception = True
assert got_exception
# TODO extend this to test sampled kernel sizes
def test_k_is_tuple(self):
# no error in case of (a, b), checks for #215
aug = iaa.MotionBlur(k=(3, 7))
for _ in range(10):
_ = aug.augment_image(np.zeros((11, 11, 3), dtype=np.uint8))
def test_direction_is_1(self):
# direction 1.0
aug = iaa.MotionBlur(k=3, angle=0, direction=1.0)
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(10)
]
expected = np.float32([
[0, 1.0/1.5, 0],
[0, 0.5/1.5, 0],
[0, 0.0/1.5, 0]
])
for matrices_image in matrices:
for matrix_channel in matrices_image:
assert np.allclose(matrix_channel, expected, rtol=0, atol=1e-2)
def test_direction_is_minus_1(self):
# direction -1.0
aug = iaa.MotionBlur(k=3, angle=0, direction=-1.0)
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(10)
]
expected = np.float32([
[0, 0.0/1.5, 0],
[0, 0.5/1.5, 0],
[0, 1.0/1.5, 0]
])
for matrices_image in matrices:
for matrix_channel in matrices_image:
assert np.allclose(matrix_channel, expected, rtol=0, atol=1e-2)
def test_direction_is_list(self):
# random direction
aug = iaa.MotionBlur(k=3, angle=[0, 90], direction=[-1.0, 1.0])
matrix_func = aug.matrix
matrices = [
matrix_func(
np.zeros((128, 128, 3), dtype=np.uint8),
3,
iarandom.RNG(i)
) for i in range(50)
]
expected1 = np.float32([
[0, 1.0/1.5, 0],
[0, 0.5/1.5, 0],
[0, 0.0/1.5, 0]
])
expected2 = np.float32([
[0, 0.0/1.5, 0],
[0, 0.5/1.5, 0],
[0, 1.0/1.5, 0]
])
nb_seen = [0, 0]
for matrices_image in matrices:
assert np.allclose(matrices_image[0], matrices_image[1])
assert np.allclose(matrices_image[1], matrices_image[2])
for matrix_channel in matrices_image:
if np.allclose(matrix_channel, expected1, rtol=0, atol=1e-2):
nb_seen[0] += 1
elif np.allclose(matrix_channel, expected2, rtol=0, atol=1e-2):
nb_seen[1] += 1
assert nb_seen[0] > 0
assert nb_seen[1] > 0
def test_k_is_3_angle_is_90_verify_results(self):
# test of actual augmenter
img = np.zeros((7, 7, 3), dtype=np.uint8)
img[3-1:3+2, 3-1:3+2, :] = 255
aug = iaa.MotionBlur(k=3, angle=90, direction=0.0)
img_aug = aug.augment_image(img)
v1 = (255*(1/3))
v2 = (255*(1/3)) * 2
v3 = (255*(1/3)) * 3
expected = np.float32([
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, v1, v2, v3, v2, v1, 0],
[0, v1, v2, v3, v2, v1, 0],
[0, v1, v2, v3, v2, v1, 0],
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0]
]).astype(np.uint8)
expected = np.tile(expected[..., np.newaxis], (1, 1, 3))
assert np.allclose(img_aug, expected)
def test_pickleable(self):
aug = iaa.MotionBlur((3, 11), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
class TestMeanShiftBlur(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
aug = iaa.MeanShiftBlur()
assert np.isclose(aug.spatial_window_radius.a.value, 5.0)
assert np.isclose(aug.spatial_window_radius.b.value, 40.0)
assert np.isclose(aug.color_window_radius.a.value, 5.0)
assert np.isclose(aug.color_window_radius.b.value, 40.0)
def test___init___custom(self):
aug = iaa.MeanShiftBlur(
spatial_radius=[1.0, 2.0, 3.0],
color_radius=iap.Deterministic(5)
)
assert np.allclose(aug.spatial_window_radius.a, [1.0, 2.0, 3.0])
assert aug.color_window_radius.value == 5
def test_draw_samples(self):
aug = iaa.MeanShiftBlur(
spatial_radius=[1.0, 2.0, 3.0],
color_radius=(1.0, 2.0)
)
batch = mock.Mock()
batch.nb_rows = 100
samples = aug._draw_samples(batch, iarandom.RNG(0))
assert np.all(
np.isclose(samples[0], 1.0)
| np.isclose(samples[0], 2.0)
| np.isclose(samples[0], 3.0)
)
assert np.all((1.0 <= samples[1]) | (samples[1] <= 2.0))
@mock.patch("imgaug.augmenters.blur.blur_mean_shift_")
def test_mocked(self, mock_ms):
aug = iaa.MeanShiftBlur(
spatial_radius=1,
color_radius=2
)
image = np.zeros((1, 1, 3), dtype=np.uint8)
mock_ms.return_value = image
_image_aug = aug(image=image)
kwargs = mock_ms.call_args_list[0][1]
assert mock_ms.call_count == 1
assert np.isclose(kwargs["spatial_window_radius"], 1.0)
assert np.isclose(kwargs["color_window_radius"], 2.0)
def test_batch_without_images(self):
aug = iaa.MeanShiftBlur()
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(5, 5, 3))
kps_aug = aug(keypoints=kpsoi)
assert kps_aug.keypoints[0].x == 0
assert kps_aug.keypoints[0].y == 1
def test_get_parameters(self):
aug = iaa.MeanShiftBlur()
params = aug.get_parameters()
assert params[0] is aug.spatial_window_radius
assert params[1] is aug.color_window_radius
def test_pickleable(self):
aug = iaa.MeanShiftBlur(seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(40, 40, 3))
================================================
FILE: test/augmenters/test_collections.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
from imgaug import augmenters as iaa
from imgaug.testutils import reseed, runtest_pickleable_uint8_img
class TestRandAugment(unittest.TestCase):
def setUp(self):
reseed()
# for some reason these mocks don't work with
# imgaug.augmenters.collections.(...)
@mock.patch("imgaug.augmenters.RandAugment._create_initial_augmenters_list")
@mock.patch("imgaug.augmenters.RandAugment._create_main_augmenters_list")
def test_n(self, mock_main, mock_initial):
mock_main.return_value = [iaa.Add(1), iaa.Add(2), iaa.Add(4)]
mock_initial.return_value = []
img = np.zeros((1, 1, 3), dtype=np.uint8)
expected = {
0: [0],
1: [1, 2, 4],
2: [1+1, 1+2, 1+4, 2+2, 2+4, 4+4]
}
for n in [0, 1, 2]:
with self.subTest(n=n):
aug = iaa.RandAugment(n=n)
img_aug = aug(image=img)
assert img_aug[0, 0, 0] in expected[n]
# for some reason these mocks don't work with
# imgaug.augmenters.collections.(...)
@mock.patch("imgaug.augmenters.RandAugment._create_initial_augmenters_list")
@mock.patch("imgaug.augmenters.RandAugment._create_main_augmenters_list")
def test_m(self, mock_main, mock_initial):
def _create_main_list(m, _cval):
return [iaa.Add(m)]
mock_main.side_effect = _create_main_list
mock_initial.return_value = []
img = np.zeros((1, 1, 3), dtype=np.uint8)
for m in [0, 1, 2]:
with self.subTest(m=m):
aug = iaa.RandAugment(m=m)
img_aug = aug(image=img)
assert img_aug[0, 0, 0] == m
def test_cval(self):
cval = 200
aug = iaa.RandAugment(n=1, m=30, cval=cval)
img = np.zeros((20, 20, 3), dtype=np.uint8)
x_cval = False
y_cval = False
# lots of iterations here, because only in some iterations an affine
# translation is actually applied
for _ in np.arange(500):
img_aug = aug(image=img)
x_cval = x_cval or np.all(img_aug[:, :1] == cval)
x_cval = x_cval or np.all(img_aug[:, -1:] == cval)
y_cval = y_cval or np.all(img_aug[:1, :] == cval)
y_cval = y_cval or np.all(img_aug[-1:, :] == cval)
if np.all([x_cval, y_cval]):
break
assert np.all([x_cval, y_cval])
def test_get_parameters(self):
aug = iaa.RandAugment(n=1, m=30, cval=100)
params = aug.get_parameters()
assert params[0] is aug[1].n
assert params[1] is aug._m
assert params[2] is aug._cval
def test_pickleable(self):
aug = iaa.RandAugment(m=(0, 10), n=(1, 2))
runtest_pickleable_uint8_img(aug, iterations=50)
================================================
FILE: test/augmenters/test_color.py
================================================
from __future__ import print_function, division, absolute_import
import itertools
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import copy as copylib
import numpy as np
import six.moves as sm
import cv2
import imgaug as ia
import imgaug.random as iarandom
from imgaug import augmenters as iaa
from imgaug import parameters as iap
import imgaug.augmenters.meta as meta
from imgaug.testutils import (reseed, runtest_pickleable_uint8_img,
is_parameter_instance)
import imgaug.augmenters.color as colorlib
class Test_change_colorspace_(unittest.TestCase):
def test_non_uint8_fails(self):
image = np.arange(4*5*3).astype(np.uint8).reshape((4, 5, 3))
image = np.copy(image) # reshape sets flag OWNDATA=False
image_float = image.astype(np.float32) / 255.0
with self.assertRaises(ValueError) as cm:
_ = iaa.change_colorspace_(image_float, iaa.CSPACE_BGR)
assert "which is a forbidden dtype" in str(cm.exception)
def test_unknown_to_colorspace_fails(self):
image = np.arange(4*5*3).astype(np.uint8).reshape((4, 5, 3))
image = np.copy(image) # reshape sets flag OWNDATA=False
from_cspace = iaa.CSPACE_RGB
to_cspace = "foo"
with self.assertRaises(AssertionError) as cm:
_ = iaa.change_colorspace_(
image, to_colorspace=to_cspace, from_colorspace=from_cspace)
assert "Expected `to_colorspace` to be one of" in str(cm.exception)
def test_unknown_from_colorspace_fails(self):
image = np.arange(4*5*3).astype(np.uint8).reshape((4, 5, 3))
image = np.copy(image) # reshape sets flag OWNDATA=False
from_cspace = "foo"
to_cspace = iaa.CSPACE_RGB
with self.assertRaises(AssertionError) as cm:
_ = iaa.change_colorspace_(
image, to_colorspace=to_cspace, from_colorspace=from_cspace)
assert "Expected `from_colorspace` to be one of" in str(cm.exception)
def test_change_to_same_colorspace_does_nothing(self):
image = np.arange(4*5*3).astype(np.uint8).reshape((4, 5, 3))
image = np.copy(image) # reshape sets flag OWNDATA=False
from_cspace = iaa.CSPACE_RGB
to_cspace = iaa.CSPACE_RGB
image_out = iaa.change_colorspace_(
np.copy(image),
to_colorspace=to_cspace, from_colorspace=from_cspace)
assert np.array_equal(image_out, image)
def test_function_works_inplace(self):
image = np.arange(4*5*3).astype(np.uint8).reshape((4, 5, 3))
image = np.copy(image) # reshape sets flag OWNDATA=False
image_orig = np.copy(image)
from_cspace = iaa.CSPACE_RGB
to_cspace = iaa.CSPACE_BGR
image_out = iaa.change_colorspace_(
image,
to_colorspace=to_cspace, from_colorspace=from_cspace)
assert image_out is image
assert np.array_equal(image_out, image)
assert not np.array_equal(image_out, image_orig)
def test_image_is_view(self):
image = np.arange(4*5*4).astype(np.uint8).reshape((4, 5, 4))
image = np.copy(image) # reshape sets flag OWNDATA=False
image_copy = np.copy(image)
image_view = image[..., 0:3]
assert image_view.flags["OWNDATA"] is False
from_cspace = iaa.CSPACE_RGB
to_cspace = iaa.CSPACE_BGR
image_out = iaa.change_colorspace_(
image_view,
to_colorspace=to_cspace, from_colorspace=from_cspace)
expected = self._generate_expected_image(
np.ascontiguousarray(image_copy[..., 0:3]),
from_cspace, to_cspace)
assert np.array_equal(image_out, expected)
def test_image_is_noncontiguous(self):
image = np.arange(4*5*3).astype(np.uint8).reshape((4, 5, 3))
image = np.copy(image) # reshape sets flag OWNDATA=False
image_copy = np.copy(np.ascontiguousarray(np.fliplr(image)))
image_noncontiguous = np.fliplr(image)
assert image_noncontiguous.flags["C_CONTIGUOUS"] is False
from_cspace = iaa.CSPACE_RGB
to_cspace = iaa.CSPACE_BGR
image_out = iaa.change_colorspace_(
image_noncontiguous,
to_colorspace=to_cspace, from_colorspace=from_cspace)
expected = self._generate_expected_image(image_copy, from_cspace,
to_cspace)
assert np.array_equal(image_out, expected)
def test_cannot_transform_from_grayscale_to_another_cspace(self):
image = np.arange(4*5*3).astype(np.uint8).reshape((4, 5, 3))
image = np.copy(image) # reshape sets flag OWNDATA=False
from_cspace = iaa.CSPACE_GRAY
to_cspace = iaa.CSPACE_RGB
with self.assertRaises(AssertionError) as cm:
_ = iaa.change_colorspace_(
np.copy(image),
from_colorspace=from_cspace, to_colorspace=to_cspace)
assert (
"Cannot convert from grayscale to another colorspace"
in str(cm.exception))
def test_image_without_channels_fails(self):
image = np.arange(4*5).astype(np.uint8).reshape((4, 5))
image = np.copy(image) # reshape sets flag OWNDATA=False
from_cspace = iaa.CSPACE_RGB
to_cspace = iaa.CSPACE_BGR
with self.assertRaises(AssertionError) as cm:
_ = iaa.change_colorspace_(
np.copy(image),
from_colorspace=from_cspace, to_colorspace=to_cspace)
assert (
"Expected image shape to be three-dimensional"
in str(cm.exception))
def test_image_with_four_channels_fails(self):
image = np.arange(4*5*4).astype(np.uint8).reshape((4, 5, 4))
image = np.copy(image) # reshape sets flag OWNDATA=False
from_cspace = iaa.CSPACE_RGB
to_cspace = iaa.CSPACE_BGR
with self.assertRaises(AssertionError) as cm:
_ = iaa.change_colorspace_(
np.copy(image),
from_colorspace=from_cspace, to_colorspace=to_cspace)
assert (
"Expected number of channels to be three"
in str(cm.exception))
def test_colorspace_combinations(self):
image = np.arange(4*5*3).astype(np.uint8).reshape((4, 5, 3))
image = np.copy(image) # reshape sets flag OWNDATA=False
from_cspaces = iaa.CSPACE_ALL
to_cspaces = iaa.CSPACE_ALL
gen = itertools.product(from_cspaces, to_cspaces)
for from_cspace, to_cspace in gen:
if from_cspace == iaa.CSPACE_GRAY:
continue
with self.subTest(from_colorspace=from_cspace,
to_colorspace=to_cspace):
image_out = iaa.change_colorspace_(np.copy(image), to_cspace,
from_cspace)
if from_cspace == to_cspace:
expected = np.copy(image)
else:
expected = self._generate_expected_image(image, from_cspace,
to_cspace)
if to_cspace == iaa.CSPACE_GRAY:
expected = np.tile(expected[..., np.newaxis], (1, 1, 3))
assert np.array_equal(image_out, expected)
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.change_colorspace_(
np.copy(image), from_colorspace="RGB", to_colorspace="BGR")
assert image_aug.shape == image.shape
@classmethod
def _generate_expected_image(cls, image, from_colorspace, to_colorspace):
cv_vars = colorlib._CSPACE_OPENCV_CONV_VARS
if from_colorspace == iaa.CSPACE_RGB:
from2rgb = None
else:
from2rgb = cv_vars[(from_colorspace, iaa.CSPACE_RGB)]
if to_colorspace == iaa.CSPACE_RGB:
rgb2to = None
else:
rgb2to = cv_vars[(iaa.CSPACE_RGB, to_colorspace)]
image_rgb = image
if from2rgb is not None:
image_rgb = cv2.cvtColor(image, from2rgb)
image_out = image_rgb
if rgb2to is not None:
image_out = cv2.cvtColor(image_rgb, rgb2to)
return image_out
class Test_change_color_temperatures_(unittest.TestCase):
def test_single_image(self):
image = np.full((1, 1, 3), 255, dtype=np.uint8)
multipliers = [
(1000, [255, 56, 0]),
(1100, [255, 71, 0]),
(1200, [255, 83, 0]),
(1300, [255, 93, 0]),
(4300, [255, 215, 177]),
(4400, [255, 217, 182]),
(4500, [255, 219, 186]),
(4600, [255, 221, 190]),
(11100, [196, 214, 255]),
(11200, [195, 214, 255]),
(11300, [195, 214, 255]),
(11400, [194, 213, 255]),
(17200, [173, 200, 255]),
(17300, [173, 200, 255]),
(17400, [173, 200, 255]),
(21900, [166, 195, 255]),
(31300, [158, 190, 255]),
(39700, [155, 188, 255]),
(39800, [155, 188, 255]),
(39900, [155, 188, 255]),
(40000, [155, 188, 255])
]
for kelvin, multiplier in multipliers:
with self.subTest(kelvin=kelvin):
image_temp = iaa.change_color_temperatures_(
[np.copy(image)],
kelvins=kelvin)[0]
expected = np.uint8(multiplier).reshape((1, 1, 3))
assert np.array_equal(image_temp, expected)
def test_three_images_as_list(self):
# separate tests for three and four images due to possible
# broadcasting errors, see issue #646
image = np.full((1, 1, 3), 255, dtype=np.uint8)
images_temp = iaa.change_color_temperatures_(
[np.copy(image), np.copy(image), np.copy(image)],
[11100, 11200, 11300]
)
expected = np.array([
[196, 214, 255],
[195, 214, 255],
[195, 214, 255]
], dtype=np.uint8).reshape((3, 1, 1, 3))
assert isinstance(images_temp, list)
assert np.array_equal(images_temp[0], expected[0])
assert np.array_equal(images_temp[1], expected[1])
assert np.array_equal(images_temp[2], expected[2])
def test_four_images_as_list(self):
# separate tests for three and four images due to possible
# broadcasting errors, see issue #646
image = np.full((1, 1, 3), 255, dtype=np.uint8)
images_temp = iaa.change_color_temperatures_(
[np.copy(image), np.copy(image), np.copy(image), np.copy(image)],
[11100, 11200, 11300, 11100]
)
expected = np.array([
[196, 214, 255],
[195, 214, 255],
[195, 214, 255],
[196, 214, 255]
], dtype=np.uint8).reshape((4, 1, 1, 3))
assert isinstance(images_temp, list)
assert np.array_equal(images_temp[0], expected[0])
assert np.array_equal(images_temp[1], expected[1])
assert np.array_equal(images_temp[2], expected[2])
def test_three_images_as_array(self):
# separate tests for three and four images due to possible
# broadcasting errors, see issue #646
image = np.full((1, 1, 3), 255, dtype=np.uint8)
images_temp = iaa.change_color_temperatures_(
np.uint8([np.copy(image), np.copy(image), np.copy(image)]),
np.float32([11100, 11200, 11300])
)
expected = np.array([
[196, 214, 255],
[195, 214, 255],
[195, 214, 255]
], dtype=np.uint8).reshape((3, 1, 1, 3))
assert ia.is_np_array(images_temp)
assert np.array_equal(images_temp, expected)
def test_four_images_as_array(self):
# separate tests for three and four images due to possible
# broadcasting errors, see issue #646
image = np.full((1, 1, 3), 255, dtype=np.uint8)
images_temp = iaa.change_color_temperatures_(
np.uint8([np.copy(image), np.copy(image), np.copy(image),
np.copy(image)]),
np.float32([11100, 11200, 11300, 11100])
)
expected = np.array([
[196, 214, 255],
[195, 214, 255],
[195, 214, 255],
[196, 214, 255]
], dtype=np.uint8).reshape((4, 1, 1, 3))
assert ia.is_np_array(images_temp)
assert np.array_equal(images_temp, expected)
def test_interpolation_of_kelvins(self):
# at 1000: [255, 56, 0]
# at 1100: [255, 71, 0]
at1050 = [255, 56 + (71-56)/2, 0]
image = np.full((1, 1, 3), 255, dtype=np.uint8)
image_temp = iaa.change_color_temperatures_(
[np.copy(image)],
kelvins=1050)[0]
expected = np.uint8(at1050).reshape((1, 1, 3))
diff = np.abs(image_temp.astype(np.int32) - expected.astype(np.int32))
assert np.all(diff <= 1)
def test_from_colorspace(self):
image_bgr = np.uint8([100, 255, 0]).reshape((1, 1, 3))
image_temp = iaa.change_color_temperatures_(
[np.copy(image_bgr)],
kelvins=1000,
from_colorspaces=iaa.CSPACE_BGR
)[0]
multiplier_rgb = np.float32(
[255/255.0, 56/255.0, 0/255.0]
).reshape((1, 1, 3))
expected = (
image_bgr[:, :, ::-1].astype(np.float32)
* multiplier_rgb
).astype(np.uint8)[:, :, ::-1]
diff = np.abs(image_temp.astype(np.int32) - expected.astype(np.int32))
assert np.all(diff <= 1)
class Test_change_color_temperature_(unittest.TestCase):
@mock.patch("imgaug.augmenters.color.change_color_temperatures_")
def test_calls_batch_function(self, mock_ccts):
image = np.full((1, 1, 3), 255, dtype=np.uint8)
mock_ccts.return_value = ["example"]
image_temp = iaa.change_color_temperature(
image, 1000, from_colorspace="foo")
assert image_temp == "example"
assert np.array_equal(mock_ccts.call_args_list[0][0][0],
image[np.newaxis, ...])
assert mock_ccts.call_args_list[0][0][1] == [1000]
assert mock_ccts.call_args_list[0][1]["from_colorspaces"] == ["foo"]
def test_single_image(self):
image = np.full((1, 1, 3), 255, dtype=np.uint8)
image_temp = iaa.change_color_temperature(np.copy(image), 1000)
expected = np.uint8([255, 56, 0]).reshape((1, 1, 3))
assert np.array_equal(image_temp, expected)
class _BatchCapturingDummyAugmenter(iaa.Augmenter):
def __init__(self):
super(_BatchCapturingDummyAugmenter, self).__init__()
self.last_batch = None
def _augment_batch_(self, batch, random_state, parents, hooks):
self.last_batch = copylib.deepcopy(batch.deepcopy())
return batch
def get_parameters(self):
return []
class TestWithBrightnessChannels(unittest.TestCase):
def setUp(self):
reseed()
@property
def valid_colorspaces(self):
return iaa.WithBrightnessChannels._VALID_COLORSPACES
def test___init___defaults(self):
aug = iaa.WithBrightnessChannels()
assert isinstance(aug.children, iaa.Augmenter)
assert len(aug.to_colorspace.a) == len(self.valid_colorspaces)
for cspace in self.valid_colorspaces:
assert cspace in aug.to_colorspace.a
assert aug.from_colorspace == iaa.CSPACE_RGB
def test___init___to_colorspace_is_all(self):
aug = iaa.WithBrightnessChannels(to_colorspace=ia.ALL)
assert isinstance(aug.children, iaa.Augmenter)
assert len(aug.to_colorspace.a) == len(self.valid_colorspaces)
for cspace in self.valid_colorspaces:
assert cspace in aug.to_colorspace.a
assert aug.from_colorspace == iaa.CSPACE_RGB
def test___init___to_colorspace_is_cspace(self):
aug = iaa.WithBrightnessChannels(to_colorspace=iaa.CSPACE_YUV)
assert isinstance(aug.children, iaa.Augmenter)
assert aug.to_colorspace.value == iaa.CSPACE_YUV
assert aug.from_colorspace == iaa.CSPACE_RGB
def test___init___to_colorspace_is_stochastic_parameter(self):
aug = iaa.WithBrightnessChannels(
to_colorspace=iap.Deterministic(iaa.CSPACE_YUV))
assert isinstance(aug.children, iaa.Augmenter)
assert aug.to_colorspace.value == iaa.CSPACE_YUV
assert aug.from_colorspace == iaa.CSPACE_RGB
def test_every_colorspace(self):
def _image_to_channel(image, cspace):
if cspace == iaa.CSPACE_YCrCb:
image_cvt = cv2.cvtColor(image, cv2.COLOR_RGB2YCR_CB)
return image_cvt[:, :, 0:0+1]
elif cspace == iaa.CSPACE_HSV:
image_cvt = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
return image_cvt[:, :, 2:2+1]
elif cspace == iaa.CSPACE_HLS:
image_cvt = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
return image_cvt[:, :, 1:1+1]
elif cspace == iaa.CSPACE_Lab:
if hasattr(cv2, "COLOR_RGB2Lab"):
image_cvt = cv2.cvtColor(image, cv2.COLOR_RGB2Lab)
else:
image_cvt = cv2.cvtColor(image, cv2.COLOR_RGB2LAB)
return image_cvt[:, :, 0:0+1]
elif cspace == iaa.CSPACE_Luv:
if hasattr(cv2, "COLOR_RGB2Luv"):
image_cvt = cv2.cvtColor(image, cv2.COLOR_RGB2Luv)
else:
image_cvt = cv2.cvtColor(image, cv2.COLOR_RGB2LUV)
return image_cvt[:, :, 0:0+1]
else:
assert cspace == iaa.CSPACE_YUV
image_cvt = cv2.cvtColor(image, cv2.COLOR_RGB2YUV)
return image_cvt[:, :, 0:0+1]
# Max differences between input image and image after augmentation
# when no child augmenter is used (for the given example image below).
# For some colorspaces the conversion to input colorspace isn't
# perfect.
# Values were manually checked.
max_diff_expected = {
iaa.CSPACE_YCrCb: 1,
iaa.CSPACE_HSV: 0,
iaa.CSPACE_HLS: 0,
iaa.CSPACE_Lab: 2,
iaa.CSPACE_Luv: 4,
iaa.CSPACE_YUV: 1
}
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
for cspace in self.valid_colorspaces:
with self.subTest(colorspace=cspace):
child = _BatchCapturingDummyAugmenter()
aug = iaa.WithBrightnessChannels(
children=child,
to_colorspace=cspace)
image_aug = aug(image=image)
expected = _image_to_channel(image, cspace)
diff = np.abs(
image.astype(np.int32) - image_aug.astype(np.int32))
assert np.all(diff <= max_diff_expected[cspace])
assert np.array_equal(child.last_batch.images[0], expected)
def test_random_colorspace(self):
def _images_to_cspaces(images, choices):
result = np.full((len(images),), -1, dtype=np.int32)
for i, image_aug in enumerate(images):
for j, choice in enumerate(choices):
if np.array_equal(image_aug, choice):
result[i] = j
break
assert np.all(result != -1)
return result
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
expected_hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)[:, :, 2:2+1]
expected_hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)[:, :, 1:1+1]
child = _BatchCapturingDummyAugmenter()
aug = iaa.WithBrightnessChannels(
children=child,
to_colorspace=[iaa.CSPACE_HSV, iaa.CSPACE_HLS])
images = [np.copy(image) for _ in sm.xrange(100)]
_ = aug(images=images)
images_aug1 = child.last_batch.images
_ = aug(images=images)
images_aug2 = child.last_batch.images
cspaces1 = _images_to_cspaces(images_aug1, [expected_hsv, expected_hls])
cspaces2 = _images_to_cspaces(images_aug2, [expected_hsv, expected_hls])
assert np.any(cspaces1 != cspaces2)
assert len(np.unique(cspaces1)) > 1
assert len(np.unique(cspaces2)) > 1
def test_from_colorspace_is_not_rgb(self):
child = _BatchCapturingDummyAugmenter()
aug = iaa.WithBrightnessChannels(
children=child,
to_colorspace=iaa.CSPACE_HSV,
from_colorspace=iaa.CSPACE_BGR)
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
expected_hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)[:, :, 2:2+1]
_ = aug(image=image)
observed = child.last_batch.images
assert np.array_equal(observed[0], expected_hsv)
def test_changes_from_child_propagate(self):
aug = iaa.WithBrightnessChannels(
children=iaa.Add(100),
to_colorspace=iaa.CSPACE_HSV)
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
image_aug = aug(image=image)
assert not np.array_equal(image_aug, image)
def test_using_hooks_to_deactivate_propagation(self):
def _propagator(images, augmenter, parents, default):
return False if augmenter.name == "foo" else default
aug = iaa.WithBrightnessChannels(
children=iaa.Add(100),
to_colorspace=iaa.CSPACE_HSV,
name="foo")
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
image_aug = aug(image=image,
hooks=ia.HooksImages(propagator=_propagator))
assert np.array_equal(image_aug, image)
def test_batch_without_images(self):
aug = iaa.WithBrightnessChannels(
children=iaa.Affine(translate_px={"x": 1}))
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=0, y=2)],
shape=(1, 1, 3))
kpsoi_aug = aug(keypoints=kpsoi)
assert np.isclose(kpsoi_aug.keypoints[0].x, 1.0)
assert np.isclose(kpsoi_aug.keypoints[0].y, 2.0)
def test_to_deterministic(self):
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
aug = iaa.WithBrightnessChannels(iaa.Add((-100, 100)))
aug_det = aug.to_deterministic()
images = np.tile(image[np.newaxis, ...], (50, 1, 1, 1))
images_aug1 = aug_det(images=images)
images_aug2 = aug_det(images=images)
assert not np.array_equal(images_aug1, images)
assert np.array_equal(images_aug1, images_aug2)
def test_get_parameters(self):
aug = iaa.WithBrightnessChannels(to_colorspace=iaa.CSPACE_HSV)
params = aug.get_parameters()
assert params[0].value == iaa.CSPACE_HSV
assert params[1] == iaa.CSPACE_RGB
def test___str__(self):
child = iaa.Identity()
aug = iaa.WithBrightnessChannels(
child,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_HSV,
name="foo")
aug_str = aug.__str__()
expected_child = iaa.Sequential([child], name="foo-then")
expected = (
"WithBrightnessChannels("
"to_colorspace=%s, "
"from_colorspace=RGB, "
"name=foo, "
"children=%s, "
"deterministic=False)" % (
str(aug.to_colorspace),
str(expected_child),
)
)
assert aug_str == expected
def test_get_children_lists(self):
child = iaa.Identity()
aug = iaa.WithBrightnessChannels([child])
children_lsts = aug.get_children_lists()
assert len(children_lsts) == 1
assert len(children_lsts[0]) == 1
assert children_lsts[0][0] is child
# TODO this test exists two times
def test_to_deterministic2(self):
child = iaa.Identity()
aug = iaa.WithBrightnessChannels([child])
aug_det = aug.to_deterministic()
assert aug_det.deterministic
assert aug_det.random_state is not aug.random_state
assert aug_det.children.deterministic
assert aug_det.children[0].deterministic
def test_pickleable(self):
aug = iaa.WithBrightnessChannels(iaa.Add((0, 50), seed=1), seed=2)
runtest_pickleable_uint8_img(aug, iterations=10)
# MultiplyBrightness re-used MultiplyAndAddToBrightness, so we don't have
# to test much here.
class TestMultiplyBrightness(unittest.TestCase):
def setUp(self):
reseed()
@property
def valid_colorspaces(self):
return iaa.WithBrightnessChannels._VALID_COLORSPACES
def test___init___defaults(self):
aug = iaa.MultiplyBrightness()
assert isinstance(aug.children, iaa.Augmenter)
assert isinstance(aug.children[0], iaa.Multiply)
assert len(aug.to_colorspace.a) == len(self.valid_colorspaces)
for cspace in self.valid_colorspaces:
assert cspace in aug.to_colorspace.a
assert aug.from_colorspace == iaa.CSPACE_RGB
def test_single_image(self):
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
aug = iaa.MultiplyBrightness(2.0)
image_aug = aug(image=image)
assert np.average(image_aug) > np.average(image)
def test_pickleable(self):
aug = iaa.MultiplyBrightness((0.5, 1.5), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
# AddToBrightness re-used MultiplyAndAddToBrightness, so we don't have
# to test much here.
class TestAddToBrightness(unittest.TestCase):
def setUp(self):
reseed()
@property
def valid_colorspaces(self):
return iaa.WithBrightnessChannels._VALID_COLORSPACES
def test___init___defaults(self):
aug = iaa.AddToBrightness()
assert isinstance(aug.children, iaa.Augmenter)
assert isinstance(aug.children[1], iaa.Add)
assert len(aug.to_colorspace.a) == len(self.valid_colorspaces)
for cspace in self.valid_colorspaces:
assert cspace in aug.to_colorspace.a
assert aug.from_colorspace == iaa.CSPACE_RGB
def test_single_image(self):
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
aug = iaa.AddToBrightness(100)
image_aug = aug(image=image)
assert np.average(image_aug) > np.average(image)
def test_pickleable(self):
aug = iaa.AddToBrightness((0, 50), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
class TestMultiplyAndAddToBrightness(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
aug = iaa.MultiplyAndAddToBrightness()
assert aug.children.random_order is True
assert isinstance(aug.children[0], iaa.Multiply)
assert isinstance(aug.children[1], iaa.Add)
assert iaa.CSPACE_HSV in aug.to_colorspace.a
assert aug.from_colorspace == iaa.CSPACE_RGB
def test___init___add_is_zero(self):
aug = iaa.MultiplyAndAddToBrightness(add=0)
assert aug.children.random_order is True
assert isinstance(aug.children[0], iaa.Multiply)
assert isinstance(aug.children[1], iaa.Identity)
assert iaa.CSPACE_HSV in aug.to_colorspace.a
assert aug.from_colorspace == iaa.CSPACE_RGB
def test___init___mul_is_1(self):
aug = iaa.MultiplyAndAddToBrightness(mul=1.0)
assert aug.children.random_order is True
assert isinstance(aug.children[0], iaa.Identity)
assert isinstance(aug.children[1], iaa.Add)
assert iaa.CSPACE_HSV in aug.to_colorspace.a
assert aug.from_colorspace == iaa.CSPACE_RGB
def test_add_to_example_image(self):
aug = iaa.MultiplyAndAddToBrightness(mul=1.0, add=10,
to_colorspace=iaa.CSPACE_HSV,
random_order=False)
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
image_aug = aug(image=image)
expected = cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype(np.float32)
expected[:, :, 2] += 10
expected = cv2.cvtColor(expected.astype(np.uint8), cv2.COLOR_HSV2RGB)
assert np.array_equal(image_aug, expected)
def test_multiply_example_image(self):
aug = iaa.MultiplyAndAddToBrightness(mul=1.2, add=0,
to_colorspace=iaa.CSPACE_HSV,
random_order=False)
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
image_aug = aug(image=image)
expected = cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype(np.float32)
expected[:, :, 2] *= 1.2
expected = cv2.cvtColor(expected.astype(np.uint8), cv2.COLOR_HSV2RGB)
assert np.allclose(image_aug, expected, rtol=0, atol=1.01)
def test_multiply_and_add_example_image(self):
aug = iaa.MultiplyAndAddToBrightness(mul=1.2, add=10,
to_colorspace=iaa.CSPACE_HSV,
random_order=False)
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
image_aug = aug(image=image)
expected = cv2.cvtColor(image, cv2.COLOR_RGB2HSV).astype(np.float32)
expected[:, :, 2] *= 1.2
expected[:, :, 2] += 10
expected = cv2.cvtColor(expected.astype(np.uint8), cv2.COLOR_HSV2RGB)
assert np.allclose(image_aug, expected, rtol=0, atol=1.01)
def test___str__(self):
params = [
(1.01, 1, iaa.Multiply(1.01), iaa.Add(1)),
(1.00, 1, iaa.Identity(), iaa.Add(1)),
(1.01, 0, iaa.Multiply(1.01), iaa.Identity()),
(1.00, 0, iaa.Identity(), iaa.Identity()),
]
for mul, add, exp_mul, exp_add in params:
with self.subTest(mul=mul, add=add):
aug = iaa.MultiplyAndAddToBrightness(
mul=mul,
add=add,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_HSV,
name="foo")
aug_str = aug.__str__()
expected = (
"MultiplyAndAddToBrightness("
"mul=%s, "
"add=%s, "
"to_colorspace=%s, "
"from_colorspace=RGB, "
"random_order=True, "
"name=foo, "
"deterministic=False)" % (
exp_mul,
exp_add,
str(aug.to_colorspace)
)
)
assert aug_str == expected
def test_pickleable(self):
aug = iaa.MultiplyAndAddToBrightness(mul=(0.5, 1.5), add=(0, 50),
seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
# TODO add tests for prop hooks
class TestWithHueAndSaturation(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
child = iaa.Identity()
aug = iaa.WithHueAndSaturation(child, from_colorspace="BGR")
assert isinstance(aug.children, list)
assert len(aug.children) == 1
assert aug.children[0] is child
assert aug.from_colorspace == "BGR"
aug = iaa.WithHueAndSaturation([child])
assert isinstance(aug.children, list)
assert len(aug.children) == 1
assert aug.children[0] is child
assert aug.from_colorspace == "RGB"
def test_augment_images(self):
class _DummyAugmenter(meta.Augmenter):
def __init__(self):
super(_DummyAugmenter, self).__init__()
self.call_count = 0
def _augment_batch_(self, batch, random_state, parents, hooks):
assert batch.images[0].dtype.name == "int16"
self.call_count += 1
return batch
def get_parameters(self):
return []
aug_dummy = _DummyAugmenter()
aug = iaa.WithHueAndSaturation(aug_dummy)
image = np.zeros((4, 4, 3), dtype=np.uint8)
image_aug = aug.augment_images([image])[0]
assert image_aug.dtype.name == "uint8"
assert np.array_equal(image_aug, image)
assert aug_dummy.call_count == 1
def test_augment_images__hue(self):
def augment_images(images, random_state, parents, hooks):
assert images[0].dtype.name == "int16"
images = np.copy(images)
images[..., 0] += 10
return images
aug = iaa.WithHueAndSaturation(iaa.Lambda(func_images=augment_images))
# example image
image = np.arange(0, 255).reshape((1, 255, 1)).astype(np.uint8)
image = np.tile(image, (1, 1, 3))
image[..., 0] += 0
image[..., 1] += 1
image[..., 2] += 2
# compute expected output
image_hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
image_hsv = image_hsv.astype(np.int16)
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/180)*255).astype(np.int16)
image_hsv[..., 0] += 10
image_hsv[..., 0] = np.mod(image_hsv[..., 0], 255)
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/255)*180).astype(np.int16)
image_hsv = image_hsv.astype(np.uint8)
image_expected = cv2.cvtColor(image_hsv, cv2.COLOR_HSV2RGB)
assert not np.array_equal(image_expected, image)
# augment and verify
images_aug = aug.augment_images(np.stack([image, image], axis=0))
assert ia.is_np_array(images_aug)
for image_aug in images_aug:
assert image_aug.shape == (1, 255, 3)
assert np.array_equal(image_aug, image_expected)
def test_augment_images__saturation(self):
def augment_images(images, random_state, parents, hooks):
assert images[0].dtype.name == "int16"
images = np.copy(images)
images[..., 1] += 10
return images
aug = iaa.WithHueAndSaturation(iaa.Lambda(func_images=augment_images))
# example image
image = np.arange(0, 255).reshape((1, 255, 1)).astype(np.uint8)
image = np.tile(image, (1, 1, 3))
image[..., 0] += 0
image[..., 1] += 1
image[..., 2] += 2
# compute expected output
image_hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
image_hsv = image_hsv.astype(np.int16)
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/180)*255).astype(np.int16)
image_hsv[..., 1] += 10
image_hsv[..., 1] = np.clip(image_hsv[..., 1], 0, 255)
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/255)*180).astype(np.int16)
image_hsv = image_hsv.astype(np.uint8)
image_expected = cv2.cvtColor(image_hsv, cv2.COLOR_HSV2RGB)
assert not np.array_equal(image_expected, image)
# augment and verify
images_aug = aug.augment_images(np.stack([image, image], axis=0))
assert ia.is_np_array(images_aug)
for image_aug in images_aug:
assert image_aug.shape == (1, 255, 3)
assert np.array_equal(image_aug, image_expected)
def test_augment_heatmaps(self):
from imgaug.augmentables.heatmaps import HeatmapsOnImage
class _DummyAugmenter(meta.Augmenter):
def __init__(self):
super(_DummyAugmenter, self).__init__()
self.call_count = 0
def _augment_batch_(self, batch, random_state, parents, hooks):
self.call_count += 1
return batch
def get_parameters(self):
return []
aug_dummy = _DummyAugmenter()
hm = np.ones((8, 12, 1), dtype=np.float32)
hmoi = HeatmapsOnImage(hm, shape=(16, 24, 3))
aug = iaa.WithHueAndSaturation(aug_dummy)
hmoi_aug = aug.augment_heatmaps(hmoi)
assert hmoi_aug.shape == (16, 24, 3)
assert hmoi_aug.arr_0to1.shape == (8, 12, 1)
assert aug_dummy.call_count == 1
def test_augment_keypoints(self):
from imgaug.augmentables.kps import KeypointsOnImage
class _DummyAugmenter(meta.Augmenter):
def __init__(self):
super(_DummyAugmenter, self).__init__()
self.call_count = 0
def _augment_batch_(self, batch, random_state, parents, hooks):
self.call_count += 1
return batch
def get_parameters(self):
return []
aug_dummy = _DummyAugmenter()
kpsoi = KeypointsOnImage.from_xy_array(np.float32([
[0, 0],
[5, 1]
]), shape=(16, 24, 3))
aug = iaa.WithHueAndSaturation(aug_dummy)
kpsoi_aug = aug.augment_keypoints(kpsoi)
assert kpsoi_aug.shape == (16, 24, 3)
assert kpsoi.keypoints[0].x == 0
assert kpsoi.keypoints[0].y == 0
assert kpsoi.keypoints[1].x == 5
assert kpsoi.keypoints[1].y == 1
assert aug_dummy.call_count == 1
def test__to_deterministic(self):
aug = iaa.WithHueAndSaturation([iaa.Identity()], from_colorspace="BGR")
aug_det = aug.to_deterministic()
assert not aug.deterministic # ensure copy
assert not aug.children[0].deterministic
assert aug_det.deterministic
assert isinstance(aug_det.children[0], iaa.Identity)
assert aug_det.children[0].deterministic
def test_get_parameters(self):
aug = iaa.WithHueAndSaturation([iaa.Identity()], from_colorspace="BGR")
assert aug.get_parameters()[0] == "BGR"
def test_get_children_lists(self):
child = iaa.Identity()
aug = iaa.WithHueAndSaturation(child)
children_lists = aug.get_children_lists()
assert len(children_lists) == 1
assert len(children_lists[0]) == 1
assert children_lists[0][0] is child
child = iaa.Identity()
aug = iaa.WithHueAndSaturation([child])
children_lists = aug.get_children_lists()
assert len(children_lists) == 1
assert len(children_lists[0]) == 1
assert children_lists[0][0] is child
def test___str__(self):
child = iaa.Sequential([iaa.Identity(name="foo")])
aug = iaa.WithHueAndSaturation(child)
observed = aug.__str__()
expected = (
"WithHueAndSaturation("
"from_colorspace=RGB, "
"name=UnnamedWithHueAndSaturation, "
"children=[%s], "
"deterministic=False"
")" % (child.__str__(),)
)
assert observed == expected
def test_pickleable(self):
aug = iaa.WithHueAndSaturation(iaa.Add((0, 50), seed=1), seed=2)
runtest_pickleable_uint8_img(aug, iterations=10)
class TestMultiplyHueAndSaturation(unittest.TestCase):
def setUp(self):
reseed()
def test_returns_correct_objects__mul(self):
aug = iaa.MultiplyHueAndSaturation(
(0.9, 1.1), per_channel=True)
assert isinstance(aug, iaa.WithHueAndSaturation)
assert isinstance(aug.children, iaa.Sequential)
assert len(aug.children) == 1
assert isinstance(aug.children[0], iaa.Multiply)
assert is_parameter_instance(aug.children[0].mul, iap.Uniform)
assert np.isclose(aug.children[0].mul.a.value, 0.9)
assert np.isclose(aug.children[0].mul.b.value, 1.1)
assert is_parameter_instance(aug.children[0].per_channel,
iap.Deterministic)
assert aug.children[0].per_channel.value == 1
def test_returns_correct_objects__mul_hue(self):
aug = iaa.MultiplyHueAndSaturation(mul_hue=(0.9, 1.1))
assert isinstance(aug, iaa.WithHueAndSaturation)
assert isinstance(aug.children, iaa.Sequential)
assert len(aug.children) == 1
assert isinstance(aug.children[0], iaa.WithChannels)
assert aug.children[0].channels == [0]
assert len(aug.children[0].children) == 1
assert isinstance(aug.children[0].children[0], iaa.Multiply)
assert is_parameter_instance(aug.children[0].children[0].mul,
iap.Uniform)
assert np.isclose(aug.children[0].children[0].mul.a.value, 0.9)
assert np.isclose(aug.children[0].children[0].mul.b.value, 1.1)
def test_returns_correct_objects__mul_saturation(self):
aug = iaa.MultiplyHueAndSaturation(mul_saturation=(0.9, 1.1))
assert isinstance(aug, iaa.WithHueAndSaturation)
assert isinstance(aug.children, iaa.Sequential)
assert len(aug.children) == 1
assert isinstance(aug.children[0], iaa.WithChannels)
assert aug.children[0].channels == [1]
assert len(aug.children[0].children) == 1
assert isinstance(aug.children[0].children[0], iaa.Multiply)
assert is_parameter_instance(aug.children[0].children[0].mul,
iap.Uniform)
assert np.isclose(aug.children[0].children[0].mul.a.value, 0.9)
assert np.isclose(aug.children[0].children[0].mul.b.value, 1.1)
def test_returns_correct_objects__mul_hue_and_mul_saturation(self):
aug = iaa.MultiplyHueAndSaturation(mul_hue=(0.9, 1.1),
mul_saturation=(0.8, 1.2))
assert isinstance(aug, iaa.WithHueAndSaturation)
assert isinstance(aug.children, iaa.Sequential)
assert len(aug.children) == 2
assert isinstance(aug.children[0], iaa.WithChannels)
assert aug.children[0].channels == [0]
assert len(aug.children[0].children) == 1
assert isinstance(aug.children[0].children[0], iaa.Multiply)
assert is_parameter_instance(aug.children[0].children[0].mul,
iap.Uniform)
assert np.isclose(aug.children[0].children[0].mul.a.value, 0.9)
assert np.isclose(aug.children[0].children[0].mul.b.value, 1.1)
assert isinstance(aug.children[1], iaa.WithChannels)
assert aug.children[1].channels == [1]
assert len(aug.children[0].children) == 1
assert isinstance(aug.children[1].children[0], iaa.Multiply)
assert is_parameter_instance(aug.children[1].children[0].mul,
iap.Uniform)
assert np.isclose(aug.children[1].children[0].mul.a.value, 0.8)
assert np.isclose(aug.children[1].children[0].mul.b.value, 1.2)
def test_augment_images__mul(self):
aug = iaa.MultiplyHueAndSaturation(1.5)
# example image
image = np.arange(0, 255).reshape((1, 255, 1)).astype(np.uint8)
image = np.tile(image, (1, 1, 3))
image[..., 0] += 0
image[..., 1] += 5
image[..., 2] += 10
# compute expected output
image_hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
image_hsv = image_hsv.astype(np.int16) # simulate WithHueAndSaturation
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/180)*255).astype(np.int16)
image_hsv = image_hsv.astype(np.float32) # simulate Multiply
image_hsv[..., 0] *= 1.5
image_hsv[..., 1] *= 1.5
image_hsv = np.round(image_hsv).astype(np.int16)
image_hsv[..., 0] = np.mod(image_hsv[..., 0], 255)
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/255)*180).astype(np.int16)
image_hsv[..., 1] = np.clip(image_hsv[..., 1], 0, 255)
image_hsv = image_hsv.astype(np.uint8)
image_expected = cv2.cvtColor(image_hsv, cv2.COLOR_HSV2RGB)
assert not np.array_equal(image_expected, image)
# augment and verify
images_aug = aug.augment_images(np.stack([image, image], axis=0))
assert ia.is_np_array(images_aug)
for image_aug in images_aug:
assert image_aug.shape == (1, 255, 3)
diff = np.abs(image_aug.astype(np.int16) - image_expected)
assert np.all(diff <= 1)
def test_augment_images__mul_hue(self):
# this is almost identical to test_augment_images__mul
# only
# aug = ...
# and
# image_hsv[...] *= 1.2
# have been changed
aug = iaa.MultiplyHueAndSaturation(mul_hue=1.5) # changed over __mul
# example image
image = np.arange(0, 255).reshape((1, 255, 1)).astype(np.uint8)
image = np.tile(image, (1, 1, 3))
image[..., 0] += 0
image[..., 1] += 5
image[..., 2] += 10
# compute expected output
image_hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
image_hsv = image_hsv.astype(np.int16) # simulate WithHueAndSaturation
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/180)*255).astype(np.int16)
image_hsv = image_hsv.astype(np.float32) # simulate Multiply
image_hsv[..., 0] *= 1.5
image_hsv[..., 1] *= 1.0 # changed over __mul
image_hsv = np.round(image_hsv).astype(np.int16)
image_hsv[..., 0] = np.mod(image_hsv[..., 0], 255)
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/255)*180).astype(np.int16)
image_hsv[..., 1] = np.clip(image_hsv[..., 1], 0, 255)
image_hsv = image_hsv.astype(np.uint8)
image_expected = cv2.cvtColor(image_hsv, cv2.COLOR_HSV2RGB)
assert not np.array_equal(image_expected, image)
# augment and verify
images_aug = aug.augment_images(np.stack([image, image], axis=0))
assert ia.is_np_array(images_aug)
for image_aug in images_aug:
assert image_aug.shape == (1, 255, 3)
diff = np.abs(image_aug.astype(np.int16) - image_expected)
assert np.all(diff <= 1)
def test_augment_images__mul_saturation(self):
# this is almost identical to test_augment_images__mul
# only
# aug = ...
# and
# image_hsv[...] *= 1.2
# have been changed
aug = iaa.MultiplyHueAndSaturation(mul_saturation=1.5) # changed
# example image
image = np.arange(0, 255).reshape((1, 255, 1)).astype(np.uint8)
image = np.tile(image, (1, 1, 3))
image[..., 0] += 0
image[..., 1] += 5
image[..., 2] += 10
# compute expected output
image_hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
image_hsv = image_hsv.astype(np.int16) # simulate WithHueAndSaturation
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/180)*255).astype(np.int16)
image_hsv = image_hsv.astype(np.float32) # simulate Multiply
image_hsv[..., 0] *= 1.0 # changed over __mul
image_hsv[..., 1] *= 1.5
image_hsv = np.round(image_hsv).astype(np.int16)
image_hsv[..., 0] = np.mod(image_hsv[..., 0], 255)
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/255)*180).astype(np.int16)
image_hsv[..., 1] = np.clip(image_hsv[..., 1], 0, 255)
image_hsv = image_hsv.astype(np.uint8)
image_expected = cv2.cvtColor(image_hsv, cv2.COLOR_HSV2RGB)
assert not np.array_equal(image_expected, image)
# augment and verify
images_aug = aug.augment_images(np.stack([image, image], axis=0))
assert ia.is_np_array(images_aug)
for image_aug in images_aug:
assert image_aug.shape == (1, 255, 3)
diff = np.abs(image_aug.astype(np.int16) - image_expected)
assert np.all(diff <= 1)
def test_augment_images__mul_hue_and_mul_saturation(self):
# this is almost identical to test_augment_images__mul
# only
# aug = ...
# and
# image_hsv[...] *= 1.2
# have been changed
aug = iaa.MultiplyHueAndSaturation(mul_hue=1.5,
mul_saturation=1.6) # changed
# example image
image = np.arange(0, 255).reshape((1, 255, 1)).astype(np.uint8)
image = np.tile(image, (1, 1, 3))
image[..., 0] += 0
image[..., 1] += 5
image[..., 2] += 10
# compute expected output
image_hsv = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
image_hsv = image_hsv.astype(np.int16) # simulate WithHueAndSaturation
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/180)*255).astype(np.int16)
image_hsv = image_hsv.astype(np.float32) # simulate Multiply
image_hsv[..., 0] *= 1.5
image_hsv[..., 1] *= 1.6 # changed over __mul
image_hsv = np.round(image_hsv).astype(np.int16)
image_hsv[..., 0] = np.mod(image_hsv[..., 0], 255)
image_hsv[..., 0] = (
(image_hsv[..., 0].astype(np.float32)/255)*180).astype(np.int16)
image_hsv[..., 1] = np.clip(image_hsv[..., 1], 0, 255)
image_hsv = image_hsv.astype(np.uint8)
image_expected = cv2.cvtColor(image_hsv, cv2.COLOR_HSV2RGB)
assert not np.array_equal(image_expected, image)
# augment and verify
images_aug = aug.augment_images(np.stack([image, image], axis=0))
assert ia.is_np_array(images_aug)
for image_aug in images_aug:
assert image_aug.shape == (1, 255, 3)
diff = np.abs(image_aug.astype(np.int16) - image_expected)
assert np.all(diff <= 1)
def test_augment_images__deterministic(self):
rs = iarandom.RNG(1)
images = rs.integers(0, 255, size=(32, 4, 4, 3), dtype=np.uint8)
for deterministic in [False, True]:
aug = iaa.MultiplyHueAndSaturation(
mul=(0.1, 5.0))
if deterministic:
aug = aug.to_deterministic()
images_aug1 = aug.augment_images(images)
images_aug2 = aug.augment_images(images)
equal = np.array_equal(images_aug1, images_aug2)
if deterministic:
assert equal
else:
assert not equal
aug = iaa.MultiplyHueAndSaturation(
mul_hue=(0.1, 5.0), mul_saturation=(0.1, 5.0))
if deterministic:
aug = aug.to_deterministic()
images_aug1 = aug.augment_images(images)
images_aug2 = aug.augment_images(images)
equal = np.array_equal(images_aug1, images_aug2)
if deterministic:
assert equal
else:
assert not equal
def test_pickleable(self):
aug = iaa.MultiplyHueAndSaturation(mul_hue=(0.5, 1.5),
mul_saturation=(0.5, 1.5),
seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
class TestMultiplyHue(unittest.TestCase):
def test_returns_correct_class(self):
# this test is practically identical to
# TestMultiplyToHueAndSaturation.test_returns_correct_objects__mul_hue
aug = iaa.MultiplyHue((0.9, 1.1))
assert isinstance(aug, iaa.WithHueAndSaturation)
assert isinstance(aug.children, iaa.Sequential)
assert len(aug.children) == 1
assert isinstance(aug.children[0], iaa.WithChannels)
assert aug.children[0].channels == [0]
assert len(aug.children[0].children) == 1
assert isinstance(aug.children[0].children[0], iaa.Multiply)
assert is_parameter_instance(aug.children[0].children[0].mul,
iap.Uniform)
assert np.isclose(aug.children[0].children[0].mul.a.value, 0.9)
assert np.isclose(aug.children[0].children[0].mul.b.value, 1.1)
def test_pickleable(self):
aug = iaa.MultiplyHue((0.5, 1.5), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(50, 50, 3))
class TestMultiplySaturation(unittest.TestCase):
def test_returns_correct_class(self):
# this test is practically identical to
# TestMultiplyToHueAndSaturation
# .test_returns_correct_objects__mul_saturation
aug = iaa.MultiplySaturation((0.9, 1.1))
assert isinstance(aug, iaa.WithHueAndSaturation)
assert isinstance(aug.children, iaa.Sequential)
assert len(aug.children) == 1
assert isinstance(aug.children[0], iaa.WithChannels)
assert aug.children[0].channels == [1]
assert len(aug.children[0].children) == 1
assert isinstance(aug.children[0].children[0], iaa.Multiply)
assert is_parameter_instance(aug.children[0].children[0].mul,
iap.Uniform)
assert np.isclose(aug.children[0].children[0].mul.a.value, 0.9)
assert np.isclose(aug.children[0].children[0].mul.b.value, 1.1)
def test_pickleable(self):
aug = iaa.MultiplySaturation((0.5, 1.5), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(50, 50, 3))
class TestRemoveSaturation(unittest.TestCase):
@classmethod
def _compute_average_saturation(cls, image_rgb):
image_hsv = iaa.change_colorspace_(np.copy(image_rgb),
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_HSV)
return np.average(image_hsv[:, :, 1])
def test___init___defaults(self):
aug = iaa.RemoveSaturation()
multiply = aug.children[0].children[0]
assert isinstance(multiply.mul, iap.Subtract)
assert np.isclose(multiply.mul.other_param.value, 1.0)
assert np.isclose(multiply.mul.val.value, 1.0)
assert aug.from_colorspace == iaa.CSPACE_RGB
def test___init___custom(self):
aug = iaa.RemoveSaturation(0.7, from_colorspace=iaa.CSPACE_HSV)
multiply = aug.children[0].children[0]
assert isinstance(multiply.mul, iap.Subtract)
assert np.isclose(multiply.mul.other_param.value, 1.0)
assert np.isclose(multiply.mul.val.value, 0.7)
assert aug.from_colorspace == iaa.CSPACE_HSV
def test_on_images(self):
image = np.mod(
np.arange(20*20*3),
255
).astype(np.uint8).reshape((20, 20, 3))
# add 100 to the red channel here to make the image more saturated
image[..., 0] = np.clip((image[..., 0].astype(np.int32) + 100), 0, 255)
image_sat = self._compute_average_saturation(image)
aug = iaa.RemoveSaturation((0.2, 0.6))
images_aug = aug(images=[image] * 100)
saturations = []
for image_aug in images_aug:
sat = self._compute_average_saturation(image_aug)
saturations.append(sat)
assert len(set(np.int32(saturations))) > 10
# correct here to not use 1.0-x, as these are the saturations remaining
# after applying (1.0-x)*sat
# we add 0.02 here due to integer-float rounding effects
assert np.all(np.float32(saturations) <= (0.8 + 0.02) * image_sat)
assert np.any(np.float32(saturations) <= 0.5 * image_sat)
def test_pickleable(self):
aug = iaa.RemoveSaturation((0.1, 0.9), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(100, 100, 3))
class TestAddToHueAndSaturation(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def create_base_image(cls):
base_img = np.zeros((4, 2, 3), dtype=np.uint8)
base_img[0, :, 0] += 0
base_img[0, :, 1] += 1
base_img[0, :, 2] += 2
base_img[1, :, 0] += 20
base_img[1, :, 1] += 40
base_img[1, :, 2] += 60
base_img[2, :, 0] += 255
base_img[2, :, 1] += 128
base_img[2, :, 2] += 0
base_img[3, :, 0] += 255
base_img[3, :, 1] += 255
base_img[3, :, 2] += 255
return base_img
# interestingly, when using this RGB2HSV and HSV2RGB conversion from
# skimage, the results differ quite a bit from the cv2 ones
"""
def _add_hue_saturation(img, value):
img_hsv = color.rgb2hsv(img / 255.0)
img_hsv[..., 0:2] += (value / 255.0)
return color.hsv2rgb(img_hsv) * 255
"""
@classmethod
def _add_hue_saturation(cls, img, value=None, value_hue=None,
value_saturation=None):
if value is not None:
assert value_hue is None and value_saturation is None
else:
assert value_hue is not None or value_saturation is not None
if value is not None:
value_hue = value
value_saturation = value
else:
value_hue = value_hue or 0
value_saturation = value_saturation or 0
img_hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)
img_hsv = img_hsv.astype(np.int32)
img_hsv[..., 0] = np.mod(
img_hsv[..., 0] + int((value_hue/255.0) * (360/2)), 180)
img_hsv[..., 1] = np.clip(
img_hsv[..., 1] + value_saturation, 0, 255)
img_hsv = img_hsv.astype(np.uint8)
return cv2.cvtColor(img_hsv, cv2.COLOR_HSV2RGB)
def test___init__(self):
aug = iaa.AddToHueAndSaturation((-20, 20))
assert is_parameter_instance(aug.value, iap.DiscreteUniform)
assert aug.value.a.value == -20
assert aug.value.b.value == 20
assert aug.value_hue is None
assert aug.value_saturation is None
assert is_parameter_instance(aug.per_channel, iap.Deterministic)
assert aug.per_channel.value == 0
def test___init___value_none(self):
aug = iaa.AddToHueAndSaturation(value_hue=(-20, 20),
value_saturation=[0, 5, 10])
assert aug.value is None
assert is_parameter_instance(aug.value_hue, iap.DiscreteUniform)
assert is_parameter_instance(aug.value_saturation, iap.Choice)
assert aug.value_hue.a.value == -20
assert aug.value_hue.b.value == 20
assert aug.value_saturation.a == [0, 5, 10]
assert is_parameter_instance(aug.per_channel, iap.Deterministic)
assert aug.per_channel.value == 0
def test___init___per_channel(self):
aug = iaa.AddToHueAndSaturation(per_channel=0.5)
assert aug.value is None
assert aug.value_hue is not None
assert aug.value_saturation is not None
assert is_parameter_instance(aug.per_channel, iap.Binomial)
assert np.isclose(aug.per_channel.p.value, 0.5)
def test__generate_lut_table(self):
def _hue(v):
return np.mod(v, 180)
def _sat(v):
return np.clip(v, 0, 255)
tables = iaa.AddToHueAndSaturation._generate_lut_table()
table_hue, table_saturation = tables
intensity_values = [0, 1, 128, 254, 255] # = pixel values
for iv in intensity_values:
with self.subTest(intensity=iv):
assert table_hue[0, iv] == _hue(iv-255) # add value: -255
assert table_hue[1, iv] == _hue(iv-254) # add value: -254
assert table_hue[254, iv] == _hue(iv-1) # add value: -1
assert table_hue[255, iv] == _hue(iv-0) # add value: 0
assert table_hue[256, iv] == _hue(iv+1) # add value: 1
assert table_hue[509, iv] == _hue(iv+254) # add value: 254
assert table_hue[510, iv] == _hue(iv+255) # add value: 255
assert table_saturation[0, iv] == _sat(iv-255) # input: -255
assert table_saturation[1, iv] == _sat(iv-254) # input: -254
assert table_saturation[254, iv] == _sat(iv-1) # input: -1
assert table_saturation[255, iv] == _sat(iv+0) # input: 0
assert table_saturation[256, iv] == _sat(iv+1) # input: 1
assert table_saturation[509, iv] == _sat(iv+254) # input: 254
assert table_saturation[510, iv] == _sat(iv+255) # input: 255
def test_augment_images_compare_backends(self):
base_img = self.create_base_image()
gen = itertools.product([False, True], [-255, -100, -1, 0, 1, 100, 255])
for per_channel, value in gen:
with self.subTest(value=value, per_channel=per_channel):
aug_cv2 = iaa.AddToHueAndSaturation(value,
per_channel=per_channel)
aug_cv2.backend = "cv2"
aug_numpy = iaa.AddToHueAndSaturation(value,
per_channel=per_channel)
aug_numpy.backend = "numpy"
img_observed1 = aug_cv2(image=base_img)
img_observed2 = aug_numpy(image=base_img)
assert np.array_equal(img_observed1, img_observed2)
def test_augment_images(self):
base_img = self.create_base_image()
gen = itertools.product([False, True], ["cv2", "numpy"])
for per_channel, backend in gen:
with self.subTest(per_channel=per_channel, backend=backend):
aug = iaa.AddToHueAndSaturation(0, per_channel=per_channel)
aug.backend = backend
observed = aug.augment_image(base_img)
expected = base_img
assert np.allclose(observed, expected)
aug = iaa.AddToHueAndSaturation(30, per_channel=per_channel)
aug.backend = backend
observed = aug.augment_image(base_img)
expected = self._add_hue_saturation(base_img, 30)
diff = np.abs(observed.astype(np.float32) - expected)
assert np.all(diff <= 1)
aug = iaa.AddToHueAndSaturation(255, per_channel=per_channel)
aug.backend = backend
observed = aug.augment_image(base_img)
expected = self._add_hue_saturation(base_img, 255)
diff = np.abs(observed.astype(np.float32) - expected)
assert np.all(diff <= 1)
aug = iaa.AddToHueAndSaturation(-255, per_channel=per_channel)
aug.backend = backend
observed = aug.augment_image(base_img)
expected = self._add_hue_saturation(base_img, -255)
diff = np.abs(observed.astype(np.float32) - expected)
assert np.all(diff <= 1)
def test_augment_images__different_hue_and_saturation__no_per_channel(self):
base_img = self.create_base_image()
class _DummyParam(iap.StochasticParameter):
def _draw_samples(self, size, random_state):
arr = np.float32([10, 20])
return np.tile(arr[np.newaxis, :], (size[0], 1))
aug = iaa.AddToHueAndSaturation(value=_DummyParam(), per_channel=False)
img_expected = self._add_hue_saturation(base_img, value=10)
img_observed = aug.augment_image(base_img)
assert np.array_equal(img_observed, img_expected)
def test_augment_images__different_hue_and_saturation__per_channel(self):
base_img = self.create_base_image()
class _DummyParam(iap.StochasticParameter):
def _draw_samples(self, size, random_state):
arr = np.float32([10, 20])
return np.tile(arr[np.newaxis, :], (size[0], 1))
aug = iaa.AddToHueAndSaturation(value=_DummyParam(), per_channel=True)
img_expected = self._add_hue_saturation(
base_img, value_hue=10, value_saturation=20)
img_observed = aug.augment_image(base_img)
assert np.array_equal(img_observed, img_expected)
def test_augment_images__different_hue_and_saturation__mixed_perchan(self):
base_img = self.create_base_image()
class _DummyParamValue(iap.StochasticParameter):
def _draw_samples(self, size, random_state):
arr = np.float32([10, 20])
return np.tile(arr[np.newaxis, :], (size[0], 1))
class _DummyParamPerChannel(iap.StochasticParameter):
def _draw_samples(self, size, random_state):
assert size == (3,)
return np.float32([1.0, 0.0, 1.0])
aug = iaa.AddToHueAndSaturation(
value=_DummyParamValue(), per_channel=_DummyParamPerChannel())
img_expected1 = self._add_hue_saturation(
base_img, value_hue=10, value_saturation=20)
img_expected2 = self._add_hue_saturation(
base_img, value_hue=10, value_saturation=10)
img_expected3 = self._add_hue_saturation(
base_img, value_hue=10, value_saturation=20)
img_observed1, img_observed2, img_observed3, = \
aug.augment_images([base_img] * 3)
assert np.array_equal(img_observed1, img_expected1)
assert np.array_equal(img_observed2, img_expected2)
assert np.array_equal(img_observed3, img_expected3)
def test_augment_images__list_as_value(self):
base_img = self.create_base_image()
aug = iaa.AddToHueAndSaturation([0, 10, 20])
base_img = base_img[1:2, 0:1, :]
expected_imgs = [
iaa.AddToHueAndSaturation(0).augment_image(base_img),
iaa.AddToHueAndSaturation(10).augment_image(base_img),
iaa.AddToHueAndSaturation(20).augment_image(base_img)
]
assert not np.array_equal(expected_imgs[0], expected_imgs[1])
assert not np.array_equal(expected_imgs[1], expected_imgs[2])
assert not np.array_equal(expected_imgs[0], expected_imgs[2])
nb_iterations = 300
seen = dict([(i, 0) for i, _ in enumerate(expected_imgs)])
for _ in sm.xrange(nb_iterations):
observed = aug.augment_image(base_img)
for i, expected_img in enumerate(expected_imgs):
if np.allclose(observed, expected_img):
seen[i] += 1
assert np.sum(list(seen.values())) == nb_iterations
n_exp = nb_iterations / 3
n_exp_tol = nb_iterations * 0.1
assert all([n_exp - n_exp_tol < v < n_exp + n_exp_tol
for v in seen.values()])
def test_augment_images__value_hue(self):
base_img = self.create_base_image()
for value_hue in [-255, -254, -128, -64, -10, -1,
0, 1, 10, 64, 128, 254, 255]:
with self.subTest(value_hue=value_hue):
aug = iaa.AddToHueAndSaturation(value_hue=value_hue)
img_expected = self._add_hue_saturation(
base_img, value_hue=value_hue)
img_observed = aug(image=base_img)
assert np.array_equal(img_observed, img_expected)
def test_augment_images__value_hue__multi_image_sampling(self):
base_img = self.create_base_image()
class _DummyParam(iap.StochasticParameter):
def _draw_samples(self, size, random_state):
return np.float32([10, 20, 30])
aug = iaa.AddToHueAndSaturation(value_hue=_DummyParam())
img_expected1 = self._add_hue_saturation(base_img, value_hue=10)
img_expected2 = self._add_hue_saturation(base_img, value_hue=20)
img_expected3 = self._add_hue_saturation(base_img, value_hue=30)
img_observed1, img_observed2, img_observed3 = \
aug.augment_images([base_img] * 3)
assert np.array_equal(img_observed1, img_expected1)
assert np.array_equal(img_observed2, img_expected2)
assert np.array_equal(img_observed3, img_expected3)
def test_augment_images__value_saturation(self):
base_img = self.create_base_image()
for value_saturation in [-255, -254, -128, -64, -10,
0, 10, 64, 128, 254, 255]:
with self.subTest(value_hue=value_saturation):
aug = iaa.AddToHueAndSaturation(
value_saturation=value_saturation)
img_expected = self._add_hue_saturation(
base_img, value_saturation=value_saturation)
img_observed = aug(image=base_img)
assert np.array_equal(img_observed, img_expected)
def test_augment_images__value_saturation__multi_image_sampling(self):
base_img = self.create_base_image()
class _DummyParam(iap.StochasticParameter):
def _draw_samples(self, size, random_state):
return np.float32([10, 20, 30])
aug = iaa.AddToHueAndSaturation(value_saturation=_DummyParam())
img_expected1 = self._add_hue_saturation(base_img, value_saturation=10)
img_expected2 = self._add_hue_saturation(base_img, value_saturation=20)
img_expected3 = self._add_hue_saturation(base_img, value_saturation=30)
img_observed1, img_observed2, img_observed3 = \
aug.augment_images([base_img] * 3)
assert np.array_equal(img_observed1, img_expected1)
assert np.array_equal(img_observed2, img_expected2)
assert np.array_equal(img_observed3, img_expected3)
def test_augment_images__value_hue_and_value_saturation(self):
base_img = self.create_base_image()
class _DummyParam(iap.StochasticParameter):
def _draw_samples(self, size, random_state):
return np.float32([10, 20, 30])
aug = iaa.AddToHueAndSaturation(value_hue=_DummyParam(),
value_saturation=_DummyParam()+40)
img_expected1 = self._add_hue_saturation(base_img, value_hue=10,
value_saturation=40+10)
img_expected2 = self._add_hue_saturation(base_img, value_hue=20,
value_saturation=40+20)
img_expected3 = self._add_hue_saturation(base_img, value_hue=30,
value_saturation=40+30)
img_observed1, img_observed2, img_observed3 = \
aug.augment_images([base_img] * 3)
assert np.array_equal(img_observed1, img_expected1)
assert np.array_equal(img_observed2, img_expected2)
assert np.array_equal(img_observed3, img_expected3)
def test_get_parameters(self):
aug = iaa.AddToHueAndSaturation((-20, 20), per_channel=0.5)
params = aug.get_parameters()
assert is_parameter_instance(params[0], iap.DiscreteUniform)
assert params[0].a.value == -20
assert params[0].b.value == 20
assert params[1] is None
assert params[2] is None
assert is_parameter_instance(params[3], iap.Binomial)
assert np.isclose(params[3].p.value, 0.5)
def test_get_parameters_value_hue_and_value_saturation(self):
aug = iaa.AddToHueAndSaturation(value_hue=(-20, 20),
value_saturation=5)
params = aug.get_parameters()
assert params[0] is None
assert is_parameter_instance(params[1], iap.DiscreteUniform)
assert params[1].a.value == -20
assert params[1].b.value == 20
assert is_parameter_instance(params[2], iap.Deterministic)
assert params[2].value == 5
assert is_parameter_instance(params[3], iap.Deterministic)
assert params[3].value == 0
def test_pickleable(self):
aug = iaa.AddToHueAndSaturation(value_hue=(-50, 50),
value_saturation=(-50, 50),
seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(50, 50, 3))
class TestAddToHue(unittest.TestCase):
def test_returns_correct_class(self):
aug = iaa.AddToHue((-20, 20))
assert isinstance(aug, iaa.AddToHueAndSaturation)
assert is_parameter_instance(aug.value_hue, iap.DiscreteUniform)
assert aug.value_hue.a.value == -20
assert aug.value_hue.b.value == 20
def test_pickleable(self):
aug = iaa.AddToHue((-50, 50), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(50, 50, 3))
class TestAddToSaturation(unittest.TestCase):
def test_returns_correct_class(self):
aug = iaa.AddToSaturation((-20, 20))
assert isinstance(aug, iaa.AddToHueAndSaturation)
assert is_parameter_instance(aug.value_saturation, iap.DiscreteUniform)
assert aug.value_saturation.a.value == -20
assert aug.value_saturation.b.value == 20
def test_pickleable(self):
aug = iaa.AddToSaturation((-50, 50), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(50, 50, 3))
class TestGrayscale(unittest.TestCase):
def setUp(self):
reseed()
@property
def base_img(self):
base_img = np.zeros((4, 4, 3), dtype=np.uint8)
base_img[..., 0] += 10
base_img[..., 1] += 20
base_img[..., 2] += 30
return base_img
@classmethod
def _compute_luminosity(cls, r, g, b):
return 0.21 * r + 0.72 * g + 0.07 * b
def test_alpha_is_0(self):
aug = iaa.Grayscale(0.0)
observed = aug.augment_image(self.base_img)
expected = np.copy(self.base_img)
assert np.allclose(observed, expected)
def test_alpha_is_1(self):
aug = iaa.Grayscale(1.0)
observed = aug.augment_image(self.base_img)
luminosity = self._compute_luminosity(10, 20, 30)
expected = np.zeros_like(self.base_img) + luminosity
assert np.allclose(observed, expected.astype(np.uint8))
def test_alpha_is_050(self):
aug = iaa.Grayscale(0.5)
observed = aug.augment_image(self.base_img)
luminosity = self._compute_luminosity(10, 20, 30)
expected = 0.5 * self.base_img + 0.5 * luminosity
assert np.allclose(observed, expected.astype(np.uint8))
def test_alpha_is_tuple(self):
aug = iaa.Grayscale((0.0, 1.0))
base_img = np.uint8([255, 0, 0]).reshape((1, 1, 3))
base_img_float = base_img.astype(np.float64) / 255.0
base_img_gray = iaa.Grayscale(1.0)\
.augment_image(base_img)\
.astype(np.float64) / 255.0
distance_max = np.linalg.norm(base_img_gray.flatten()
- base_img_float.flatten())
nb_iterations = 1000
distances = []
for _ in sm.xrange(nb_iterations):
observed = aug.augment_image(base_img).astype(np.float64) / 255.0
distance = np.linalg.norm(
observed.flatten() - base_img_float.flatten()) / distance_max
distances.append(distance)
assert 0 - 1e-4 < min(distances) < 0.1
assert 0.4 < np.average(distances) < 0.6
assert 0.9 < max(distances) < 1.0 + 1e-4
nb_bins = 5
hist, _ = np.histogram(
distances, bins=nb_bins, range=(0.0, 1.0), density=False)
density_expected = 1.0/nb_bins
density_tolerance = 0.05
for nb_samples in hist:
density = nb_samples / nb_iterations
assert np.isclose(density, density_expected,
rtol=0, atol=density_tolerance)
def test_pickleable(self):
aug = iaa.Grayscale((0.1, 0.9), seed=1)
runtest_pickleable_uint8_img(aug)
class TestChangeColorTemperature(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
aug = iaa.ChangeColorTemperature()
assert is_parameter_instance(aug.kelvin, iap.Uniform)
assert aug.kelvin.a.value == 1000
assert aug.kelvin.b.value == 11000
assert aug.from_colorspace == iaa.CSPACE_RGB
def test___init___kelvin_is_deterministic(self):
aug = iaa.ChangeColorTemperature(1000)
assert aug.kelvin.value == 1000
def test___init___kelvin_is_tuple(self):
aug = iaa.ChangeColorTemperature((2000, 3000))
assert is_parameter_instance(aug.kelvin, iap.Uniform)
assert aug.kelvin.a.value == 2000
assert aug.kelvin.b.value == 3000
def test___init___kelvin_is_list(self):
aug = iaa.ChangeColorTemperature([1000, 2000, 3000])
assert is_parameter_instance(aug.kelvin, iap.Choice)
assert aug.kelvin.a == [1000, 2000, 3000]
def test___init___kelvin_is_stochastic_param(self):
param = iap.Deterministic(5000)
aug = iaa.ChangeColorTemperature(param)
assert is_parameter_instance(aug.kelvin, iap.Deterministic)
assert aug.kelvin.value == 5000
@mock.patch("imgaug.augmenters.color.change_color_temperatures_")
def test_mocked(self, mock_ccts):
image = np.zeros((1, 1, 3), dtype=np.uint8)
aug = iaa.ChangeColorTemperature((1000, 40000),
from_colorspace=iaa.CSPACE_HLS)
def _side_effect(images, kelvins, from_colorspaces):
return images
mock_ccts.side_effect = _side_effect
_image_aug = aug(images=[image, image])
assert mock_ccts.call_count == 1
assert np.array_equal(mock_ccts.call_args_list[0][0][0],
[image, image])
assert not np.isclose(
mock_ccts.call_args_list[0][0][1][0], # kelvin img 1
mock_ccts.call_args_list[0][0][1][1], # kelvin img 2
)
assert (mock_ccts.call_args_list[0][1]["from_colorspaces"]
== iaa.CSPACE_HLS)
def test_single_image(self):
image = np.full((1, 1, 3), 255, dtype=np.uint8)
aug = iaa.ChangeColorTemperature(1000)
image_aug = aug(image=image)
expected = np.uint8([255, 56, 0]).reshape((1, 1, 3))
assert np.array_equal(image_aug, expected)
def test_get_parameters(self):
aug = iaa.ChangeColorTemperature(1111,
from_colorspace=iaa.CSPACE_HLS)
params = aug.get_parameters()
assert params[0].value == 1111
assert params[1] == iaa.CSPACE_HLS
def test_pickleable(self):
aug = iaa.ChangeColorTemperature(seed=1)
runtest_pickleable_uint8_img(aug, iterations=10)
# Note that TestUniformColorQuantization inherits from this class,
# which is why it contains the overwriteable @property functions
class TestKMeansColorQuantization(unittest.TestCase):
def setUp(self):
reseed()
@property
def augmenter(self):
return iaa.KMeansColorQuantization
@property
def quantization_func_name(self):
return "imgaug.augmenters.color.quantize_kmeans"
def test___init___defaults(self):
aug = self.augmenter()
assert is_parameter_instance(aug.n_colors, iap.DiscreteUniform)
assert aug.n_colors.a.value == 2
assert aug.n_colors.b.value == 16
assert aug.from_colorspace == iaa.CSPACE_RGB
assert isinstance(aug.to_colorspace, list)
assert aug.to_colorspace == [iaa.CSPACE_RGB,
iaa.CSPACE_Lab]
assert aug.max_size == 128
assert aug.interpolation == "linear"
def test___init___custom_parameters(self):
aug = self.augmenter(
n_colors=(5, 8),
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=[iaa.CSPACE_HSV, iaa.CSPACE_Lab],
max_size=None,
interpolation="cubic"
)
assert is_parameter_instance(aug.n_colors, iap.DiscreteUniform)
assert aug.n_colors.a.value == 5
assert aug.n_colors.b.value == 8
assert aug.from_colorspace == iaa.CSPACE_BGR
assert isinstance(aug.to_colorspace, list)
assert aug.to_colorspace == [iaa.CSPACE_HSV,
iaa.CSPACE_Lab]
assert aug.max_size is None
assert aug.interpolation == "cubic"
def test_n_colors_deterministic(self):
aug = self.augmenter(n_colors=5)
mock_quantize_func = mock.MagicMock(
return_value=np.zeros((4, 4, 3), dtype=np.uint8))
fname = self.quantization_func_name
with mock.patch(fname, mock_quantize_func):
_ = aug.augment_image(np.zeros((4, 4, 3), dtype=np.uint8))
# call 0, args, argument 1
assert mock_quantize_func.call_args_list[0][0][1] == 5
def test_n_colors_tuple(self):
aug = self.augmenter(n_colors=(2, 1000))
mock_quantize_func = mock.MagicMock(
return_value=np.zeros((4, 4, 3), dtype=np.uint8))
n_images = 10
fname = self.quantization_func_name
with mock.patch(fname, mock_quantize_func):
image = np.zeros((4, 4, 3), dtype=np.uint8)
_ = aug.augment_images([image] * n_images)
# call i, args, argument 1
n_colors = [mock_quantize_func.call_args_list[i][0][1]
for i in sm.xrange(n_images)]
assert all([2 <= n_colors_i <= 1000 for n_colors_i in n_colors])
assert len(set(n_colors)) > 1
def test_to_colorspace(self):
image = np.arange(3*3*3, dtype=np.uint8).reshape((3, 3, 3))
aug = self.augmenter(to_colorspace="HSV")
mock_quantize_func = mock.MagicMock(
return_value=np.zeros((4, 4, 3), dtype=np.uint8))
fname = self.quantization_func_name
with mock.patch(fname, mock_quantize_func):
_ = aug.augment_image(image)
# call 0, kwargs, argument 'to_colorspace'
expected = cv2.cvtColor(image, cv2.COLOR_RGB2HSV)
assert np.array_equal(mock_quantize_func.call_args_list[0][0][0],
expected)
def test_to_colorspace_is_none(self):
image = np.arange(3*3*3, dtype=np.uint8).reshape((3, 3, 3))
aug = self.augmenter(to_colorspace=None)
mock_quantize_func = mock.MagicMock(
return_value=np.zeros((4, 4, 3), dtype=np.uint8))
fname = self.quantization_func_name
with mock.patch(fname, mock_quantize_func):
_ = aug.augment_image(image)
# call 0, kwargs, argument 'to_colorspace'
assert np.array_equal(mock_quantize_func.call_args_list[0][0][0],
image)
def test_from_colorspace(self):
def _noop(img):
return img
aug = self.augmenter(from_colorspace="BGR")
mock_change_colorspace = mock.MagicMock()
mock_change_colorspace.return_value = mock_change_colorspace
mock_change_colorspace.augment_image.side_effect = _noop
mock_change_colorspace._draw_samples.return_value = (None, ["foo"])
fname = "imgaug.augmenters.color.ChangeColorspace"
with mock.patch(fname, mock_change_colorspace):
_ = aug.augment_image(np.zeros((4, 4, 3), dtype=np.uint8))
# call 0, kwargs, argument 'from_colorspace'
assert (
mock_change_colorspace.call_args_list[0][1]["from_colorspace"]
== "BGR")
# call 1, kwargs, argument 'from_colorspace' (inverse transform)
assert (
mock_change_colorspace.call_args_list[1][1]["from_colorspace"]
== "foo")
def test_max_size_is_none(self):
image = np.zeros((1000, 4, 3), dtype=np.uint8)
aug = iaa.KMeansColorQuantization(max_size=None)
mock_imresize = mock.MagicMock(return_value=image)
fname = "imgaug.imresize_single_image"
with mock.patch(fname, mock_imresize):
image_aug = aug.augment_image(image)
assert image_aug.shape == image.shape
assert mock_imresize.call_count == 0
def test_max_size_is_int_and_resize_necessary(self):
image = np.zeros((200, 100, 3), dtype=np.uint8)
aug = self.augmenter(max_size=100)
class _ImresizeSideEffect(object):
def __init__(self):
self.nth_call = 0
def __call__(self, *_args, **_kwargs):
if self.nth_call == 0:
self.nth_call += 1
return np.zeros((100, 50, 3), dtype=np.uint8)
else:
return np.zeros((200, 100, 3), dtype=np.uint8)
mock_imresize = mock.Mock()
mock_imresize.side_effect = _ImresizeSideEffect()
fname = "imgaug.imresize_single_image"
with mock.patch(fname, mock_imresize):
_ = aug.augment_image(image)
# call 0, args, argument 1 (size)
# call 1, args, argument 1 (size)
assert mock_imresize.call_count == 2
assert mock_imresize.call_args_list[0][0][1] == (100, 50)
assert mock_imresize.call_args_list[1][0][1] == image.shape[0:2]
def test_max_size_is_int_and_resize_not_necessary(self):
image = np.zeros((99, 4, 3), dtype=np.uint8)
aug = self.augmenter(max_size=100)
mock_imresize = mock.MagicMock(return_value=image)
fname = "imgaug.imresize_single_image"
with mock.patch(fname, mock_imresize):
image_aug = aug.augment_image(image)
assert image_aug.shape == image.shape
assert mock_imresize.call_count == 0
def test_interpolation(self):
image = np.zeros((200, 100, 3), dtype=np.uint8)
aug = self.augmenter(max_size=100, interpolation="cubic")
class _ImresizeSideEffect(object):
def __init__(self):
self.nth_call = 0
def __call__(self, *_args, **_kwargs):
if self.nth_call == 0:
self.nth_call += 1
return np.zeros((100, 50, 3), dtype=np.uint8)
else:
return np.zeros((200, 100, 3), dtype=np.uint8)
mock_imresize = mock.Mock()
mock_imresize.side_effect = _ImresizeSideEffect()
fname = "imgaug.imresize_single_image"
with mock.patch(fname, mock_imresize):
_ = aug.augment_image(image)
assert mock_imresize.call_count == 2
# downscaling
# call 0, args, argument 1 (sizes)
# call 0, kwargs, argument "interpolation"
assert mock_imresize.call_args_list[0][0][1] == (100, 50)
assert mock_imresize.call_args_list[0][1]["interpolation"] == "cubic"
# upscaling
# call 1, args, argument 1 (sizes)
# call 1, kwargs, argument "interpolation"
assert mock_imresize.call_args_list[1][0][1] == image.shape[0:2]
assert mock_imresize.call_args_list[1][1]["interpolation"] == "cubic"
def test_images_with_1_channel_integrationtest(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[0, 0, 255, 255],
[0, 0, 255, 255],
])
aug = self.augmenter(
n_colors=2,
from_colorspace="RGB",
to_colorspace="RGB",
max_size=None)
observed = aug(image=image)
assert np.array_equal(observed, expected)
def test_images_with_3_channels_integrationtest(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[0, 0, 255, 255],
[0, 0, 255, 255],
])
image = np.tile(image[..., np.newaxis], (1, 1, 3))
expected = np.tile(expected[..., np.newaxis], (1, 1, 3))
aug = self.augmenter(
n_colors=2,
from_colorspace="RGB",
to_colorspace="RGB",
max_size=None)
observed = aug(image=image)
assert np.array_equal(observed, expected)
def test_images_with_4_channels_integrationtest(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[0, 0, 255, 255],
[0, 0, 255, 255],
])
image = np.tile(image[..., np.newaxis], (1, 1, 4))
expected = np.tile(expected[..., np.newaxis], (1, 1, 3))
# alpha channel is expected to not be altered by quantization
expected = np.concatenate([expected, image[:, :, 3:4]], axis=-1)
aug = self.augmenter(
n_colors=2,
from_colorspace="RGB",
to_colorspace="RGB",
max_size=None)
observed = aug(image=image)
assert np.array_equal(observed, expected)
def test_get_parameters(self):
aug = self.augmenter(
n_colors=(5, 8),
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=[iaa.CSPACE_HSV, iaa.CSPACE_Lab],
max_size=None,
interpolation="cubic"
)
params = aug.get_parameters()
assert is_parameter_instance(params[0], iap.DiscreteUniform)
assert params[0].a.value == 5
assert params[0].b.value == 8
assert params[1] == iaa.CSPACE_BGR
assert isinstance(params[2], list)
assert params[2] == [iaa.CSPACE_HSV,
iaa.CSPACE_Lab]
assert params[3] is None
assert params[4] == "cubic"
def test_pickleable(self):
aug = self.augmenter(n_colors=(2, 16), seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(100, 100, 3))
class Test_quantize_colors_kmeans(unittest.TestCase):
@mock.patch("imgaug.imgaug.warn_deprecated")
def test_warns_deprecated(self, mock_warn):
arr = np.arange(1*1*3).astype(np.uint8).reshape((1, 1, 3))
_ = iaa.quantize_colors_kmeans(arr, 2)
assert mock_warn.call_count == 1
@mock.patch("imgaug.augmenters.color.quantize_kmeans")
@mock.patch("imgaug.imgaug.warn_deprecated")
def test_calls_quantize_kmeans(self, mock_warn, mock_qu):
arr = np.arange(1*1*3).astype(np.uint8).reshape((1, 1, 3))
mock_qu.return_value = "foo"
result = iaa.quantize_colors_kmeans(arr, 7)
mock_qu.assert_called_once_with(arr=arr, nb_clusters=7,
nb_max_iter=10, eps=1.0)
assert result == "foo"
assert mock_warn.call_count == 1
class Test_quantize_kmeans(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _test_images_with_n_channels(cls, nb_channels):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[0, 0, 255, 255],
[0, 0, 255, 255],
])
if nb_channels is not None:
image = np.tile(image[..., np.newaxis], (1, 1, nb_channels))
expected = np.tile(expected[..., np.newaxis], (1, 1, nb_channels))
observed = iaa.quantize_kmeans(image, 2)
assert np.array_equal(observed, expected)
def test_images_with_no_channels(self):
self._test_images_with_n_channels(None)
def test_images_with_1_channel(self):
self._test_images_with_n_channels(1)
def test_images_with_3_channels(self):
self._test_images_with_n_channels(3)
def test_more_colors_than_pixels(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.copy(image)
observed = iaa.quantize_kmeans(image, 100)
assert np.array_equal(observed, expected)
def test_failure_if_n_colors_less_than_2(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
got_exception = False
try:
_ = iaa.quantize_kmeans(image, 1)
except AssertionError as exc:
assert "[2..256]" in str(exc)
got_exception = True
assert got_exception
def test_quantization_is_deterministic(self):
rs = iarandom.RNG(1)
image = rs.integers(0, 255, (100, 100, 3)).astype(np.uint8)
# simulate multiple calls, each one of them should produce the
# same quantization
images_quantized = []
for _ in sm.xrange(20):
images_quantized.append(iaa.quantize_kmeans(image, 20))
for image_quantized in images_quantized[1:]:
assert np.array_equal(image_quantized, images_quantized[0])
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.quantize_kmeans(image, 2)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.quantize_kmeans(image, 2)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
class TestUniformColorQuantization(TestKMeansColorQuantization):
def setUp(self):
reseed()
@property
def augmenter(self):
return iaa.UniformColorQuantization
@property
def quantization_func_name(self):
return "imgaug.augmenters.color.quantize_uniform_"
def test___init___defaults(self):
aug = self.augmenter()
assert is_parameter_instance(aug.n_colors, iap.DiscreteUniform)
assert aug.n_colors.a.value == 2
assert aug.n_colors.b.value == 16
assert aug.from_colorspace == iaa.CSPACE_RGB
assert aug.to_colorspace is None
assert aug.max_size is None
assert aug.interpolation == "linear"
def test___init___custom_parameters(self):
aug = self.augmenter(
n_colors=(5, 8),
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=[iaa.CSPACE_HSV, iaa.CSPACE_Lab],
max_size=128,
interpolation="cubic"
)
assert is_parameter_instance(aug.n_colors, iap.DiscreteUniform)
assert aug.n_colors.a.value == 5
assert aug.n_colors.b.value == 8
assert aug.from_colorspace == iaa.CSPACE_BGR
assert isinstance(aug.to_colorspace, list)
assert aug.to_colorspace == [iaa.CSPACE_HSV,
iaa.CSPACE_Lab]
assert aug.max_size == 128
assert aug.interpolation == "cubic"
def test_images_with_1_channel_integrationtest(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[64, 64, 192, 192],
[64, 64, 192, 192],
])
aug = self.augmenter(
n_colors=2,
from_colorspace="RGB",
to_colorspace="RGB",
max_size=None)
observed = aug(image=image)
assert np.array_equal(observed, expected)
def test_images_with_3_channels_integrationtest(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[64, 64, 192, 192],
[64, 64, 192, 192],
])
image = np.tile(image[..., np.newaxis], (1, 1, 3))
expected = np.tile(expected[..., np.newaxis], (1, 1, 3))
aug = self.augmenter(
n_colors=2,
from_colorspace="RGB",
to_colorspace="RGB",
max_size=None)
observed = aug(image=image)
assert np.array_equal(observed, expected)
def test_images_with_4_channels_integrationtest(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[64, 64, 192, 192],
[64, 64, 192, 192],
])
image = np.tile(image[..., np.newaxis], (1, 1, 4))
expected = np.tile(expected[..., np.newaxis], (1, 1, 3))
# alpha channel is expected to not be altered by quantization
expected = np.concatenate([expected, image[:, :, 3:4]], axis=-1)
aug = self.augmenter(
n_colors=2,
from_colorspace="RGB",
to_colorspace="RGB",
max_size=None)
observed = aug(image=image)
assert np.array_equal(observed, expected)
def test_from_colorspace(self):
def _noop(img):
return img
# Actual to_colorspace doesn't matter here as it is overwritten
# via return_value. Important is just to set it to a non-None value
# so that a colorspace conversion actually happens.
aug = self.augmenter(from_colorspace="BGR",
to_colorspace="Lab")
mock_change_colorspace = mock.MagicMock()
mock_change_colorspace.return_value = mock_change_colorspace
mock_change_colorspace.augment_image.side_effect = _noop
mock_change_colorspace._draw_samples.return_value = (None, ["foo"])
fname = "imgaug.augmenters.color.ChangeColorspace"
with mock.patch(fname, mock_change_colorspace):
_ = aug.augment_image(np.zeros((4, 4, 3), dtype=np.uint8))
# call 0, kwargs, argument 'from_colorspace'
assert (
mock_change_colorspace.call_args_list[0][1]["from_colorspace"]
== "BGR")
# call 1, kwargs, argument 'from_colorspace' (inverse transform)
assert (
mock_change_colorspace.call_args_list[1][1]["from_colorspace"]
== "foo")
def test_pickleable(self):
aug = self.augmenter(n_colors=(2, 32), seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(100, 100, 3))
# Not that many tests here as it is basically identical to e.g.
# UniformColorQuantization
class TestUniformColorQuantizationToNBits(unittest.TestCase):
def setUp(self):
reseed()
@property
def augmenter(self):
return iaa.UniformColorQuantizationToNBits
@property
def quantization_func_name(self):
return "imgaug.augmenters.color.quantize_uniform_to_n_bits"
def test___init___defaults(self):
aug = self.augmenter()
assert is_parameter_instance(aug.counts, iap.DiscreteUniform)
assert aug.counts.a.value == 1
assert aug.counts.b.value == 8
assert aug.from_colorspace == iaa.CSPACE_RGB
assert aug.to_colorspace is None
assert aug.max_size is None
assert aug.interpolation == "linear"
def test___init___custom_parameters(self):
aug = self.augmenter(
nb_bits=(5, 8),
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=[iaa.CSPACE_HSV, iaa.CSPACE_Lab],
max_size=128,
interpolation="cubic"
)
assert is_parameter_instance(aug.counts, iap.DiscreteUniform)
assert aug.counts.a.value == 5
assert aug.counts.b.value == 8
assert aug.from_colorspace == iaa.CSPACE_BGR
assert isinstance(aug.to_colorspace, list)
assert aug.to_colorspace == [iaa.CSPACE_HSV,
iaa.CSPACE_Lab]
assert aug.max_size == 128
assert aug.interpolation == "cubic"
def test_images_with_1_channel_integrationtest(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[0, 0, 128, 128],
[0, 0, 128, 128],
])
aug = self.augmenter(
nb_bits=1,
from_colorspace="RGB",
to_colorspace="RGB",
max_size=None)
observed = aug(image=image)
assert np.array_equal(observed, expected)
def test_images_with_3_channels_integrationtest(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[0, 0, 128, 128],
[0, 0, 128, 128],
])
image = np.tile(image[..., np.newaxis], (1, 1, 3))
expected = np.tile(expected[..., np.newaxis], (1, 1, 3))
aug = self.augmenter(
nb_bits=1,
from_colorspace="RGB",
to_colorspace="RGB",
max_size=None)
observed = aug(image=image)
assert np.array_equal(observed, expected)
def test_pickleable(self):
aug = iaa.UniformColorQuantizationToNBits(seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(100, 100, 3))
class TestPosterize(TestUniformColorQuantizationToNBits):
@property
def augmenter(self):
return iaa.Posterize
class Test_quantize_colors_uniform(unittest.TestCase):
@mock.patch("imgaug.imgaug.warn_deprecated")
def test_warns_deprecated(self, mock_warn):
arr = np.arange(1*1*3).astype(np.uint8).reshape((1, 1, 3))
_ = iaa.quantize_colors_uniform(arr, 2)
assert mock_warn.call_count == 1
@mock.patch("imgaug.augmenters.color.quantize_uniform")
@mock.patch("imgaug.imgaug.warn_deprecated")
def test_calls_quantize_uniform(self, mock_warn, mock_qu):
arr = np.arange(1*1*3).astype(np.uint8).reshape((1, 1, 3))
mock_qu.return_value = "foo"
result = iaa.quantize_colors_uniform(arr, 7)
mock_qu.assert_called_once_with(arr=arr, nb_bins=7)
assert result == "foo"
assert mock_warn.call_count == 1
class Test_quantize_uniform(unittest.TestCase):
@mock.patch("imgaug.augmenters.color.quantize_uniform_")
def test_calls_inplace_function(self, mock_qu):
image = np.zeros((1, 1, 3), dtype=np.uint8)
mock_qu.return_value = "foo"
result = iaa.quantize_uniform(image, 3)
# image provided to in-place func must be copy with same content
assert mock_qu.call_args_list[0][0][0] is not image
assert np.array_equal(mock_qu.call_args_list[0][0][0], image)
assert mock_qu.call_args_list[0][1]["nb_bins"] == 3
assert result == "foo"
class Test_quantize_uniform_(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _test_images_with_n_channels_2_colors(cls, nb_channels):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[64, 64, 192, 192],
[64, 64, 192, 192],
])
if nb_channels is not None:
image = np.tile(image[..., np.newaxis], (1, 1, nb_channels))
expected = np.tile(expected[..., np.newaxis], (1, 1, nb_channels))
observed = iaa.quantize_uniform_(np.copy(image), 2)
assert np.array_equal(observed, expected)
@classmethod
def _test_images_with_n_channels_4_colors(cls, nb_channels):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
[127, 128, 220, 220]
])
q = 256/4
c1 = np.floor(0/q) * q + q/2 # 32
c2 = np.floor(64/q) * q + q/2 # 96
c3 = np.floor(128/q) * q + q/2 # 160
c4 = np.floor(192/q) * q + q/2 # 224
assert int(c1) == 32
assert int(c2) == 96
assert int(c3) == 160
assert int(c4) == 224
expected = np.uint8([
[c1, c1, c4, c4],
[c1, c1, c4, c4],
[c2, c3, c4, c4]
])
if nb_channels is not None:
image = np.tile(image[..., np.newaxis], (1, 1, nb_channels))
expected = np.tile(expected[..., np.newaxis], (1, 1, nb_channels))
observed = iaa.quantize_uniform_(np.copy(image), 4)
assert np.array_equal(observed, expected)
def test_images_with_no_channels_2_colors(self):
self._test_images_with_n_channels_2_colors(None)
def test_images_with_1_channel_2_colors(self):
self._test_images_with_n_channels_2_colors(1)
def test_images_with_3_channels_2_colors(self):
self._test_images_with_n_channels_2_colors(3)
def test_images_with_no_channels_4_colors(self):
self._test_images_with_n_channels_4_colors(None)
def test_images_with_1_channel_4_colors(self):
self._test_images_with_n_channels_4_colors(1)
def test_images_with_3_channels_4_colors(self):
self._test_images_with_n_channels_4_colors(3)
def test_to_bin_centers_is_false(self):
nb_channels = 3
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
[127, 128, 220, 220]
])
c1 = 0
c2 = 64
c3 = 128
c4 = 192
expected = np.uint8([
[c1, c1, c4, c4],
[c1, c1, c4, c4],
[c2, c3, c4, c4]
])
image = np.tile(image[..., np.newaxis], (1, 1, nb_channels))
expected = np.tile(expected[..., np.newaxis], (1, 1, nb_channels))
observed = iaa.quantize_uniform_(np.copy(image),
4,
to_bin_centers=False)
assert np.array_equal(observed, expected)
def test_failure_if_n_colors_less_than_2(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
got_exception = False
try:
_ = iaa.quantize_uniform_(np.copy(image), 1)
except AssertionError as exc:
assert "[2..256]" in str(exc)
got_exception = True
assert got_exception
def test_noncontiguous(self):
image = np.uint8([
[0, 0, 255, 255],
[0, 1, 255, 255],
])
expected = np.uint8([
[192, 192, 64, 64],
[192, 192, 64, 64],
])
image_v = np.fliplr(np.copy(image))
assert image_v.flags["C_CONTIGUOUS"] is False
observed = iaa.quantize_uniform_(image_v, 2)
assert observed.shape == (2, 4)
assert observed.dtype.name == "uint8"
assert np.array_equal(observed, expected)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.quantize_uniform_(np.copy(image), 2)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.quantize_uniform_(np.copy(image), 2)
assert np.any(image_aug > 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
class Test_quantize_uniform_to_n_bits(unittest.TestCase):
@mock.patch("imgaug.augmenters.color.quantize_uniform_to_n_bits_")
def test_calls_inplace_function(self, mock_qu):
image = np.zeros((1, 1, 3), dtype=np.uint8)
mock_qu.return_value = "foo"
result = iaa.quantize_uniform_to_n_bits(image, 3)
# image provided to in-place func must be copy with same content
assert mock_qu.call_args_list[0][0][0] is not image
assert np.array_equal(mock_qu.call_args_list[0][0][0], image)
assert mock_qu.call_args_list[0][1]["nb_bits"] == 3
assert result == "foo"
# not much testing necessary here as the function is a wrapper around
# quantize_uniform()
class Test_quantize_uniform_to_n_bits_(unittest.TestCase):
@mock.patch("imgaug.augmenters.color.quantize_uniform_")
def test_mocked(self, mock_qu):
mock_qu.return_value = "foo"
image = np.zeros((1, 1, 3), dtype=np.uint8)
nb_bits = 3
result = iaa.quantize_uniform_to_n_bits_(np.copy(image), nb_bits)
# image provided to in-place func must be copy with same content
assert mock_qu.call_args_list[0][0][0] is not image
assert np.array_equal(mock_qu.call_args_list[0][0][0], image)
assert mock_qu.call_args_list[0][1]["nb_bins"] == 2**nb_bits
assert mock_qu.call_args_list[0][1]["to_bin_centers"] is False
assert result == "foo"
class Test_posterize(unittest.TestCase):
@mock.patch("imgaug.augmenters.color.quantize_uniform_to_n_bits")
def test_mocked(self, mock_qu):
mock_qu.return_value = "foo"
image = np.zeros((1, 1, 3), dtype=np.uint8)
nb_bits = 3
result = iaa.posterize(image, nb_bits)
mock_qu.assert_called_once_with(image, nb_bits)
assert result == "foo"
================================================
FILE: test/augmenters/test_contrast.py
================================================
from __future__ import print_function, division, absolute_import
import itertools
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import warnings
import numpy as np
import six.moves as sm
import skimage
import skimage.data
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug.augmenters import contrast as contrast_lib
from imgaug.testutils import (ArgCopyingMagicMock, keypoints_equal, reseed,
runtest_pickleable_uint8_img, assertWarns,
is_parameter_instance)
from imgaug.augmentables.batches import _BatchInAugmentation
class TestGammaContrast(unittest.TestCase):
def setUp(self):
reseed()
def test___init___tuple_to_uniform(self):
aug = iaa.GammaContrast((1, 2))
assert is_parameter_instance(aug.params1d[0], iap.Uniform)
assert is_parameter_instance(aug.params1d[0].a, iap.Deterministic)
assert is_parameter_instance(aug.params1d[0].b, iap.Deterministic)
assert aug.params1d[0].a.value == 1
assert aug.params1d[0].b.value == 2
def test___init___list_to_choice(self):
aug = iaa.GammaContrast([1, 2])
assert is_parameter_instance(aug.params1d[0], iap.Choice)
assert np.all([val in aug.params1d[0].a for val in [1, 2]])
def test_images_basic_functionality(self):
img = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
img = np.uint8(img)
img3d = np.tile(img[:, :, np.newaxis], (1, 1, 3))
# check basic functionality with gamma=1 or 2 (deterministic) and
# per_channel on/off (makes
# no difference due to deterministic gamma)
for per_channel in [False, 0, 0.0, True, 1, 1.0]:
for gamma in [1, 2]:
aug = iaa.GammaContrast(
gamma=iap.Deterministic(gamma),
per_channel=per_channel)
img_aug = aug.augment_image(img)
img3d_aug = aug.augment_image(img3d)
assert img_aug.dtype.name == "uint8"
assert img3d_aug.dtype.name == "uint8"
assert np.array_equal(
img_aug,
skimage.exposure.adjust_gamma(img, gamma=gamma))
assert np.array_equal(
img3d_aug,
skimage.exposure.adjust_gamma(img3d, gamma=gamma))
def test_per_channel_is_float(self):
# check that per_channel at 50% prob works
aug = iaa.GammaContrast((0.5, 2.0), per_channel=0.5)
seen = [False, False]
img1000d = np.zeros((1, 1, 1000), dtype=np.uint8) + 128
for _ in sm.xrange(100):
img_aug = aug.augment_image(img1000d)
assert img_aug.dtype.name == "uint8"
nb_values_uq = len(set(img_aug.flatten().tolist()))
if nb_values_uq == 1:
seen[0] = True
else:
seen[1] = True
if np.all(seen):
break
assert np.all(seen)
def test_keypoints_not_changed(self):
aug = iaa.GammaContrast(gamma=2)
kpsoi = ia.KeypointsOnImage([ia.Keypoint(1, 1)], shape=(3, 3, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])
assert keypoints_equal([kpsoi], kpsoi_aug)
def test_heatmaps_not_changed(self):
aug = iaa.GammaContrast(gamma=2)
heatmaps_arr = np.zeros((3, 3, 1), dtype=np.float32) + 0.5
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
assert np.allclose(heatmaps.arr_0to1, heatmaps_aug.arr_0to1)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.GammaContrast(0.5)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.GammaContrast(0.5)
image_aug = aug(image=image)
assert np.any(image_aug != 128)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_other_dtypes_uint_int(self):
try:
high_res_dt = np.float128
dts = [np.uint8, np.uint16, np.uint32, np.uint64,
np.int8, np.int16, np.int32, np.int64]
except AttributeError:
# cannot reliably check uint64 and int64 on systems that dont
# support float128
high_res_dt = np.float64
dts = [np.uint8, np.uint16, np.uint32,
np.int8, np.int16, np.int32]
for dtype in dts:
dtype = np.dtype(dtype)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
exps = [1, 2, 3]
values = [0, 100, int(center_value + 0.1*max_value)]
tolerances = [0, 0, 1e-8 * max_value
if dtype
in [np.uint64, np.int64] else 0]
for exp in exps:
aug = iaa.GammaContrast(exp)
for value, tolerance in zip(values, tolerances):
with self.subTest(dtype=dtype.name, exp=exp,
nb_channels=None):
image = np.full((3, 3), value, dtype=dtype)
expected = (
(
(image.astype(high_res_dt) / max_value)
** exp
) * max_value
).astype(dtype)
image_aug = aug.augment_image(image)
value_aug = int(image_aug[0, 0])
value_expected = int(expected[0, 0])
diff = abs(value_aug - value_expected)
assert image_aug.dtype.name == dtype.name
assert len(np.unique(image_aug)) == 1
assert diff <= tolerance
# test other channel numbers
for nb_channels in [1, 2, 3, 4, 5, 7, 11]:
with self.subTest(dtype=dtype.name, exp=exp,
nb_channels=nb_channels):
image = np.full((3, 3), value, dtype=dtype)
image = np.tile(image[..., np.newaxis],
(1, 1, nb_channels))
for c in sm.xrange(nb_channels):
image[..., c] += c
expected = (
(
(image.astype(high_res_dt) / max_value)
** exp
) * max_value
).astype(dtype)
image_aug = aug.augment_image(image)
assert image_aug.shape == (3, 3, nb_channels)
assert image_aug.dtype.name == dtype.name
# can be less than nb_channels when multiple input
# values map to the same output value
# mapping distribution can behave exponential with
# slow start and fast growth at the end
assert len(np.unique(image_aug)) <= nb_channels
for c in sm.xrange(nb_channels):
value_aug = int(image_aug[0, 0, c])
value_expected = int(expected[0, 0, c])
diff = abs(value_aug - value_expected)
assert diff <= tolerance
def test_other_dtypes_float(self):
dts = [np.float16, np.float32, np.float64]
for dtype in dts:
dtype = np.dtype(dtype)
def _allclose(a, b):
atol = 1e-3 if dtype == np.float16 else 1e-8
return np.allclose(a, b, atol=atol, rtol=0)
exps = [1, 2]
isize = np.dtype(dtype).itemsize
values = [0, 1.0, 50.0, 100 ** (isize - 1)]
for exp in exps:
aug = iaa.GammaContrast(exp)
for value in values:
with self.subTest(dtype=dtype.name, exp=exp,
nb_channels=None):
image = np.full((3, 3), value, dtype=dtype)
expected = (
image.astype(np.float64)
** exp
).astype(dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype == np.dtype(dtype)
assert _allclose(image_aug, expected)
# test other channel numbers
for nb_channels in [1, 2, 3, 4, 5, 7, 11]:
with self.subTest(dtype=dtype.name, exp=exp,
nb_channels=nb_channels):
image = np.full((3, 3), value, dtype=dtype)
image = np.tile(image[..., np.newaxis],
(1, 1, nb_channels))
for c in sm.xrange(nb_channels):
image[..., c] += float(c)
expected = (
image.astype(np.float64)
** exp
).astype(dtype)
image_aug = aug.augment_image(image)
assert image_aug.shape == (3, 3, nb_channels)
assert image_aug.dtype.name == dtype.name
for c in sm.xrange(nb_channels):
value_aug = image_aug[0, 0, c]
value_expected = expected[0, 0, c]
assert _allclose(value_aug, value_expected)
def test_pickleable(self):
aug = iaa.GammaContrast((0.5, 2.0), seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
class TestSigmoidContrast(unittest.TestCase):
def setUp(self):
reseed()
def test___init___tuple_to_uniform(self):
# check that tuple to uniform works
# note that gain and cutoff are saved in inverted order in
# _ContrastFuncWrapper to match the order of skimage's function
aug = iaa.SigmoidContrast(gain=(1, 2), cutoff=(0.25, 0.75))
assert is_parameter_instance(aug.params1d[0], iap.Uniform)
assert is_parameter_instance(aug.params1d[0].a, iap.Deterministic)
assert is_parameter_instance(aug.params1d[0].b, iap.Deterministic)
assert aug.params1d[0].a.value == 1
assert aug.params1d[0].b.value == 2
assert is_parameter_instance(aug.params1d[1], iap.Uniform)
assert is_parameter_instance(aug.params1d[1].a, iap.Deterministic)
assert is_parameter_instance(aug.params1d[1].b, iap.Deterministic)
assert np.allclose(aug.params1d[1].a.value, 0.25)
assert np.allclose(aug.params1d[1].b.value, 0.75)
def test___init___list_to_choice(self):
# check that list to choice works
# note that gain and cutoff are saved in inverted order in
# _ContrastFuncWrapper to match the order of skimage's function
aug = iaa.SigmoidContrast(gain=[1, 2], cutoff=[0.25, 0.75])
assert is_parameter_instance(aug.params1d[0], iap.Choice)
assert np.all([val in aug.params1d[0].a for val in [1, 2]])
assert is_parameter_instance(aug.params1d[1], iap.Choice)
assert np.all([
np.allclose(val, val_choice)
for val, val_choice
in zip([0.25, 0.75], aug.params1d[1].a)])
def test_images_basic_functionality(self):
img = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
img = np.uint8(img)
img3d = np.tile(img[:, :, np.newaxis], (1, 1, 3))
# check basic functionality with per_chanenl on/off (makes no
# difference due to deterministic parameters)
for per_channel in [False, 0, 0.0, True, 1, 1.0]:
for gain, cutoff in itertools.product([5, 10], [0.25, 0.75]):
with self.subTest(gain=gain, cutoff=cutoff,
per_channel=per_channel):
aug = iaa.SigmoidContrast(
gain=iap.Deterministic(gain),
cutoff=iap.Deterministic(cutoff),
per_channel=per_channel)
img_aug = aug.augment_image(img)
img3d_aug = aug.augment_image(img3d)
assert img_aug.dtype.name == "uint8"
assert img3d_aug.dtype.name == "uint8"
assert np.array_equal(
img_aug,
skimage.exposure.adjust_sigmoid(
img, gain=gain, cutoff=cutoff))
assert np.array_equal(
img3d_aug,
skimage.exposure.adjust_sigmoid(
img3d, gain=gain, cutoff=cutoff))
def test_per_channel_is_float(self):
# check that per_channel at 50% prob works
aug = iaa.SigmoidContrast(gain=(1, 10),
cutoff=(0.25, 0.75),
per_channel=0.5)
seen = [False, False]
img1000d = np.zeros((1, 1, 1000), dtype=np.uint8) + 128
for _ in sm.xrange(100):
img_aug = aug.augment_image(img1000d)
assert img_aug.dtype.name == "uint8"
nb_values_uq = len(set(img_aug.flatten().tolist()))
if nb_values_uq == 1:
seen[0] = True
else:
seen[1] = True
if np.all(seen):
break
assert np.all(seen)
def test_keypoints_dont_change(self):
aug = iaa.SigmoidContrast(gain=10, cutoff=0.5)
kpsoi = ia.KeypointsOnImage([ia.Keypoint(1, 1)], shape=(3, 3, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])
assert keypoints_equal([kpsoi], kpsoi_aug)
def test_heatmaps_dont_change(self):
aug = iaa.SigmoidContrast(gain=10, cutoff=0.5)
heatmaps_arr = np.zeros((3, 3, 1), dtype=np.float32) + 0.5
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
assert np.allclose(heatmaps.arr_0to1, heatmaps_aug.arr_0to1)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.SigmoidContrast(gain=10, cutoff=0.5)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.SigmoidContrast(gain=10, cutoff=1.0)
image_aug = aug(image=image)
assert np.any(image_aug != 128)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_other_dtypes_uint_int(self):
try:
high_res_dt = np.float128
dtypes = [np.uint8, np.uint16, np.uint32, np.uint64,
np.int8, np.int16, np.int32, np.int64]
except AttributeError:
# cannot reliably check uint64 and int64 on systems that dont
# support float128
high_res_dt = np.float64
dtypes = [np.uint8, np.uint16, np.uint32,
np.int8, np.int16, np.int32]
for dtype in dtypes:
dtype = np.dtype(dtype)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
gains = [5, 20]
cutoffs = [0.25, 0.75]
values = [0, 100, int(center_value + 0.1 * max_value)]
tmax = 1e-8 * max_value if dtype in [np.uint64, np.int64] else 0
tolerances = [tmax, tmax, tmax]
for gain, cutoff in itertools.product(gains, cutoffs):
with self.subTest(dtype=dtype.name, gain=gain, cutoff=cutoff):
aug = iaa.SigmoidContrast(gain=gain, cutoff=cutoff)
for value, tolerance in zip(values, tolerances):
image = np.full((3, 3), value, dtype=dtype)
# TODO this looks like the equation commented out
# should actually the correct one, but when using
# it we get a difference between expectation and
# skimage ground truth
# 1/(1 + exp(gain*(cutoff - I_ij/max)))
expected = (
1
/ (
1
+ np.exp(
gain
* (
cutoff
- image.astype(high_res_dt)/max_value
)
)
)
)
expected = (expected * max_value).astype(dtype)
# expected = (
# 1/(1 + np.exp(gain * (
# cutoff - (
# image.astype(high_res_dt)-min_value
# )/dynamic_range
# ))))
# expected = (
# min_value + expected * dynamic_range).astype(dtype)
image_aug = aug.augment_image(image)
value_aug = int(image_aug[0, 0])
value_expected = int(expected[0, 0])
diff = abs(value_aug - value_expected)
assert image_aug.dtype.name == dtype.name
assert len(np.unique(image_aug)) == 1
assert diff <= tolerance
def test_other_dtypes_float(self):
dtypes = [np.float16, np.float32, np.float64]
for dtype in dtypes:
dtype = np.dtype(dtype)
def _allclose(a, b):
atol = 1e-3 if dtype == np.float16 else 1e-8
return np.allclose(a, b, atol=atol, rtol=0)
gains = [5, 20]
cutoffs = [0.25, 0.75]
isize = np.dtype(dtype).itemsize
values = [0, 1.0, 50.0, 100 ** (isize - 1)]
for gain, cutoff in itertools.product(gains, cutoffs):
with self.subTest(dtype=dtype, gain=gain, cutoff=cutoff):
aug = iaa.SigmoidContrast(gain=gain, cutoff=cutoff)
for value in values:
image = np.full((3, 3), value, dtype=dtype)
expected = (
1
/ (
1
+ np.exp(
gain
* (
cutoff
- image.astype(np.float64)
)
)
)
).astype(dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype.name
assert _allclose(image_aug, expected)
def test_pickleable(self):
aug = iaa.SigmoidContrast(gain=(1, 2), seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
class TestLogContrast(unittest.TestCase):
def setUp(self):
reseed()
def test_images_basic_functionality(self):
img = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
img = np.uint8(img)
img3d = np.tile(img[:, :, np.newaxis], (1, 1, 3))
# check basic functionality with gain=1 or 2 (deterministic) and
# per_channel on/off (makes no difference due to deterministic gain)
for per_channel in [False, 0, 0.0, True, 1, 1.0]:
for gain in [1, 2]:
with self.subTest(gain=gain, per_channel=per_channel):
aug = iaa.LogContrast(
gain=iap.Deterministic(gain),
per_channel=per_channel)
img_aug = aug.augment_image(img)
img3d_aug = aug.augment_image(img3d)
assert img_aug.dtype.name == "uint8"
assert img3d_aug.dtype.name == "uint8"
assert np.array_equal(
img_aug,
skimage.exposure.adjust_log(img, gain=gain))
assert np.array_equal(
img3d_aug,
skimage.exposure.adjust_log(img3d, gain=gain))
def test___init___tuple_to_uniform(self):
aug = iaa.LogContrast((1, 2))
assert is_parameter_instance(aug.params1d[0], iap.Uniform)
assert is_parameter_instance(aug.params1d[0].a, iap.Deterministic)
assert is_parameter_instance(aug.params1d[0].b, iap.Deterministic)
assert aug.params1d[0].a.value == 1
assert aug.params1d[0].b.value == 2
def test___init___list_to_choice(self):
aug = iaa.LogContrast([1, 2])
assert is_parameter_instance(aug.params1d[0], iap.Choice)
assert np.all([val in aug.params1d[0].a for val in [1, 2]])
def test_per_channel_is_float(self):
# check that per_channel at 50% prob works
aug = iaa.LogContrast((0.5, 2.0), per_channel=0.5)
seen = [False, False]
img1000d = np.zeros((1, 1, 1000), dtype=np.uint8) + 128
for _ in sm.xrange(100):
img_aug = aug.augment_image(img1000d)
assert img_aug.dtype.name == "uint8"
nb_values_uq = len(set(img_aug.flatten().tolist()))
if nb_values_uq == 1:
seen[0] = True
else:
seen[1] = True
if np.all(seen):
break
assert np.all(seen)
def test_keypoints_not_changed(self):
aug = iaa.LogContrast(gain=2)
kpsoi = ia.KeypointsOnImage([ia.Keypoint(1, 1)], shape=(3, 3, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])
assert keypoints_equal([kpsoi], kpsoi_aug)
def test_heatmaps_not_changed(self):
aug = iaa.LogContrast(gain=2)
heatmap_arr = np.zeros((3, 3, 1), dtype=np.float32) + 0.5
heatmaps = ia.HeatmapsOnImage(heatmap_arr, shape=(3, 3, 3))
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
assert np.allclose(heatmaps.arr_0to1, heatmaps_aug.arr_0to1)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.LogContrast(gain=2)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.LogContrast(gain=2)
image_aug = aug(image=image)
assert np.any(image_aug != 128)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_other_dtypes_uint_int(self):
# support before 1.17:
# [np.uint8, np.uint16, np.uint32, np.uint64,
# np.int8, np.int16, np.int32, np.int64]
# support beginning with 1.17:
# [np.uint8, np.uint16,
# np.int8, np.int16]
# uint, int
dtypes = [np.uint8, np.uint16, np.int8, np.int16]
for dtype in dtypes:
dtype = np.dtype(dtype)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
gains = [0.5, 0.75, 1.0, 1.1]
values = [0, 100, int(center_value + 0.1 * max_value)]
tmax = 1e-8 * max_value if dtype in [np.uint64, np.int64] else 0
tolerances = [0, tmax, tmax]
for gain in gains:
aug = iaa.LogContrast(gain)
for value, tolerance in zip(values, tolerances):
with self.subTest(dtype=dtype.name, gain=gain):
image = np.full((3, 3), value, dtype=dtype)
expected = (
gain
* np.log2(
1 + (image.astype(np.float64)/max_value)
)
)
expected = (expected*max_value).astype(dtype)
image_aug = aug.augment_image(image)
value_aug = int(image_aug[0, 0])
value_expected = int(expected[0, 0])
diff = abs(value_aug - value_expected)
assert image_aug.dtype.name == dtype.name
assert len(np.unique(image_aug)) == 1
assert diff <= tolerance
def test_other_dtypes_float(self):
dtypes = [np.float16, np.float32, np.float64]
for dtype in dtypes:
dtype = np.dtype(dtype)
def _allclose(a, b):
# since numpy 1.17 this needs for some reason at least 1e-5 as
# the tolerance, previously 1e-8 worked
atol = 1e-2 if dtype == np.float16 else 1e-5
return np.allclose(a, b, atol=atol, rtol=0)
gains = [0.5, 0.75, 1.0, 1.1]
isize = np.dtype(dtype).itemsize
values = [0, 1.0, 50.0, 100 ** (isize - 1)]
for gain in gains:
aug = iaa.LogContrast(gain)
for value in values:
with self.subTest(dtype=dtype.name, gain=gain):
image = np.full((3, 3), value, dtype=dtype)
expected = (
gain
* np.log2(
1 + image.astype(np.float64)
)
)
expected = expected.astype(dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert _allclose(image_aug, expected)
def test_pickleable(self):
aug = iaa.LogContrast((1, 2), seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
class TestLinearContrast(unittest.TestCase):
def setUp(self):
reseed()
def test_images_basic_functionality(self):
img = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
img = np.uint8(img)
img3d = np.tile(img[:, :, np.newaxis], (1, 1, 3))
# check basic functionality with alpha=1 or 2 (deterministic) and
# per_channel on/off (makes no difference due to deterministic alpha)
for per_channel in [False, 0, 0.0, True, 1, 1.0]:
for alpha in [1, 2]:
with self.subTest(alpha=alpha, per_channel=per_channel):
aug = iaa.LinearContrast(
alpha=iap.Deterministic(alpha),
per_channel=per_channel)
img_aug = aug.augment_image(img)
img3d_aug = aug.augment_image(img3d)
assert img_aug.dtype.name == "uint8"
assert img3d_aug.dtype.name == "uint8"
assert np.array_equal(
img_aug,
contrast_lib.adjust_contrast_linear(img, alpha=alpha))
assert np.array_equal(
img3d_aug,
contrast_lib.adjust_contrast_linear(img3d, alpha=alpha))
def test___init___tuple_to_uniform(self):
aug = iaa.LinearContrast((1, 2))
assert is_parameter_instance(aug.params1d[0], iap.Uniform)
assert is_parameter_instance(aug.params1d[0].a, iap.Deterministic)
assert is_parameter_instance(aug.params1d[0].b, iap.Deterministic)
assert aug.params1d[0].a.value == 1
assert aug.params1d[0].b.value == 2
def test___init___list_to_choice(self):
aug = iaa.LinearContrast([1, 2])
assert is_parameter_instance(aug.params1d[0], iap.Choice)
assert np.all([val in aug.params1d[0].a for val in [1, 2]])
def test_float_as_per_channel(self):
# check that per_channel at 50% prob works
aug = iaa.LinearContrast((0.5, 2.0), per_channel=0.5)
seen = [False, False]
# must not use just value 128 here, otherwise nothing will change as
# all values would have distance 0 to 128
img1000d = np.zeros((1, 1, 1000), dtype=np.uint8) + 128 + 20
for _ in sm.xrange(100):
img_aug = aug.augment_image(img1000d)
assert img_aug.dtype.name == "uint8"
nb_values_uq = len(set(img_aug.flatten().tolist()))
if nb_values_uq == 1:
seen[0] = True
else:
seen[1] = True
if np.all(seen):
break
assert np.all(seen)
def test_keypoints_not_changed(self):
aug = iaa.LinearContrast(alpha=2)
kpsoi = ia.KeypointsOnImage([ia.Keypoint(1, 1)], shape=(3, 3, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])
assert keypoints_equal([kpsoi], kpsoi_aug)
def test_heatmaps_not_changed(self):
aug = iaa.LinearContrast(alpha=2)
heatmaps_arr = np.zeros((3, 3, 1), dtype=np.float32) + 0.5
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
assert np.allclose(heatmaps.arr_0to1, heatmaps_aug.arr_0to1)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 129, dtype=np.uint8)
aug = iaa.LinearContrast(alpha=2)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 129, dtype=np.uint8)
aug = iaa.LinearContrast(alpha=2)
image_aug = aug(image=image)
assert np.any(image_aug != 128)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
# test for other dtypes are in Test_adjust_contrast_linear
def test_pickleable(self):
aug = iaa.LinearContrast((0.5, 2.0), seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
class Test_adjust_contrast_linear(unittest.TestCase):
def setUp(self):
reseed()
def test_other_dtypes(self):
dtypes = [np.uint8, np.uint16, np.uint32,
np.int8, np.int16, np.int32,
np.float16, np.float32, np.float64]
for dtype in dtypes:
dtype = np.dtype(dtype)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
cv = center_value
kind = np.dtype(dtype).kind
if kind in ["u", "i"]:
cv = int(cv)
def _compare(a, b):
if kind in ["u", "i"]:
return np.array_equal(a, b)
else:
assert kind == "f"
atol = 1e-4 if dtype == np.float16 else 1e-8
return np.allclose(a, b, atol=atol, rtol=0)
img = [
[cv-4, cv-3, cv-2],
[cv-1, cv+0, cv+1],
[cv+2, cv+3, cv+4]
]
img = np.array(img, dtype=dtype)
alphas = [0, 1, 2, 4]
if dtype.name not in ["uint8", "int8", "float16"]:
alphas.append(100)
for alpha in alphas:
expected = [
[cv-4*alpha, cv-3*alpha, cv-2*alpha],
[cv-1*alpha, cv+0*alpha, cv+1*alpha],
[cv+2*alpha, cv+3*alpha, cv+4*alpha]
]
expected = np.array(expected, dtype=dtype)
observed = contrast_lib.adjust_contrast_linear(
img, alpha=alpha)
assert observed.dtype.name == dtype.name
assert observed.shape == img.shape
assert _compare(observed, expected)
def test_output_values_exceed_uint8_value_range(self):
cv = 127
img = [
[cv-4, cv-3, cv-2],
[cv-1, cv+0, cv+1],
[cv+2, cv+3, cv+4]
]
img = np.array(img, dtype=np.uint8)
observed = contrast_lib.adjust_contrast_linear(img, alpha=255)
expected = [
[0, 0, 0],
[0, cv, 255],
[255, 255, 255]
]
assert np.array_equal(observed, expected)
def test_alpha_exceeds_uint8_value_range(self):
# overflow in alpha for uint8, should not cause issues
cv = 127
img = [
[cv, cv, cv],
[cv, cv, cv],
[cv, cv, cv]
]
img = np.array(img, dtype=np.uint8)
observed = contrast_lib.adjust_contrast_linear(img, alpha=257)
expected = [
[cv, cv, cv],
[cv, cv, cv],
[cv, cv, cv]
]
assert np.array_equal(observed, expected)
class TestAllChannelsCLAHE(unittest.TestCase):
def setUp(self):
reseed()
def test_init(self):
aug = iaa.AllChannelsCLAHE(
clip_limit=10,
tile_grid_size_px=11,
tile_grid_size_px_min=4,
per_channel=True)
assert is_parameter_instance(aug.clip_limit, iap.Deterministic)
assert aug.clip_limit.value == 10
assert is_parameter_instance(aug.tile_grid_size_px[0],
iap.Deterministic)
assert aug.tile_grid_size_px[0].value == 11
assert aug.tile_grid_size_px[1] is None
assert aug.tile_grid_size_px_min == 4
assert is_parameter_instance(aug.per_channel, iap.Deterministic)
assert np.isclose(aug.per_channel.value, 1.0)
aug = iaa.AllChannelsCLAHE(
clip_limit=(10, 20),
tile_grid_size_px=(11, 17),
tile_grid_size_px_min=4,
per_channel=0.5)
assert is_parameter_instance(aug.clip_limit, iap.Uniform)
assert aug.clip_limit.a.value == 10
assert aug.clip_limit.b.value == 20
assert is_parameter_instance(aug.tile_grid_size_px[0],
iap.DiscreteUniform)
assert aug.tile_grid_size_px[0].a.value == 11
assert aug.tile_grid_size_px[0].b.value == 17
assert aug.tile_grid_size_px[1] is None
assert aug.tile_grid_size_px_min == 4
assert is_parameter_instance(aug.per_channel, iap.Binomial)
assert np.isclose(aug.per_channel.p.value, 0.5)
aug = iaa.AllChannelsCLAHE(
clip_limit=[10, 20, 30],
tile_grid_size_px=[11, 17, 21])
assert is_parameter_instance(aug.clip_limit, iap.Choice)
assert aug.clip_limit.a[0] == 10
assert aug.clip_limit.a[1] == 20
assert aug.clip_limit.a[2] == 30
assert is_parameter_instance(aug.tile_grid_size_px[0], iap.Choice)
assert aug.tile_grid_size_px[0].a[0] == 11
assert aug.tile_grid_size_px[0].a[1] == 17
assert aug.tile_grid_size_px[0].a[2] == 21
assert aug.tile_grid_size_px[1] is None
aug = iaa.AllChannelsCLAHE(tile_grid_size_px=((11, 17), [11, 13, 15]))
assert is_parameter_instance(aug.tile_grid_size_px[0], iap.DiscreteUniform)
assert aug.tile_grid_size_px[0].a.value == 11
assert aug.tile_grid_size_px[0].b.value == 17
assert is_parameter_instance(aug.tile_grid_size_px[1], iap.Choice)
assert aug.tile_grid_size_px[1].a[0] == 11
assert aug.tile_grid_size_px[1].a[1] == 13
assert aug.tile_grid_size_px[1].a[2] == 15
def test_basic_functionality(self):
img = [
[99, 100, 101],
[99, 100, 101],
[99, 100, 101]
]
img = np.uint8(img)
img3d = np.tile(img[:, :, np.newaxis], (1, 1, 3))
img3d[..., 1] += 1
img3d[..., 2] += 2
aug = iaa.AllChannelsCLAHE(clip_limit=20, tile_grid_size_px=17)
mock_clahe = ArgCopyingMagicMock()
mock_clahe.apply.return_value = img
# image with single channel
with mock.patch('cv2.createCLAHE') as mock_createCLAHE:
mock_createCLAHE.return_value = mock_clahe
_ = aug.augment_image(img)
mock_createCLAHE.assert_called_once_with(
clipLimit=20, tileGridSize=(17, 17))
assert np.array_equal(mock_clahe.apply.call_args_list[0][0][0], img)
def test_basic_functionality_3d(self):
# image with three channels
img = [
[99, 100, 101],
[99, 100, 101],
[99, 100, 101]
]
img = np.uint8(img)
img3d = np.tile(img[:, :, np.newaxis], (1, 1, 3))
img3d[..., 1] += 1
img3d[..., 2] += 2
aug = iaa.AllChannelsCLAHE(clip_limit=20, tile_grid_size_px=17)
mock_clahe = ArgCopyingMagicMock()
mock_clahe.apply.return_value = img
with mock.patch('cv2.createCLAHE') as mock_createCLAHE:
mock_createCLAHE.return_value = mock_clahe
_ = aug.augment_image(img3d)
clist = mock_clahe.apply.call_args_list
assert np.array_equal(clist[0][0][0], img3d[..., 0])
assert np.array_equal(clist[1][0][0], img3d[..., 1])
assert np.array_equal(clist[2][0][0], img3d[..., 2])
def test_basic_functionality_integrationtest(self):
img = np.zeros((3, 7), dtype=np.uint8)
img[0, 0] = 90
img[0, 1] = 100
img[0, 2] = 110
for per_channel in [False, 0, 0.0, True, 1, 1.0]:
for clip_limit in [4, 6]:
for tile_grid_size_px in [3, 5, 7]:
with self.subTest(per_channel=per_channel,
clip_limit=clip_limit,
tile_grid_size_px=tile_grid_size_px):
aug = iaa.AllChannelsCLAHE(
clip_limit=clip_limit,
tile_grid_size_px=tile_grid_size_px,
per_channel=per_channel)
img_aug = aug.augment_image(img)
assert int(np.max(img_aug)) - int(np.min(img_aug)) > 2
def test_tile_grid_size_px_min(self):
img = np.zeros((1, 1), dtype=np.uint8)
aug = iaa.AllChannelsCLAHE(
clip_limit=20,
tile_grid_size_px=iap.Deterministic(-1),
tile_grid_size_px_min=5)
mock_clahe = mock.Mock()
mock_clahe.apply.return_value = img
mock_createCLAHE = mock.MagicMock(return_value=mock_clahe)
with mock.patch('cv2.createCLAHE', mock_createCLAHE):
_ = aug.augment_image(img)
mock_createCLAHE.assert_called_once_with(
clipLimit=20, tileGridSize=(5, 5))
def test_per_channel_integrationtest(self):
# check that per_channel at 50% prob works
aug = iaa.AllChannelsCLAHE(
clip_limit=(1, 200),
tile_grid_size_px=(3, 8),
per_channel=0.5)
seen = [False, False]
img1000d = np.zeros((3, 7, 1000), dtype=np.uint8)
img1000d[0, 0, :] = 90
img1000d[0, 1, :] = 100
img1000d[0, 2, :] = 110
for _ in sm.xrange(100):
with assertWarns(self, iaa.SuspiciousSingleImageShapeWarning):
img_aug = aug.augment_image(img1000d)
assert img_aug.dtype.name == "uint8"
maxs = np.max(img_aug, axis=(0, 1))
mins = np.min(img_aug, axis=(0, 1))
diffs = maxs.astype(np.int32) - mins.astype(np.int32)
nb_diffs_uq = len(set(diffs.flatten().tolist()))
if nb_diffs_uq == 1:
seen[0] = True
else:
seen[1] = True
if np.all(seen):
break
assert np.all(seen)
def test_unit_sized_kernels(self):
img = np.zeros((1, 1), dtype=np.uint8)
tile_grid_sizes = [0, 0, 0, 1, 1, 1, 3, 3, 3]
tile_grid_min_sizes = [0, 1, 3, 0, 1, 3, 0, 1, 3]
nb_calls_expected = [0, 0, 1, 0, 0, 1, 1, 1, 1]
gen = zip(tile_grid_sizes, tile_grid_min_sizes, nb_calls_expected)
for tile_grid_size_px, tile_grid_size_px_min, nb_calls_exp_i in gen:
with self.subTest(tile_grid_size_px=tile_grid_size_px,
tile_grid_size_px_min=tile_grid_size_px_min,
nb_calls_expected_i=nb_calls_exp_i):
aug = iaa.AllChannelsCLAHE(
clip_limit=20,
tile_grid_size_px=tile_grid_size_px,
tile_grid_size_px_min=tile_grid_size_px_min)
mock_clahe = mock.Mock()
mock_clahe.apply.return_value = img
mock_createCLAHE = mock.MagicMock(return_value=mock_clahe)
with mock.patch('cv2.createCLAHE', mock_createCLAHE):
_ = aug.augment_image(img)
assert mock_createCLAHE.call_count == nb_calls_exp_i
def test_other_dtypes(self):
aug = iaa.AllChannelsCLAHE(clip_limit=0.01, tile_grid_size_px=3)
# np.uint32: TypeError: src data type = 6 is not supported
# np.uint64: cv2.error: OpenCV(3.4.2) (...)/clahe.cpp:351:
# error: (-215:Assertion failed)
# src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3))
# || _src.type() == (((2) & ((1 << 3) - 1))
# + (((1)-1) << 3)) in function 'apply'
# np.int8: cv2.error: OpenCV(3.4.2) (...)/clahe.cpp:351: error:
# (-215:Assertion failed)
# src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3))
# || _src.type() == (((2) & ((1 << 3) - 1))
# + (((1)-1) << 3)) in function 'apply'
# np.int16: cv2.error: OpenCV(3.4.2) (...)/clahe.cpp:351: error:
# (-215:Assertion failed)
# src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3))
# || _src.type() == (((2) & ((1 << 3) - 1))
# + (((1)-1) << 3)) in function 'apply'
# np.int32: cv2.error: OpenCV(3.4.2) (...)/clahe.cpp:351: error:
# (-215:Assertion failed)
# src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3))
# || _src.type() == (((2) & ((1 << 3) - 1))
# + (((1)-1) << 3)) in function 'apply'
# np.int64: cv2.error: OpenCV(3.4.2) (...)/clahe.cpp:351: error:
# (-215:Assertion failed)
# src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3))
# || _src.type() == (((2) & ((1 << 3) - 1))
# + (((1)-1) << 3)) in function 'apply'
# np.float16: TypeError: src data type = 23 is not supported
# np.float32: cv2.error: OpenCV(3.4.2) (...)/clahe.cpp:351: error:
# (-215:Assertion failed)
# src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3))
# || _src.type() == (((2) & ((1 << 3) - 1))
# + (((1)-1) << 3)) in function 'apply'
# np.float64: cv2.error: OpenCV(3.4.2) (...)/clahe.cpp:351: error:
# (-215:Assertion failed)
# src.type() == (((0) & ((1 << 3) - 1)) + (((1)-1) << 3))
# || _src.type() == (((2) & ((1 << 3) - 1))
# + (((1)-1) << 3)) in function 'apply'
# np.float128: TypeError: src data type = 13 is not supported
for dtype in [np.uint8, np.uint16]:
with self.subTest(dtype=np.dtype(dtype).name):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
dynamic_range = max_value - min_value
img = np.zeros((11, 11, 1), dtype=dtype)
img[:, 0, 0] = min_value
img[:, 1, 0] = min_value + 30
img[:, 2, 0] = min_value + 40
img[:, 3, 0] = min_value + 50
img[:, 4, 0] = (
int(center_value)
if np.dtype(dtype).kind != "f"
else center_value)
img[:, 5, 0] = max_value - 50
img[:, 6, 0] = max_value - 40
img[:, 7, 0] = max_value - 30
img[:, 8, 0] = max_value
img_aug = aug.augment_image(img)
assert img_aug.dtype.name == np.dtype(dtype).name
assert (
min_value
<= np.min(img_aug)
<= min_value + 0.2 * dynamic_range)
assert (
max_value - 0.2 * dynamic_range
<= np.max(img_aug)
<= max_value)
# TypeError: src data type = 0 is not supported
"""
with self.subTest("bool"):
dtype = np.dtype(bool)
print(dtype)
img = np.zeros((11, 11, 1), dtype=dtype)
img[:, 0, 0] = 0
img[:, 1, 0] = 0
img[:, 2, 0] = 0
img[:, 3, 0] = 0
img[:, 4, 0] = 0
img[:, 5, 0] = 1
img[:, 6, 0] = 1
img[:, 7, 0] = 1
img[:, 8, 0] = 1
img_aug = aug.augment_image(img)
print(img[..., 0])
print(img_aug[..., 0])
assert img_aug.dtype.name == np.dtype(dtype).name
assert np.min(img_aug) == 0
assert np.max(img_aug) == 1
"""
def test_keypoints_not_changed(self):
aug = iaa.AllChannelsCLAHE()
kpsoi = ia.KeypointsOnImage([ia.Keypoint(1, 1)], shape=(3, 3, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])
assert keypoints_equal([kpsoi], kpsoi_aug)
def test_heatmaps_not_changed(self):
aug = iaa.AllChannelsCLAHE()
heatmaps_arr = np.zeros((3, 3, 1), dtype=np.float32) + 0.5
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
assert np.allclose(heatmaps.arr_0to1, heatmaps_aug.arr_0to1)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 129, dtype=np.uint8)
aug = iaa.AllChannelsCLAHE()
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 129, dtype=np.uint8)
aug = iaa.AllChannelsCLAHE()
image_aug = aug(image=image)
assert np.any(image_aug != 128)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
aug = iaa.AllChannelsCLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
per_channel=True)
params = aug.get_parameters()
assert np.all([
is_parameter_instance(params[i], iap.Deterministic)
for i
in [0, 3]])
assert params[0].value == 1
assert params[1][0].value == 3
assert params[1][1] is None
assert params[2] == 2
assert params[3].value == 1
def test_pickleable(self):
aug = iaa.AllChannelsCLAHE(clip_limit=(30, 50),
tile_grid_size_px=(4, 12),
seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(100, 100, 3))
class TestCLAHE(unittest.TestCase):
def setUp(self):
reseed()
def test_init(self):
clahe = iaa.CLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=iaa.CSPACE_HSV)
assert clahe.all_channel_clahe.clip_limit.value == 1
assert clahe.all_channel_clahe.tile_grid_size_px[0].value == 3
assert clahe.all_channel_clahe.tile_grid_size_px[1] is None
assert clahe.all_channel_clahe.tile_grid_size_px_min == 2
icba = clahe.intensity_channel_based_applier
assert icba.from_colorspace == iaa.CSPACE_BGR
assert icba.to_colorspace == iaa.CSPACE_HSV
@mock.patch("imgaug.augmenters.color.change_colorspace_")
def test_single_image_grayscale(self, mock_cs):
img = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]
]
img = np.uint8(img)
mocked_batch = _BatchInAugmentation(
images=[img[..., np.newaxis] + 2])
def _side_effect(image, _to_colorspace, _from_colorspace):
return image + 1
mock_cs.side_effect = _side_effect
mock_all_channel_clahe = ArgCopyingMagicMock()
mock_all_channel_clahe._augment_batch_.return_value = mocked_batch
clahe = iaa.CLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_Lab)
clahe.all_channel_clahe = mock_all_channel_clahe
img_aug = clahe.augment_image(img)
assert np.array_equal(img_aug, img+2)
assert mock_cs.call_count == 0
assert mock_all_channel_clahe._augment_batch_.call_count == 1
@classmethod
def _test_single_image_3d_rgb_to_x(cls, to_colorspace, channel_idx):
fname_cs = "imgaug.augmenters.color.change_colorspace_"
with mock.patch(fname_cs) as mock_cs:
img = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]
]
img = np.uint8(img)
img3d = np.tile(img[..., np.newaxis], (1, 1, 3))
img3d[..., 1] += 10
img3d[..., 2] += 20
def side_effect_change_colorspace(image, _to_colorspace,
_from_colorspace):
return image + 1
def side_effect_all_channel_clahe(batch_call, _random_state,
_parents, _hooks):
batch_call = batch_call.deepcopy()
batch_call.images = [batch_call.images[0] + 2]
return batch_call
mock_cs.side_effect = side_effect_change_colorspace
mock_all_channel_clahe = ArgCopyingMagicMock()
mock_all_channel_clahe._augment_batch_.side_effect = \
side_effect_all_channel_clahe
clahe = iaa.CLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=to_colorspace)
clahe.all_channel_clahe = mock_all_channel_clahe
img3d_aug = clahe.augment_image(np.copy(img3d))
expected1 = img3d + 1
expected2 = np.copy(expected1)
expected2[..., channel_idx] += 2
expected3 = np.copy(expected2) + 1
assert np.array_equal(img3d_aug, expected3)
assert mock_cs.call_count == 2
assert mock_all_channel_clahe._augment_batch_.call_count == 1
# indices: call 0, args, arg 0
assert np.array_equal(mock_cs.call_args_list[0][0][0], img3d)
# for some unclear reason, call_args_list here seems to contain the
# output instead of the input to side_effect_all_channel_clahe, so
# this assert is deactivated for now
# cargs = mock_all_channel_clahe.call_args_list
# print("mock", cargs[0][0][0][0].shape)
# print("mock", cargs[0][0][0][0][..., 0])
# print("exp ", expected1[..., channel_idx])
# assert np.array_equal(
# cargs[0][0][0][0],
# expected1[..., channel_idx:channel_idx+1]
# )
assert np.array_equal(mock_cs.call_args_list[1][0][0], expected2)
def test_single_image_3d_rgb_to_lab(self):
self._test_single_image_3d_rgb_to_x(iaa.CSPACE_Lab, 0)
def test_single_image_3d_rgb_to_hsv(self):
self._test_single_image_3d_rgb_to_x(iaa.CSPACE_HSV, 2)
def test_single_image_3d_rgb_to_hls(self):
self._test_single_image_3d_rgb_to_x(iaa.CSPACE_HLS, 1)
@mock.patch("imgaug.augmenters.color.change_colorspace_")
def test_single_image_4d_rgb_to_lab(self, mock_cs):
channel_idx = 0
img = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]
]
img = np.uint8(img)
img4d = np.tile(img[..., np.newaxis], (1, 1, 4))
img4d[..., 1] += 10
img4d[..., 2] += 20
img4d[..., 3] += 30
def side_effect_change_colorspace(image, _to_colorspace,
_from_colorspace):
return image + 1
def side_effect_all_channel_clahe(batch_call, _random_state, _parents,
_hooks):
batch_call = batch_call.deepcopy()
batch_call.images = [batch_call.images[0] + 2]
return batch_call
mock_cs.side_effect = side_effect_change_colorspace
mock_all_channel_clahe = ArgCopyingMagicMock()
mock_all_channel_clahe._augment_batch_.side_effect = \
side_effect_all_channel_clahe
clahe = iaa.CLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_Lab)
clahe.all_channel_clahe = mock_all_channel_clahe
img4d_aug = clahe.augment_image(img4d)
expected1 = img4d[..., 0:3] + 1
expected2 = np.copy(expected1)
expected2[..., channel_idx] += 2
expected3 = np.copy(expected2) + 1
expected4 = np.dstack((expected3, img4d[..., 3:4]))
assert np.array_equal(img4d_aug, expected4)
assert mock_cs.call_count == 2
assert mock_all_channel_clahe._augment_batch_.call_count == 1
# indices: call 0, args, arg 0
assert np.array_equal(mock_cs.call_args_list[0][0][0], img4d[..., 0:3])
# for some unclear reason, call_args_list here seems to contain the
# output instead of the input to side_effect_all_channel_clahe, so
# this assert is deactivated for now
# assert np.array_equal(
# mock_all_channel_clahe.call_args_list[0][0][0][0],
# expected1[..., channel_idx:channel_idx+1]
# )
assert np.array_equal(mock_cs.call_args_list[1][0][0], expected2)
@mock.patch("imgaug.augmenters.color.change_colorspace_")
def test_single_image_5d_rgb_to_lab(self, mock_cs):
img = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]
]
img = np.uint8(img)
img5d = np.tile(img[..., np.newaxis], (1, 1, 5))
img5d[..., 1] += 10
img5d[..., 2] += 20
img5d[..., 3] += 30
img5d[..., 4] += 40
def side_effect_change_colorspace(image, _to_colorspace,
_from_colorspace):
return image + 1
def side_effect_all_channel_clahe(batch_call, _random_state, _parents,
_hooks):
batch_call = batch_call.deepcopy()
batch_call.images = [batch_call.images[0] + 2]
return batch_call
mock_cs.side_effect = side_effect_change_colorspace
mock_all_channel_clahe = ArgCopyingMagicMock()
mock_all_channel_clahe._augment_batch_.side_effect = \
side_effect_all_channel_clahe
clahe = iaa.CLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_Lab,
name="ExampleCLAHE")
clahe.all_channel_clahe = mock_all_channel_clahe
# note that self.assertWarningRegex does not exist in python 2.7
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
img5d_aug = clahe.augment_image(img5d)
assert len(caught_warnings) == 1
assert (
"Got image with 5 channels in _IntensityChannelBasedApplier "
"(parents: ExampleCLAHE)"
in str(caught_warnings[-1].message)
)
assert np.array_equal(img5d_aug, img5d + 2)
assert mock_cs.call_count == 0
assert mock_all_channel_clahe._augment_batch_.call_count == 1
# indices: call 0, args, arg 0, image 0 in list of images
assert np.array_equal(
mock_all_channel_clahe
._augment_batch_
.call_args_list[0][0][0]
.images[0],
img5d
)
@classmethod
def _test_many_images_rgb_to_lab_list(cls, with_3d_images):
fname_cs = "imgaug.augmenters.color.change_colorspace_"
with mock.patch(fname_cs) as mock_cs:
img = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]
]
img = np.uint8(img)
n_imgs = 2
n_3d_imgs = 3 if with_3d_images else 0
imgs = []
for i in sm.xrange(n_imgs):
imgs.append(img + i)
for i in sm.xrange(n_3d_imgs):
imgs.append(np.tile(img[..., np.newaxis], (1, 1, 3)) + 2 + i)
def side_effect_change_colorspace(image, _to_colorspace,
_from_colorspace):
return image + 1
def side_effect_all_channel_clahe(batch_call, _random_state,
_parents, _hooks):
batch_call = batch_call.deepcopy()
batch_call.images = [image + 2 for image in batch_call.images]
return batch_call
mock_cs.side_effect = side_effect_change_colorspace
mock_all_channel_clahe = ArgCopyingMagicMock()
mock_all_channel_clahe._augment_batch_.side_effect = \
side_effect_all_channel_clahe
clahe = iaa.CLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_Lab)
clahe.all_channel_clahe = mock_all_channel_clahe
imgs_aug = clahe.augment_images(imgs)
assert isinstance(imgs_aug, list)
assert mock_cs.call_count == (n_3d_imgs*2 if with_3d_images else 0)
assert (
mock_all_channel_clahe
._augment_batch_
.call_count == 1)
# indices: call 0, args, arg 0
assert isinstance(
mock_all_channel_clahe
._augment_batch_
.call_args_list[0][0][0],
_BatchInAugmentation)
assert (
len(mock_all_channel_clahe
._augment_batch_
.call_args_list[0][0][0]
.images)
== 5 if with_3d_images else 2)
# indices: call 0, args, arg 0, image i in list of images
for i in sm.xrange(0, 2):
expected = imgs[i][..., np.newaxis]
assert np.array_equal(
mock_all_channel_clahe
._augment_batch_
.call_args_list[0][0][0]
.images[i],
expected
)
if with_3d_images:
for i in sm.xrange(2, 5):
expected = imgs[i]
if expected.shape[2] == 4:
expected = expected[..., 0:3]
assert np.array_equal(
mock_cs.call_args_list[i-2][0][0],
expected
)
# for some unclear reason, call_args_list here seems to
# contain the output instead of the input to
# side_effect_all_channel_clahe, so this assert is
# deactivated for now
# assert np.array_equal(
# mock_all_channel_clahe.call_args_list[0][0][0][i],
# (expected + 1)[..., 0:1]
# )
exp = (expected + 1)
exp[..., 0:1] += 2
assert np.array_equal(
mock_cs.call_args_list[3+i-2][0][0],
exp
)
def test_many_images_rgb_to_lab_list_without_3d_images(self):
self._test_many_images_rgb_to_lab_list(with_3d_images=False)
def test_many_images_rgb_to_lab_list_with_3d_images(self):
self._test_many_images_rgb_to_lab_list(with_3d_images=True)
@classmethod
def _test_many_images_rgb_to_lab_array(cls, nb_channels, nb_images):
fname_cs = "imgaug.augmenters.color.change_colorspace_"
with mock.patch(fname_cs) as mock_cs:
with_color_conversion = (
True if nb_channels is not None and nb_channels in [3, 4]
else False)
img = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]
]
img = np.uint8(img)
if nb_channels is not None:
img = np.tile(img[..., np.newaxis], (1, 1, nb_channels))
imgs = [img] * nb_images
imgs = np.uint8(imgs)
def side_effect_change_colorspace(image, _to_colorspace,
_from_colorspace):
return image + 1
def side_effect_all_channel_clahe(batch_call, _random_state,
_parents, _hooks):
batch_call = batch_call.deepcopy()
batch_call.images = [image + 2 for image in batch_call.images]
return batch_call
mock_cs.side_effect = side_effect_change_colorspace
mock_all_channel_clahe = ArgCopyingMagicMock()
mock_all_channel_clahe._augment_batch_.side_effect = \
side_effect_all_channel_clahe
clahe = iaa.CLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_Lab)
clahe.all_channel_clahe = mock_all_channel_clahe
imgs_aug = clahe.augment_images(imgs)
assert ia.is_np_array(imgs_aug)
assert mock_cs.call_count == (2*nb_images
if with_color_conversion
else 0)
assert (
mock_all_channel_clahe
._augment_batch_
.call_count
== 1)
# indices: call 0, args, arg 0
assert isinstance(
mock_all_channel_clahe
._augment_batch_
.call_args_list[0][0][0],
_BatchInAugmentation)
assert (
len(
mock_all_channel_clahe
._augment_batch_
.call_args_list[0][0][0]
.images)
== nb_images)
# indices: call 0, args, arg 0, image i in list of images
if not with_color_conversion:
for i in sm.xrange(nb_images):
expected = imgs[i]
if expected.ndim == 2:
expected = expected[..., np.newaxis]
# cant have 4 channels and no color conversion for RGB2Lab
assert np.array_equal(
mock_all_channel_clahe
._augment_batch_
.call_args_list[0][0][0]
.images[i],
expected
)
else:
for i in sm.xrange(nb_images):
expected = imgs[i]
if expected.shape[2] == 4:
expected = expected[..., 0:3]
# cant have color conversion for RGB2Lab and no channel
# axis
assert np.array_equal(
mock_cs.call_args_list[i][0][0],
expected
)
# for some unclear reason, call_args_list here seems to
# contain the output instead of the input to
# side_effect_all_channel_clahe, so this assert is
# deactivated for now
# assert np.array_equal(
# mock_all_channel_clahe.call_args_list[0][0][0][i],
# (expected + 1)[..., 0:1]
# )
exp = (expected + 1)
exp[..., 0:1] += 2
assert np.array_equal(
mock_cs.call_args_list[nb_images+i][0][0],
exp
)
def test_many_images_rgb_to_lab_array(self):
gen = itertools.product([None, 1, 3, 4], [1, 2, 4])
for nb_channels, nb_images in gen:
with self.subTest(nb_channels=nb_channels, nb_images=nb_images):
self._test_many_images_rgb_to_lab_array(
nb_channels=nb_channels,
nb_images=nb_images)
def test_determinism(self):
clahe = iaa.CLAHE(clip_limit=(1, 100),
tile_grid_size_px=(3, 60),
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_Lab)
for nb_channels in [None, 1, 3, 4]:
with self.subTest(nb_channels=nb_channels):
img = np.random.randint(0, 255, (128, 128), dtype=np.uint8)
if nb_channels is not None:
img = np.tile(img[..., np.newaxis], (1, 1, nb_channels))
all_same = True
for _ in sm.xrange(10):
result1 = clahe.augment_image(img)
result2 = clahe.augment_image(img)
same = np.array_equal(result1, result2)
all_same = all_same and same
if not all_same:
break
assert not all_same
clahe_det = clahe.to_deterministic()
all_same = True
for _ in sm.xrange(10):
result1 = clahe_det.augment_image(img)
result2 = clahe_det.augment_image(img)
same = np.array_equal(result1, result2)
all_same = all_same and same
if not all_same:
break
assert all_same
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 129, dtype=np.uint8)
aug = iaa.CLAHE()
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
clahe = iaa.CLAHE(
clip_limit=1,
tile_grid_size_px=3,
tile_grid_size_px_min=2,
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=iaa.CSPACE_HSV)
params = clahe.get_parameters()
assert params[0].value == 1
assert params[1][0].value == 3
assert params[1][1] is None
assert params[2] == 2
assert params[3] == iaa.CSPACE_BGR
assert params[4] == iaa.CSPACE_HSV
def test_pickleable(self):
aug = iaa.CLAHE(clip_limit=(30, 50),
tile_grid_size_px=(4, 12),
seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(100, 100, 3))
class TestAllChannelsHistogramEqualization(unittest.TestCase):
def setUp(self):
reseed()
def test_basic_functionality(self):
gen = itertools.product([None, 1, 2, 3], [1, 2, 3], [False, True])
for nb_channels, nb_images, is_array in gen:
with self.subTest(nb_channels=nb_channels, nb_images=nb_images,
is_array=is_array):
img = [
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19]
]
img = np.uint8(img)
if nb_channels is not None:
img = np.tile(img[..., np.newaxis], (1, 1, nb_channels))
imgs = [img] * nb_images
if is_array:
imgs = np.uint8(imgs)
def _side_effect(img_call):
return img_call + 1
mock_equalizeHist = mock.MagicMock(side_effect=_side_effect)
with mock.patch('cv2.equalizeHist', mock_equalizeHist):
aug = iaa.AllChannelsHistogramEqualization()
imgs_aug = aug.augment_images(imgs)
if is_array:
assert ia.is_np_array(imgs_aug)
else:
assert isinstance(imgs_aug, list)
assert len(imgs_aug) == nb_images
for i in sm.xrange(nb_images):
assert imgs_aug[i].dtype.name == "uint8"
assert np.array_equal(imgs_aug[i], imgs[i] + 1)
def test_basic_functionality_integrationtest(self):
nb_channels = 3
nb_images = 2
img = [
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19]
]
img = np.uint8(img)
img = np.tile(img[..., np.newaxis], (1, 1, nb_channels))
imgs = [img] * nb_images
imgs = np.uint8(imgs)
imgs[1][3:, ...] = 0
aug = iaa.AllChannelsHistogramEqualization()
imgs_aug = aug.augment_images(imgs)
assert imgs_aug.dtype.name == "uint8"
assert len(imgs_aug) == nb_images
for i in sm.xrange(nb_images):
assert imgs_aug[i].shape == img.shape
assert np.max(imgs_aug[i]) > np.max(img)
assert len(np.unique(imgs_aug[0])) > len(np.unique(imgs_aug[1]))
def test_other_dtypes(self):
aug = iaa.AllChannelsHistogramEqualization()
# np.uint16: cv2.error: OpenCV(3.4.5) (...)/histogram.cpp:3345:
# error: (-215:Assertion failed)
# src.type() == CV_8UC1 in function 'equalizeHist'
# np.uint32: TypeError: src data type = 6 is not supported
# np.uint64: see np.uint16
# np.int8: see np.uint16
# np.int16: see np.uint16
# np.int32: see np.uint16
# np.int64: see np.uint16
# np.float16: TypeError: src data type = 23 is not supported
# np.float32: see np.uint16
# np.float64: see np.uint16
# np.float128: TypeError: src data type = 13 is not supported
for dtype in [np.uint8]:
with self.subTest(dtype=np.dtype(dtype).name):
min_value, _center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
dynamic_range = max_value + abs(min_value)
if np.dtype(dtype).kind == "f":
img = np.zeros((16,), dtype=dtype)
for i in sm.xrange(16):
img[i] = min_value + i * (0.01 * dynamic_range)
img = img.reshape((4, 4))
else:
img = np.arange(
min_value, min_value + 16, dtype=dtype).reshape((4, 4))
img_aug = aug.augment_image(img)
assert img_aug.dtype.name == np.dtype(dtype).name
assert img_aug.shape == img.shape
assert np.min(img_aug) < min_value + 0.1 * dynamic_range
assert np.max(img_aug) > max_value - 0.1 * dynamic_range
def test_keypoints_not_changed(self):
aug = iaa.AllChannelsHistogramEqualization()
kpsoi = ia.KeypointsOnImage([ia.Keypoint(1, 1)], shape=(3, 3, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])
assert keypoints_equal([kpsoi], kpsoi_aug)
def test_heatmaps_not_changed(self):
aug = iaa.AllChannelsHistogramEqualization()
heatmaps_arr = np.zeros((3, 3, 1), dtype=np.float32) + 0.5
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
assert np.allclose(heatmaps.arr_0to1, heatmaps_aug.arr_0to1)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 129, dtype=np.uint8)
aug = iaa.AllChannelsHistogramEqualization()
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 129, dtype=np.uint8)
aug = iaa.AllChannelsHistogramEqualization()
image_aug = aug(image=image)
assert np.any(image_aug != 128)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
aug = iaa.AllChannelsHistogramEqualization()
params = aug.get_parameters()
assert len(params) == 0
def test_pickleable(self):
aug = iaa.AllChannelsHistogramEqualization(seed=1)
runtest_pickleable_uint8_img(aug, iterations=2, shape=(100, 100, 3))
class TestHistogramEqualization(unittest.TestCase):
def setUp(self):
reseed()
def test_init(self):
aug = iaa.HistogramEqualization(
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=iaa.CSPACE_HSV)
assert isinstance(
aug.all_channel_histogram_equalization,
iaa.AllChannelsHistogramEqualization)
icba = aug.intensity_channel_based_applier
assert icba.from_colorspace == iaa.CSPACE_BGR
assert icba.to_colorspace == iaa.CSPACE_HSV
def test_basic_functionality_integrationtest(self):
for nb_channels in [None, 1, 3, 4, 5]:
with self.subTest(nb_channels=nb_channels):
img = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]
]
img = np.uint8(img)
if nb_channels is not None:
img = np.tile(img[..., np.newaxis], (1, 1, nb_channels))
if nb_channels >= 3:
img[..., 1] += 10
img[..., 2] += 20
aug = iaa.HistogramEqualization(
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=iaa.CSPACE_HSV,
name="ExampleHistEq")
if nb_channels is None or nb_channels != 5:
img_aug = aug.augment_image(img)
else:
with warnings.catch_warnings(record=True) as caught_warns:
warnings.simplefilter("always")
img_aug = aug.augment_image(img)
assert len(caught_warns) == 1
assert (
"Got image with 5 channels in "
"_IntensityChannelBasedApplier (parents: "
"ExampleHistEq)"
in str(caught_warns[-1].message)
)
expected = img
if nb_channels is None or nb_channels == 1:
expected = cv2.equalizeHist(expected)
if nb_channels == 1:
expected = expected[..., np.newaxis]
elif nb_channels == 5:
for c in sm.xrange(expected.shape[2]):
expected[..., c:c+1] = cv2.equalizeHist(
expected[..., c]
)[..., np.newaxis]
else:
if nb_channels == 4:
expected = expected[..., 0:3]
expected = cv2.cvtColor(expected, cv2.COLOR_RGB2HSV)
expected[..., 2] = cv2.equalizeHist(expected[..., 2])
expected = cv2.cvtColor(expected, cv2.COLOR_HSV2RGB)
if nb_channels == 4:
expected = np.concatenate(
(expected, img[..., 3:4]), axis=2)
assert np.array_equal(img_aug, expected)
def test_determinism(self):
aug = iaa.HistogramEqualization(
from_colorspace=iaa.CSPACE_RGB,
to_colorspace=iaa.CSPACE_Lab)
for nb_channels in [None, 1, 3, 4]:
with self.subTest(nb_channels=nb_channels):
img = np.random.randint(0, 255, (128, 128), dtype=np.uint8)
if nb_channels is not None:
img = np.tile(img[..., np.newaxis], (1, 1, nb_channels))
aug_det = aug.to_deterministic()
result1 = aug_det.augment_image(img)
result2 = aug_det.augment_image(img)
assert np.array_equal(result1, result2)
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 129, dtype=np.uint8)
aug = iaa.HistogramEqualization()
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
aug = iaa.HistogramEqualization(
from_colorspace=iaa.CSPACE_BGR,
to_colorspace=iaa.CSPACE_HSV)
params = aug.get_parameters()
assert params[0] == iaa.CSPACE_BGR
assert params[1] == iaa.CSPACE_HSV
def test_pickleable(self):
aug = iaa.HistogramEqualization(seed=1)
runtest_pickleable_uint8_img(aug, iterations=2, shape=(100, 100, 3))
================================================
FILE: test/augmenters/test_convolutional.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug import random as iarandom
from imgaug.testutils import reseed, runtest_pickleable_uint8_img
# TODO add tests for EdgeDetect
# TODO add tests for DirectedEdgeDetect
class Test_convolve(unittest.TestCase):
def test_1x1_identity_matrix_2d_image(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
matrix = np.float32([
[1.0]
])
image_aug = iaa.convolve(image, matrix)
assert image_aug is not image
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3)
assert np.array_equal(image_aug, image)
class Test_convolve_(unittest.TestCase):
def test_1x1_identity_matrix_2d_image_small_image_sizes(self):
for height in np.arange(16):
for width in np.arange(16):
shapes = [
(height, width),
(height, width, 1),
(height, width, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.mod(
np.arange(int(np.prod(shape))).reshape(shape),
255
).astype(np.uint8)
matrix = np.float32([
[1.0]
])
image_aug = iaa.convolve_(np.copy(image), matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
assert np.array_equal(image_aug, image)
def test_1x1_identity_matrix_2d_image(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
matrix = np.float32([
[1.0]
])
image_aug = iaa.convolve_(np.copy(image), matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3)
assert np.array_equal(image_aug, image)
def test_2x2_identity_matrix_2d_image(self):
image = np.array([
[0, 10, 20, 30],
[40, 50, 60, 70]
], dtype=np.uint8)
matrix = np.float32([
[0.0, 0.0],
[0.0, 1.0]
])
image_aug = iaa.convolve_(np.copy(image), matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 4)
assert np.array_equal(image_aug, image)
def test_3x3_identity_matrix_2d_image(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
matrix = np.float32([
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0]
])
image_aug = iaa.convolve_(np.copy(image), matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3)
assert np.array_equal(image_aug, image)
def test_single_matrix_2d_image(self):
image = np.array([
[0, 10, 20],
[30, 40, 50],
[60, 70, 80]
], dtype=np.uint8)
matrix = np.float32([
[0.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0]
])
expected = np.array([
[0+30, 10+40, 20+50],
[30+0, 40+10, 50+20],
[60+30, 70+40, 80+50]
], dtype=np.float32)
image_aug = iaa.convolve_(np.copy(image), matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (3, 3)
assert np.array_equal(image_aug, expected)
def test_single_matrix_3d_image(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image = np.tile(image[:, :, np.newaxis], (1, 1, 2))
matrix = np.float32([
[0.0, 0.0, 0.0],
[0.0, 2.0, 0.0],
[0.0, 0.0, 0.0]
])
image_aug = iaa.convolve_(np.copy(image), matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3, 2)
assert np.array_equal(image_aug, 2*image)
def test_matrix_is_list_of_arrays(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image = np.tile(image[:, :, np.newaxis], (1, 1, 2))
matrices = [
np.float32([
[0.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 0.0]
]),
np.float32([
[0.0, 0.0, 0.0],
[0.0, 2.0, 0.0],
[0.0, 0.0, 0.0]
])
]
image_aug = iaa.convolve_(np.copy(image), matrices)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3, 2)
assert np.array_equal(image_aug[:, :, 0], image[:, :, 0])
assert np.array_equal(image_aug[:, :, 1], 2*image[:, :, 1])
def test_matrix_is_list_containing_none(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image = np.tile(image[:, :, np.newaxis], (1, 1, 2))
matrices = [
None,
np.float32([
[0.0, 0.0, 0.0],
[0.0, 2.0, 0.0],
[0.0, 0.0, 0.0]
])
]
image_aug = iaa.convolve_(np.copy(image), matrices)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3, 2)
assert np.array_equal(image_aug[:, :, 0], image[:, :, 0])
assert np.array_equal(image_aug[:, :, 1], 2*image[:, :, 1])
def test_matrix_is_list_containing_only_none(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image = np.tile(image[:, :, np.newaxis], (1, 1, 2))
matrices = [
None,
None
]
image_aug = iaa.convolve_(np.copy(image), matrices)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3, 2)
assert np.array_equal(image_aug[:, :, 0], image[:, :, 0])
assert np.array_equal(image_aug[:, :, 1], image[:, :, 1])
def test_unusual_channel_numbers(self):
for nb_channels in [1, 2, 3, 4, 5, 10, 512, 513]:
with self.subTest(nb_channels=nb_channels):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image = image[:, :, np.newaxis]
image = np.tile(image, (1, 1, nb_channels))
matrix = np.float32([
[2.0]
])
image_aug = iaa.convolve_(np.copy(image), matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3, nb_channels)
assert np.array_equal(image_aug, 2*image)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
matrix = np.float32([
[2.0]
])
image_aug = iaa.convolve_(np.copy(image), matrix)
assert image_aug.shape == image.shape
def test_view_heightwise(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image_view = np.copy(image)[:2, :]
assert image_view.flags["OWNDATA"] is False
matrix = np.float32([
[2.0]
])
image_aug = iaa.convolve_(image_view, matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3)
assert np.array_equal(image_aug, 2*image)
def test_view_channelwise_1_channel(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
image_view = np.copy(image)[:, :, [False, True, False]]
assert image_view.flags["OWNDATA"] is False
assert image_view.base.shape == (1, 2, 3)
matrix = np.float32([
[2.0]
])
image_aug = iaa.convolve_(image_view, matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3, 1)
assert np.array_equal(image_aug, 2*image[:, :, 1:2])
def test_view_channelwise_4_channels(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image = np.tile(image[:, :, np.newaxis], (1, 1, 6))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
mask = [False, True, True, True, True, False]
image_view = np.copy(image)[:, :, mask]
assert image_view.flags["OWNDATA"] is False
assert image_view.base.shape == (4, 2, 3)
matrix = np.float32([
[2.0]
])
image_aug = iaa.convolve_(image_view, matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3, 4)
assert np.array_equal(image_aug, 2*image[:, :, mask])
def test_noncontiguous(self):
image = np.array([
[0, 10, 20],
[30, 40, 50]
], dtype=np.uint8)
image_nonc = np.array(image, dtype=np.uint8, order="F")
assert image_nonc.flags["C_CONTIGUOUS"] is False
matrix = np.float32([
[2.0]
])
image_aug = iaa.convolve_(image_nonc, matrix)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 3)
assert np.array_equal(image_aug, 2*image)
# TODO add test for keypoints once their handling was improved in Convolve
class TestConvolve(unittest.TestCase):
def setUp(self):
reseed()
@property
def img(self):
return np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
], dtype=np.uint8)
def test_matrix_is_none(self):
aug = iaa.Convolve(matrix=None)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, self.img)
def test_matrix_is_lambda_none(self):
def _matrix_generator(_img, _nb_channels, _random_state):
return [None]
aug = iaa.Convolve(matrix=_matrix_generator)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, self.img)
def test_matrix_is_1x1_identity(self):
# matrix is [[1]]
aug = iaa.Convolve(matrix=np.float32([[1]]))
observed = aug.augment_image(self.img)
assert np.array_equal(observed, self.img)
def test_matrix_is_lambda_1x1_identity(self):
def _matrix_generator(_img, _nb_channels, _random_state):
return np.float32([[1]])
aug = iaa.Convolve(matrix=_matrix_generator)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, self.img)
def test_matrix_is_3x3_identity(self):
m = np.float32([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=m)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, self.img)
def test_matrix_is_lambda_3x3_identity(self):
def _matrix_generator(_img, _nb_channels, _random_state):
return np.float32([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=_matrix_generator)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, self.img)
def test_matrix_is_3x3_two_in_center(self):
m = np.float32([
[0, 0, 0],
[0, 2, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=m)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, 2*self.img)
def test_matrix_is_lambda_3x3_two_in_center(self):
def _matrix_generator(_img, _nb_channels, _random_state):
return np.float32([
[0, 0, 0],
[0, 2, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=_matrix_generator)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, 2*self.img)
def test_matrix_is_3x3_two_in_center_3_channels(self):
m = np.float32([
[0, 0, 0],
[0, 2, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=m)
img3 = np.tile(self.img[..., np.newaxis], (1, 1, 3)) # 3 channels
observed = aug.augment_image(img3)
assert np.array_equal(observed, 2*img3)
def test_matrix_is_lambda_3x3_two_in_center_3_channels(self):
def _matrix_generator(_img, _nb_channels, _random_state):
return np.float32([
[0, 0, 0],
[0, 2, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=_matrix_generator)
img3 = np.tile(self.img[..., np.newaxis], (1, 1, 3)) # 3 channels
observed = aug.augment_image(img3)
assert np.array_equal(observed, 2*img3)
def test_matrix_is_3x3_with_multiple_nonzero_values(self):
m = np.float32([
[0, -1, 0],
[0, 10, 0],
[0, 0, 0]
])
expected = np.uint8([
[10*1+(-1)*4, 10*2+(-1)*5, 10*3+(-1)*6],
[10*4+(-1)*1, 10*5+(-1)*2, 10*6+(-1)*3],
[10*7+(-1)*4, 10*8+(-1)*5, 10*9+(-1)*6]
])
aug = iaa.Convolve(matrix=m)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, expected)
def test_matrix_is_lambda_3x3_with_multiple_nonzero_values(self):
def _matrix_generator(_img, _nb_channels, _random_state):
return np.float32([
[0, -1, 0],
[0, 10, 0],
[0, 0, 0]
])
expected = np.uint8([
[10*1+(-1)*4, 10*2+(-1)*5, 10*3+(-1)*6],
[10*4+(-1)*1, 10*5+(-1)*2, 10*6+(-1)*3],
[10*7+(-1)*4, 10*8+(-1)*5, 10*9+(-1)*6]
])
aug = iaa.Convolve(matrix=_matrix_generator)
observed = aug.augment_image(self.img)
assert np.array_equal(observed, expected)
def test_lambda_with_changing_matrices(self):
# changing matrices when using callable
def _matrix_generator(_img, _nb_channels, random_state):
return np.float32([[
iarandom.polyfill_integers(random_state, 0, 5)
]])
expected = []
for i in sm.xrange(5):
expected.append(self.img * i)
aug = iaa.Convolve(matrix=_matrix_generator)
seen = [False] * 5
for _ in sm.xrange(200):
observed = aug.augment_image(self.img)
found = False
for i, expected_i in enumerate(expected):
if np.array_equal(observed, expected_i):
seen[i] = True
found = True
break
assert found
if all(seen):
break
assert np.all(seen)
def test_matrix_has_bad_datatype(self):
# don't use assertRaisesRegex, because it doesnt exist in 2.7
got_exception = False
try:
_aug = iaa.Convolve(matrix=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Convolve(matrix=np.float32([[1]]))
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_get_parameters(self):
matrix = np.int32([[1]])
aug = iaa.Convolve(matrix=matrix)
params = aug.get_parameters()
assert np.array_equal(params[0], matrix)
assert params[1] == "constant"
def test_other_dtypes_bool_identity_matrix(self):
identity_matrix = np.int64([[1]])
aug = iaa.Convolve(matrix=identity_matrix)
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = aug.augment_image(image)
assert image.dtype.type == np.bool_
assert np.all(image_aug == image)
def test_other_dtypes_uint_int_identity_matrix(self):
identity_matrix = np.int64([[1]])
aug = iaa.Convolve(matrix=identity_matrix)
for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = 100
image_aug = aug.augment_image(image)
assert image.dtype.type == dtype
assert np.all(image_aug == image)
def test_other_dtypes_float_identity_matrix(self):
identity_matrix = np.int64([[1]])
aug = iaa.Convolve(matrix=identity_matrix)
for dtype in [np.float16, np.float32, np.float64]:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = 100.0
image_aug = aug.augment_image(image)
assert image.dtype.type == dtype
assert np.allclose(image_aug, image)
def test_other_dtypes_bool_non_identity_matrix_with_small_values(self):
matrix = np.float64([
[0, 0.6, 0],
[0, 0.4, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=matrix)
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image[2, 1] = True
expected = np.zeros((3, 3), dtype=bool)
expected[0, 1] = True
expected[2, 1] = True
image_aug = aug.augment_image(image)
assert image.dtype.type == np.bool_
assert np.all(image_aug == expected)
def test_other_dtypes_uint_int_non_identity_matrix_with_small_values(self):
matrix = np.float64([
[0, 0.5, 0],
[0, 0.5, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=matrix)
for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = 100
image[2, 1] = 100
image_aug = aug.augment_image(image)
expected = np.zeros((3, 3), dtype=dtype)
expected[0, 1] = int(np.round(100 * 0.5))
expected[1, 1] = int(np.round(100 * 0.5))
expected[2, 1] = int(np.round(100 * 0.5 + 100 * 0.5))
diff = np.abs(
image_aug.astype(np.int64)
- expected.astype(np.int64))
assert image_aug.dtype.type == dtype
assert np.max(diff) <= 2
def test_other_dtypes_float_non_identity_matrix_with_small_values(self):
matrix = np.float64([
[0, 0.5, 0],
[0, 0.5, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=matrix)
for dtype in [np.float16, np.float32, np.float64]:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = 100.0
image[2, 1] = 100.0
image_aug = aug.augment_image(image)
expected = np.zeros((3, 3), dtype=dtype)
expected[0, 1] = 100 * 0.5
expected[1, 1] = 100 * 0.5
expected[2, 1] = 100 * 0.5 + 100 * 0.5
diff = np.abs(
image_aug.astype(np.float64) - expected.astype(np.float64)
)
assert image_aug.dtype.type == dtype
assert np.max(diff) < 1.0
def test_other_dtypes_uint_int_non_identity_matrix_with_large_values(self):
matrix = np.float64([
[0, 0.5, 0],
[0, 0.5, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=matrix)
for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
_min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = int(center_value + 0.4 * max_value)
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image[2, 1] = value
image_aug = aug.augment_image(image)
expected = np.zeros((3, 3), dtype=dtype)
expected[0, 1] = int(np.round(value * 0.5))
expected[1, 1] = int(np.round(value * 0.5))
expected[2, 1] = int(np.round(value * 0.5 + value * 0.5))
diff = np.abs(
image_aug.astype(np.int64)
- expected.astype(np.int64))
assert image_aug.dtype.type == dtype
assert np.max(diff) <= 2
def test_other_dtypes_float_non_identity_matrix_with_large_values(self):
matrix = np.float64([
[0, 0.5, 0],
[0, 0.5, 0],
[0, 0, 0]
])
aug = iaa.Convolve(matrix=matrix)
for dtype, value in zip([np.float16, np.float32, np.float64],
[5000, 1000*1000, 1000*1000*1000]):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image[2, 1] = value
image_aug = aug.augment_image(image)
expected = np.zeros((3, 3), dtype=dtype)
expected[0, 1] = value * 0.5
expected[1, 1] = value * 0.5
expected[2, 1] = value * 0.5 + value * 0.5
diff = np.abs(
image_aug.astype(np.float64) - expected.astype(np.float64))
assert image_aug.dtype.type == dtype
assert np.max(diff) < 1.0
def test_failure_on_invalid_dtypes(self):
# don't use assertRaisesRegex, because it doesnt exist in 2.7
identity_matrix = np.int64([[1]])
aug = iaa.Convolve(matrix=identity_matrix)
for dt in [np.uint32, np.uint64, np.int32, np.int64]:
got_exception = False
try:
_ = aug.augment_image(np.zeros((1, 1), dtype=dt))
except Exception as exc:
assert "forbidden dtype" in str(exc)
got_exception = True
assert got_exception
def test_pickleable__identity_matrix(self):
identity_matrix = np.int64([[1]])
aug = iaa.Convolve(identity_matrix, seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
def test_pickleable__callback_function(self):
aug = iaa.Convolve(_convolve_pickleable_matrix_generator,
seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
def _convolve_pickleable_matrix_generator(_img, _nb_channels, random_state):
return np.float32([[random_state.integers(0, 5)]])
class TestSharpen(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _compute_sharpened_base_img(cls, lightness, m):
img = np.zeros((3, 3), dtype=np.float32)
k = 1
# note that cv2 uses reflection padding by default
img[0, 0] = (
(m[1, 1] + lightness)/k * 10
+ 4 * (m[0, 0]/k) * 10
+ 4 * (m[2, 2]/k) * 20
)
img[0, 2] = img[0, 0]
img[2, 0] = img[0, 0]
img[2, 2] = img[0, 0]
img[0, 1] = (
(m[1, 1] + lightness)/k * 10
+ 6 * (m[0, 1]/k) * 10
+ 2 * (m[2, 2]/k) * 20
)
img[1, 0] = img[0, 1]
img[1, 2] = img[0, 1]
img[2, 1] = img[0, 1]
img[1, 1] = (
(m[1, 1] + lightness)/k * 20
+ 8 * (m[0, 1]/k) * 10
)
img = np.clip(img, 0, 255).astype(np.uint8)
return img
@property
def base_img(self):
base_img = [[10, 10, 10],
[10, 20, 10],
[10, 10, 10]]
base_img = np.uint8(base_img)
return base_img
@property
def base_img_sharpened(self):
return self._compute_sharpened_base_img(1, self.m)
@property
def m(self):
return np.array([[-1, -1, -1],
[-1, 8, -1],
[-1, -1, -1]], dtype=np.float32)
@property
def m_noop(self):
return np.array([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]], dtype=np.float32)
def test_alpha_zero(self):
aug = iaa.Sharpen(alpha=0, lightness=1)
observed = aug.augment_image(self.base_img)
expected = self.base_img
assert np.allclose(observed, expected)
def test_alpha_one(self):
aug = iaa.Sharpen(alpha=1.0, lightness=1)
observed = aug.augment_image(self.base_img)
expected = self.base_img_sharpened
assert np.allclose(observed, expected)
def test_alpha_050(self):
aug = iaa.Sharpen(alpha=0.5, lightness=1)
observed = aug.augment_image(self.base_img)
expected = self._compute_sharpened_base_img(
0.5*1, 0.5 * self.m_noop + 0.5 * self.m)
assert np.allclose(observed, expected.astype(np.uint8))
def test_alpha_075(self):
aug = iaa.Sharpen(alpha=0.75, lightness=1)
observed = aug.augment_image(self.base_img)
expected = self._compute_sharpened_base_img(
0.75*1, 0.25 * self.m_noop + 0.75 * self.m)
assert np.allclose(observed, expected)
def test_alpha_is_stochastic_parameter(self):
aug = iaa.Sharpen(alpha=iap.Choice([0.5, 1.0]), lightness=1)
observed = aug.augment_image(self.base_img)
expected1 = self._compute_sharpened_base_img(
0.5*1, 0.5 * self.m_noop + 0.5 * self.m)
expected2 = self._compute_sharpened_base_img(
1.0*1, 0.0 * self.m_noop + 1.0 * self.m)
assert (
np.allclose(observed, expected1)
or np.allclose(observed, expected2)
)
def test_failure_if_alpha_has_bad_datatype(self):
# don't use assertRaisesRegex, because it doesnt exist in 2.7
got_exception = False
try:
_ = iaa.Sharpen(alpha="test", lightness=1)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_alpha_1_lightness_2(self):
aug = iaa.Sharpen(alpha=1.0, lightness=2)
observed = aug.augment_image(self.base_img)
expected = self._compute_sharpened_base_img(1.0*2, self.m)
assert np.allclose(observed, expected)
def test_alpha_1_lightness_3(self):
aug = iaa.Sharpen(alpha=1.0, lightness=3)
observed = aug.augment_image(self.base_img)
expected = self._compute_sharpened_base_img(1.0*3, self.m)
assert np.allclose(observed, expected)
def test_alpha_1_lightness_is_stochastic_parameter(self):
aug = iaa.Sharpen(alpha=1.0, lightness=iap.Choice([1.0, 1.5]))
observed = aug.augment_image(self.base_img)
expected1 = self._compute_sharpened_base_img(1.0*1.0, self.m)
expected2 = self._compute_sharpened_base_img(1.0*1.5, self.m)
assert (
np.allclose(observed, expected1)
or np.allclose(observed, expected2)
)
def test_failure_if_lightness_has_bad_datatype(self):
# don't use assertRaisesRegex, because it doesnt exist in 2.7
got_exception = False
try:
_ = iaa.Sharpen(alpha=1.0, lightness="test")
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
# this part doesnt really work so far due to nonlinearities resulting
# from clipping to uint8
"""
# alpha range
aug = iaa.Sharpen(alpha=(0.0, 1.0), lightness=1)
base_img = np.copy(base_img)
base_img_sharpened_min = _compute_sharpened_base_img(
0.0*1, 1.0 * m_noop + 0.0 * m)
base_img_sharpened_max = _compute_sharpened_base_img(
1.0*1, 0.0 * m_noop + 1.0 * m)
#distance_max = np.average(
np.abs(
base_img_sharpened.astype(np.float32)
- base_img.astype(np.float32)
)
)
distance_max = np.average(
np.abs(
base_img_sharpened_max
- base_img_sharpened_min
)
)
nb_iterations = 250
distances = []
for _ in sm.xrange(nb_iterations):
observed = aug.augment_image(base_img)
distance = np.average(
np.abs(
observed.astype(np.float32)
- base_img_sharpened_max.astype(np.float32)
)
) / distance_max
distances.append(distance)
print(distances)
print(min(distances), np.average(distances), max(distances))
assert 0 - 1e-4 < min(distances) < 0.1
assert 0.4 < np.average(distances) < 0.6
assert 0.9 < max(distances) < 1.0 + 1e-4
nb_bins = 5
hist, _ = np.histogram(distances, bins=nb_bins, range=(0.0, 1.0),
density=False)
density_expected = 1.0/nb_bins
density_tolerance = 0.05
for nb_samples in hist:
density = nb_samples / nb_iterations
assert (
density_expected - density_tolerance
< density
< density_expected + density_tolerance)
# lightness range
aug = iaa.Sharpen(alpha=1.0, lightness=(0.5, 2.0))
base_img = np.copy(base_img)
base_img_sharpened = _compute_sharpened_base_img(1.0*2.0, m)
distance_max = np.average(
np.abs(
base_img_sharpened.astype(np.int32)
- base_img.astype(np.int32)
)
)
nb_iterations = 250
distances = []
for _ in sm.xrange(nb_iterations):
observed = aug.augment_image(base_img)
distance = np.average(
np.abs(
observed.astype(np.int32)
- base_img.astype(np.int32)
)
) / distance_max
distances.append(distance)
assert 0 - 1e-4 < min(distances) < 0.1
assert 0.4 < np.average(distances) < 0.6
assert 0.9 < max(distances) < 1.0 + 1e-4
nb_bins = 5
hist, _ = np.histogram(distances, bins=nb_bins, range=(0.0, 1.0),
density=False)
density_expected = 1.0/nb_bins
density_tolerance = 0.05
for nb_samples in hist:
density = nb_samples / nb_iterations
assert (
density_expected - density_tolerance
< density
< density_expected + density_tolerance)
"""
def test_pickleable(self):
aug = iaa.Sharpen(alpha=(0.0, 1.0), lightness=(1, 3), seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
class TestEmboss(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _compute_embossed_base_img(cls, img, alpha, strength):
img = np.copy(img)
base_img_embossed = np.zeros((3, 3), dtype=np.float32)
m = np.float32([[-1, 0, 0],
[0, 1, 0],
[0, 0, 1]])
strength_matrix = strength * np.float32([
[-1, -1, 0],
[-1, 0, 1],
[0, 1, 1]
])
ms = m + strength_matrix
for i in range(base_img_embossed.shape[0]):
for j in range(base_img_embossed.shape[1]):
for u in range(ms.shape[0]):
for v in range(ms.shape[1]):
weight = ms[u, v]
inputs_i = abs(i + (u - (ms.shape[0]-1)//2))
inputs_j = abs(j + (v - (ms.shape[1]-1)//2))
if inputs_i >= img.shape[0]:
diff = inputs_i - (img.shape[0]-1)
inputs_i = img.shape[0] - 1 - diff
if inputs_j >= img.shape[1]:
diff = inputs_j - (img.shape[1]-1)
inputs_j = img.shape[1] - 1 - diff
inputs = img[inputs_i, inputs_j]
base_img_embossed[i, j] += inputs * weight
return np.clip(
(1-alpha) * img
+ alpha * base_img_embossed,
0,
255
).astype(np.uint8)
@classmethod
def _allclose(cls, a, b):
return np.max(
a.astype(np.float32)
- b.astype(np.float32)
) <= 2.1
@property
def base_img(self):
return np.array([[10, 10, 10],
[10, 20, 10],
[10, 10, 15]], dtype=np.uint8)
def test_alpha_0_strength_1(self):
aug = iaa.Emboss(alpha=0, strength=1)
observed = aug.augment_image(self.base_img)
expected = self.base_img
assert self._allclose(observed, expected)
def test_alpha_1_strength_1(self):
aug = iaa.Emboss(alpha=1.0, strength=1)
observed = aug.augment_image(self.base_img)
expected = self._compute_embossed_base_img(
self.base_img, alpha=1.0, strength=1)
assert self._allclose(observed, expected)
def test_alpha_050_strength_1(self):
aug = iaa.Emboss(alpha=0.5, strength=1)
observed = aug.augment_image(self.base_img)
expected = self._compute_embossed_base_img(
self.base_img, alpha=0.5, strength=1)
assert self._allclose(observed, expected.astype(np.uint8))
def test_alpha_075_strength_1(self):
aug = iaa.Emboss(alpha=0.75, strength=1)
observed = aug.augment_image(self.base_img)
expected = self._compute_embossed_base_img(
self.base_img, alpha=0.75, strength=1)
assert self._allclose(observed, expected)
def test_alpha_stochastic_parameter_strength_1(self):
aug = iaa.Emboss(alpha=iap.Choice([0.5, 1.0]), strength=1)
observed = aug.augment_image(self.base_img)
expected1 = self._compute_embossed_base_img(
self.base_img, alpha=0.5, strength=1)
expected2 = self._compute_embossed_base_img(
self.base_img, alpha=1.0, strength=1)
assert (
self._allclose(observed, expected1)
or self._allclose(observed, expected2)
)
def test_failure_on_invalid_datatype_for_alpha(self):
# don't use assertRaisesRegex, because it doesnt exist in 2.7
got_exception = False
try:
_ = iaa.Emboss(alpha="test", strength=1)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_alpha_1_strength_2(self):
aug = iaa.Emboss(alpha=1.0, strength=2)
observed = aug.augment_image(self.base_img)
expected = self._compute_embossed_base_img(
self.base_img, alpha=1.0, strength=2)
assert self._allclose(observed, expected)
def test_alpha_1_strength_3(self):
aug = iaa.Emboss(alpha=1.0, strength=3)
observed = aug.augment_image(self.base_img)
expected = self._compute_embossed_base_img(
self.base_img, alpha=1.0, strength=3)
assert self._allclose(observed, expected)
def test_alpha_1_strength_6(self):
aug = iaa.Emboss(alpha=1.0, strength=6)
observed = aug.augment_image(self.base_img)
expected = self._compute_embossed_base_img(
self.base_img, alpha=1.0, strength=6)
assert self._allclose(observed, expected)
def test_alpha_1_strength_stochastic_parameter(self):
aug = iaa.Emboss(alpha=1.0, strength=iap.Choice([1.0, 2.5]))
observed = aug.augment_image(self.base_img)
expected1 = self._compute_embossed_base_img(
self.base_img, alpha=1.0, strength=1.0)
expected2 = self._compute_embossed_base_img(
self.base_img, alpha=1.0, strength=2.5)
assert (
self._allclose(observed, expected1)
or self._allclose(observed, expected2)
)
def test_failure_on_invalid_datatype_for_strength(self):
# don't use assertRaisesRegex, because it doesnt exist in 2.7
got_exception = False
try:
_ = iaa.Emboss(alpha=1.0, strength="test")
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_pickleable(self):
aug = iaa.Emboss(alpha=(0.0, 1.0), strength=(1, 3), seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
================================================
FILE: test/augmenters/test_debug.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import os
try:
import cPickle as pickle
except ImportError:
import pickle
import numpy as np
import cv2
import imageio
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import random as iarandom
from imgaug.testutils import reseed, TemporaryDirectory
import imgaug.augmenters.debug as debuglib
class Test_draw_debug_image(unittest.TestCase):
@classmethod
def _find_in_image_avg_diff(cls, find_image, in_image):
res = cv2.matchTemplate(in_image, find_image, cv2.TM_SQDIFF)
min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
top_left = min_loc
bottom_right = (top_left[0] + find_image.shape[1],
top_left[1] + find_image.shape[0])
image_found = in_image[top_left[1]:bottom_right[1],
top_left[0]:bottom_right[0],
:]
diff = np.abs(image_found.astype(np.float32)
- find_image.astype(np.float32))
return np.average(diff)
@classmethod
def _image_contains(cls, find_image, in_image, threshold=2.0):
return cls._find_in_image_avg_diff(find_image, in_image) <= threshold
def test_one_image(self):
rng = iarandom.RNG(0)
image = rng.integers(0, 256, size=(256, 256, 3), dtype=np.uint8)
debug_image = iaa.draw_debug_image([image])
assert self._image_contains(image, debug_image)
def test_two_images(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 256, 256, 3), dtype=np.uint8)
debug_image = iaa.draw_debug_image(images)
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
def test_two_images_of_different_sizes(self):
rng = iarandom.RNG(0)
image1 = rng.integers(0, 256, size=(256, 256, 3), dtype=np.uint8)
image2 = rng.integers(0, 256, size=(512, 256, 3), dtype=np.uint8)
debug_image = iaa.draw_debug_image([image1, image2])
assert self._image_contains(image1, debug_image)
assert self._image_contains(image2, debug_image)
def test_two_images_and_heatmaps(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 256, 256, 3), dtype=np.uint8)
heatmap = np.zeros((256, 256, 1), dtype=np.float32)
heatmap[128-25:128+25, 128-25:128+25] = 1.0
heatmap1 = ia.HeatmapsOnImage(np.copy(heatmap), shape=images[0].shape)
heatmap2 = ia.HeatmapsOnImage(1.0 - heatmap, shape=images[1].shape)
image1_w_overlay = heatmap1.draw_on_image(images[0])[0]
image2_w_overlay = heatmap2.draw_on_image(images[1])[0]
debug_image = iaa.draw_debug_image(images,
heatmaps=[heatmap1, heatmap2])
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
assert self._image_contains(image1_w_overlay, debug_image)
assert self._image_contains(image2_w_overlay, debug_image)
def test_two_images_and_segmaps(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 256, 256, 3), dtype=np.uint8)
sm1 = np.zeros((256, 256, 1), dtype=np.int32)
sm1[128-25:128+25, 128-25:128+25] = 1
sm2 = np.zeros((256, 256, 1), dtype=np.int32)
sm2[64-25:64+25, 64-25:64+25] = 2
sm2[192-25:192+25, 192-25:192+25] = 3
segmap1 = ia.SegmentationMapsOnImage(sm1, shape=images[0].shape)
segmap2 = ia.SegmentationMapsOnImage(sm2, shape=images[1].shape)
image1_w_overlay = segmap1.draw_on_image(images[0],
draw_background=True)[0]
image2_w_overlay = segmap2.draw_on_image(images[1],
draw_background=True)[0]
debug_image = iaa.draw_debug_image(images,
segmentation_maps=[segmap1, segmap2])
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
assert self._image_contains(image1_w_overlay, debug_image)
assert self._image_contains(image2_w_overlay, debug_image)
def test_two_images_and_heatmaps__map_size_differs_from_image(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 256, 256, 3), dtype=np.uint8)
heatmap = np.zeros((128, 128, 1), dtype=np.float32)
heatmap[64-25:64+25, 64-25:64+25] = 1.0
heatmap1 = ia.HeatmapsOnImage(np.copy(heatmap), shape=images[0].shape)
heatmap2 = ia.HeatmapsOnImage(1.0 - heatmap, shape=images[1].shape)
image1_w_overlay = heatmap1.draw_on_image(images[0])[0]
image2_w_overlay = heatmap2.draw_on_image(images[1])[0]
debug_image = iaa.draw_debug_image(images,
heatmaps=[heatmap1, heatmap2])
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
assert self._image_contains(image1_w_overlay, debug_image)
assert self._image_contains(image2_w_overlay, debug_image)
def test_two_images_and_heatmaps__multichannel(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 256, 256, 3), dtype=np.uint8)
heatmap = np.zeros((256, 256, 2), dtype=np.float32)
heatmap[100-25:100+25, 100-25:100+25, 0] = 1.0
heatmap[200-25:200+25, 200-25:200+25, 1] = 1.0
heatmap1 = ia.HeatmapsOnImage(np.copy(heatmap), shape=images[0].shape)
heatmap2 = ia.HeatmapsOnImage(1.0 - heatmap, shape=images[1].shape)
image1_w_overlay_c1, image1_w_overlay_c2 = \
heatmap1.draw_on_image(images[0])
image2_w_overlay_c1, image2_w_overlay_c2 = \
heatmap2.draw_on_image(images[1])
debug_image = iaa.draw_debug_image(images, heatmaps=[heatmap1, heatmap2])
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
assert self._image_contains(image1_w_overlay_c1, debug_image)
assert self._image_contains(image1_w_overlay_c2, debug_image)
assert self._image_contains(image2_w_overlay_c1, debug_image)
assert self._image_contains(image2_w_overlay_c2, debug_image)
def test_two_images_and_keypoints(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 256, 256, 3), dtype=np.uint8)
kps = []
for x in np.linspace(0, 256, 10):
for y in np.linspace(0, 256, 10):
kps.append(ia.Keypoint(x=x, y=y))
kpsoi1 = ia.KeypointsOnImage(kps, shape=images[0].shape)
kpsoi2 = kpsoi1.shift(x=20)
image1_w_overlay = kpsoi1.draw_on_image(images[0])
image2_w_overlay = kpsoi2.draw_on_image(images[1])
debug_image = iaa.draw_debug_image(images, keypoints=[kpsoi1, kpsoi2])
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
assert self._image_contains(image1_w_overlay, debug_image)
assert self._image_contains(image2_w_overlay, debug_image)
def test_two_images_and_bounding_boxes(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 256, 256, 3), dtype=np.uint8)
bbs = []
for x in np.linspace(0, 256, 5):
for y in np.linspace(0, 256, 5):
bbs.append(ia.BoundingBox(x1=x, y1=y, x2=x+20, y2=y+20))
bbsoi1 = ia.BoundingBoxesOnImage(bbs, shape=images[0].shape)
bbsoi2 = bbsoi1.shift(x=20)
image1_w_overlay = bbsoi1.draw_on_image(images[0])
image2_w_overlay = bbsoi2.draw_on_image(images[1])
debug_image = iaa.draw_debug_image(images,
bounding_boxes=[bbsoi1, bbsoi2])
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
assert self._image_contains(image1_w_overlay, debug_image)
assert self._image_contains(image2_w_overlay, debug_image)
def test_two_images_and_polygons(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 32, 32, 3), dtype=np.uint8)
polys = []
for x in np.linspace(0, 256, 4):
for y in np.linspace(0, 256, 4):
polys.append(ia.Polygon([(x, y), (x+20, y), (x+20, y+20),
(x, y+20)]))
psoi1 = ia.PolygonsOnImage(polys, shape=images[0].shape)
psoi2 = psoi1.shift(x=20)
image1_w_overlay = psoi1.draw_on_image(images[0])
image2_w_overlay = psoi2.draw_on_image(images[1])
debug_image = iaa.draw_debug_image(images,
polygons=[psoi1, psoi2])
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
assert self._image_contains(image1_w_overlay, debug_image)
assert self._image_contains(image2_w_overlay, debug_image)
def test_two_images_and_line_strings(self):
rng = iarandom.RNG(0)
images = rng.integers(0, 256, size=(2, 32, 32, 3), dtype=np.uint8)
ls = []
for x in np.linspace(0, 256, 4):
for y in np.linspace(0, 256, 4):
ls.append(ia.LineString([(x, y), (x+20, y), (x+20, y+20),
(x, y+20)]))
lsoi1 = ia.LineStringsOnImage(ls, shape=images[0].shape)
lsoi2 = lsoi1.deepcopy()
image1_w_overlay = lsoi1.draw_on_image(images[0])
image2_w_overlay = lsoi2.draw_on_image(images[1])
debug_image = iaa.draw_debug_image(images,
line_strings=[lsoi1, lsoi2])
assert self._image_contains(images[0, ...], debug_image)
assert self._image_contains(images[1, ...], debug_image)
assert self._image_contains(image1_w_overlay, debug_image)
assert self._image_contains(image2_w_overlay, debug_image)
def test_one_image_float32(self):
rng = iarandom.RNG(0)
image = rng.random(size=(256, 256, 3)).astype(np.float32)
debug_image = iaa.draw_debug_image([image])
assert self._image_contains((image * 255).astype(np.uint8),
debug_image)
def test_one_image_float32_and_heatmap(self):
rng = iarandom.RNG(0)
image = rng.random(size=(256, 256, 3)).astype(np.float32)
heatmap = np.zeros((256, 256, 1), dtype=np.float32)
heatmap[128-25:128+25, 128-25:128+25] = 1.0
heatmap = ia.HeatmapsOnImage(heatmap, shape=image.shape)
image1_w_overlay = heatmap.draw_on_image(
(image*255).astype(np.uint8))[0]
debug_image = iaa.draw_debug_image([image], heatmaps=[heatmap])
assert self._image_contains((image * 255).astype(np.uint8), debug_image)
assert self._image_contains(image1_w_overlay, debug_image)
class SaveDebugImageEveryNBatches(unittest.TestCase):
def setUp(self):
reseed()
def test_mocked(self):
class _DummyDestination(debuglib._IImageDestination):
def __init__(self):
self.received = []
def receive(self, image):
self.received.append(np.copy(image))
image = iarandom.RNG(0).integers(0, 256, size=(256, 256, 3),
dtype=np.uint8)
destination = _DummyDestination()
aug = iaa.SaveDebugImageEveryNBatches(destination, 10)
for _ in np.arange(20):
_ = aug(image=image)
expected = iaa.draw_debug_image([image])
assert len(destination.received) == 2
assert np.array_equal(destination.received[0], expected)
assert np.array_equal(destination.received[1], expected)
def test_temp_directory(self):
with TemporaryDirectory() as folder_path:
image = iarandom.RNG(0).integers(0, 256, size=(256, 256, 3),
dtype=np.uint8)
aug = iaa.SaveDebugImageEveryNBatches(folder_path, 10)
for _ in np.arange(20):
_ = aug(image=image)
expected = iaa.draw_debug_image([image])
path1 = os.path.join(folder_path, "batch_000000.png")
path2 = os.path.join(folder_path, "batch_000010.png")
path_latest = os.path.join(folder_path, "batch_latest.png")
assert len(list(os.listdir(folder_path))) == 3
assert os.path.isfile(path1)
assert os.path.isfile(path2)
assert os.path.isfile(path_latest)
assert np.array_equal(imageio.imread(path1), expected)
assert np.array_equal(imageio.imread(path2), expected)
assert np.array_equal(imageio.imread(path_latest), expected)
def test_pickleable(self):
shape = (16, 16, 3)
image = np.mod(np.arange(int(np.prod(shape))), 256).astype(np.uint8)
image = image.reshape(shape)
with TemporaryDirectory() as folder_path:
path1 = os.path.join(folder_path, "batch_000000.png")
path2 = os.path.join(folder_path, "batch_000010.png")
augmenter = iaa.SaveDebugImageEveryNBatches(folder_path, 10)
augmenter_pkl = pickle.loads(pickle.dumps(augmenter, protocol=-1))
# save two images via augmenter without pickling
for _ in np.arange(20):
_ = augmenter(image=image)
img11 = imageio.imread(path1)
img12 = imageio.imread(path2)
# reset folder content
os.remove(path1)
os.remove(path2)
# save two images via augmenter that was pickled
for _ in np.arange(20):
_ = augmenter_pkl(image=image)
img21 = imageio.imread(path1)
img22 = imageio.imread(path2)
# compare the two images of original/pickled augmenters
assert np.array_equal(img11, img21)
assert np.array_equal(img12, img22)
================================================
FILE: test/augmenters/test_edges.py
================================================
from __future__ import print_function, division, absolute_import
import itertools
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
try:
import cPickle as pickle
except ImportError:
import pickle
import numpy as np
import cv2
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import random as iarandom
from imgaug.testutils import (reseed, runtest_pickleable_uint8_img,
is_parameter_instance, remove_prefetching)
class TestRandomColorsBinaryImageColorizer(unittest.TestCase):
def setUp(self):
reseed()
def test___init___default_settings(self):
colorizer = iaa.RandomColorsBinaryImageColorizer()
assert is_parameter_instance(colorizer.color_true, iap.DiscreteUniform)
assert is_parameter_instance(colorizer.color_false, iap.DiscreteUniform)
assert colorizer.color_true.a.value == 0
assert colorizer.color_true.b.value == 255
assert colorizer.color_false.a.value == 0
assert colorizer.color_false.b.value == 255
def test___init___deterministic_settinga(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(color_true=1,
color_false=2)
assert is_parameter_instance(colorizer.color_true, iap.Deterministic)
assert is_parameter_instance(colorizer.color_false, iap.Deterministic)
assert colorizer.color_true.value == 1
assert colorizer.color_false.value == 2
def test___init___tuple_and_list(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=(0, 100), color_false=[200, 201, 202])
assert is_parameter_instance(colorizer.color_true, iap.DiscreteUniform)
assert is_parameter_instance(colorizer.color_false, iap.Choice)
assert colorizer.color_true.a.value == 0
assert colorizer.color_true.b.value == 100
assert colorizer.color_false.a[0] == 200
assert colorizer.color_false.a[1] == 201
assert colorizer.color_false.a[2] == 202
def test___init___stochastic_parameters(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=iap.DiscreteUniform(0, 100),
color_false=iap.Choice([200, 201, 202]))
assert is_parameter_instance(colorizer.color_true, iap.DiscreteUniform)
assert is_parameter_instance(colorizer.color_false, iap.Choice)
assert colorizer.color_true.a.value == 0
assert colorizer.color_true.b.value == 100
assert colorizer.color_false.a[0] == 200
assert colorizer.color_false.a[1] == 201
assert colorizer.color_false.a[2] == 202
def test__draw_samples(self):
class _ListSampler(iap.StochasticParameter):
def __init__(self, offset):
super(_ListSampler, self).__init__()
self.offset = offset
self.last_random_state = None
def _draw_samples(self, size, random_state=None):
assert size == (3,)
self.last_random_state = random_state
return np.uint8([0, 1, 2]) + self.offset
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=_ListSampler(0),
color_false=_ListSampler(1))
random_state = iarandom.RNG(42)
color_true, color_false = colorizer._draw_samples(random_state)
assert np.array_equal(color_true, [0, 1, 2])
assert np.array_equal(color_false, [1, 2, 3])
assert colorizer.color_true.last_random_state.equals(random_state)
assert colorizer.color_false.last_random_state.equals(random_state)
def test_colorize__one_channel(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=100,
color_false=10)
random_state = iarandom.RNG(42)
# input image has shape (H,W,1)
image = np.zeros((5, 5, 1), dtype=np.uint8)
image[:, 0:3, :] = 255
image_binary = np.zeros((5, 5), dtype=bool)
image_binary[:, 0:3] = True
image_color = colorizer.colorize(
image_binary, image, nth_image=0, random_state=random_state)
assert image_color.ndim == 3
assert image_color.shape[-1] == 1
assert np.all(image_color[image_binary] == 100)
assert np.all(image_color[~image_binary] == 10)
def test_colorize__three_channels(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=100,
color_false=10)
random_state = iarandom.RNG(42)
# input image has shape (H,W,3)
image = np.zeros((5, 5, 3), dtype=np.uint8)
image[:, 0:3, :] = 255
image_binary = np.zeros((5, 5), dtype=bool)
image_binary[:, 0:3] = True
image_color = colorizer.colorize(
image_binary, image, nth_image=0, random_state=random_state)
assert image_color.ndim == 3
assert image_color.shape[-1] == 3
assert np.all(image_color[image_binary] == 100)
assert np.all(image_color[~image_binary] == 10)
def test_colorize__four_channels(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=100,
color_false=10)
random_state = iarandom.RNG(42)
# input image has shape (H,W,4)
image = np.zeros((5, 5, 4), dtype=np.uint8)
image[:, 0:3, 0:3] = 255
image[:, 1:4, 3] = 123 # set some content for alpha channel
image_binary = np.zeros((5, 5), dtype=bool)
image_binary[:, 0:3] = True
image_color = colorizer.colorize(
image_binary, image, nth_image=0, random_state=random_state)
assert image_color.ndim == 3
assert image_color.shape[-1] == 4
assert np.all(image_color[image_binary, 0:3] == 100)
assert np.all(image_color[~image_binary, 0:3] == 10)
# alpha channel must have been kept untouched
assert np.all(image_color[:, :, 3:4] == image[:, :, 3:4])
def test_pickleable(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=(50, 100),
color_false=(10, 50))
colorizer_pkl = pickle.loads(pickle.dumps(colorizer))
random_state = iarandom.RNG(1)
color_true, color_false = colorizer._draw_samples(
random_state.copy())
color_true_pkl, color_false_pkl = colorizer_pkl._draw_samples(
random_state.copy())
assert np.array_equal(color_true, color_true_pkl)
assert np.array_equal(color_false, color_false_pkl)
class TestCanny(unittest.TestCase):
def test___init___default_settings(self):
aug = iaa.Canny()
assert is_parameter_instance(aug.alpha, iap.Uniform)
assert isinstance(aug.hysteresis_thresholds, tuple)
assert is_parameter_instance(aug.sobel_kernel_size, iap.DiscreteUniform)
assert isinstance(aug.colorizer, iaa.RandomColorsBinaryImageColorizer)
assert np.isclose(aug.alpha.a.value, 0.0)
assert np.isclose(aug.alpha.b.value, 1.0)
assert len(aug.hysteresis_thresholds) == 2
assert is_parameter_instance(aug.hysteresis_thresholds[0],
iap.DiscreteUniform)
assert np.isclose(aug.hysteresis_thresholds[0].a.value, 100-40)
assert np.isclose(aug.hysteresis_thresholds[0].b.value, 100+40)
assert is_parameter_instance(aug.hysteresis_thresholds[1],
iap.DiscreteUniform)
assert np.isclose(aug.hysteresis_thresholds[1].a.value, 200-40)
assert np.isclose(aug.hysteresis_thresholds[1].b.value, 200+40)
assert aug.sobel_kernel_size.a.value == 3
assert aug.sobel_kernel_size.b.value == 7
assert is_parameter_instance(aug.colorizer.color_true,
iap.DiscreteUniform)
assert is_parameter_instance(aug.colorizer.color_false,
iap.DiscreteUniform)
assert aug.colorizer.color_true.a.value == 0
assert aug.colorizer.color_true.b.value == 255
assert aug.colorizer.color_false.a.value == 0
assert aug.colorizer.color_false.b.value == 255
def test___init___custom_settings(self):
aug = iaa.Canny(
alpha=0.2,
hysteresis_thresholds=([0, 1, 2], iap.DiscreteUniform(1, 10)),
sobel_kernel_size=[3, 5],
colorizer=iaa.RandomColorsBinaryImageColorizer(
color_true=10, color_false=20)
)
assert is_parameter_instance(aug.alpha, iap.Deterministic)
assert isinstance(aug.hysteresis_thresholds, tuple)
assert is_parameter_instance(aug.sobel_kernel_size, iap.Choice)
assert isinstance(aug.colorizer, iaa.RandomColorsBinaryImageColorizer)
assert np.isclose(aug.alpha.value, 0.2)
assert len(aug.hysteresis_thresholds) == 2
assert is_parameter_instance(aug.hysteresis_thresholds[0], iap.Choice)
assert aug.hysteresis_thresholds[0].a == [0, 1, 2]
assert is_parameter_instance(aug.hysteresis_thresholds[1],
iap.DiscreteUniform)
assert np.isclose(aug.hysteresis_thresholds[1].a.value, 1)
assert np.isclose(aug.hysteresis_thresholds[1].b.value, 10)
assert is_parameter_instance(aug.sobel_kernel_size, iap.Choice)
assert aug.sobel_kernel_size.a == [3, 5]
assert is_parameter_instance(aug.colorizer.color_true,
iap.Deterministic)
assert is_parameter_instance(aug.colorizer.color_false,
iap.Deterministic)
assert aug.colorizer.color_true.value == 10
assert aug.colorizer.color_false.value == 20
def test___init___single_value_hysteresis(self):
aug = iaa.Canny(
alpha=0.2,
hysteresis_thresholds=[0, 1, 2],
sobel_kernel_size=[3, 5],
colorizer=iaa.RandomColorsBinaryImageColorizer(
color_true=10, color_false=20)
)
assert is_parameter_instance(aug.alpha, iap.Deterministic)
assert is_parameter_instance(aug.hysteresis_thresholds, iap.Choice)
assert is_parameter_instance(aug.sobel_kernel_size, iap.Choice)
assert isinstance(aug.colorizer, iaa.RandomColorsBinaryImageColorizer)
assert np.isclose(aug.alpha.value, 0.2)
assert aug.hysteresis_thresholds.a == [0, 1, 2]
assert is_parameter_instance(aug.sobel_kernel_size, iap.Choice)
assert aug.sobel_kernel_size.a == [3, 5]
assert is_parameter_instance(aug.colorizer.color_true,
iap.Deterministic)
assert is_parameter_instance(aug.colorizer.color_false,
iap.Deterministic)
assert aug.colorizer.color_true.value == 10
assert aug.colorizer.color_false.value == 20
def test__draw_samples__single_value_hysteresis(self):
seed = 1
nb_images = 1000
aug = iaa.Canny(
alpha=0.2,
hysteresis_thresholds=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
sobel_kernel_size=[3, 5, 7],
random_state=iarandom.RNG(seed))
aug.alpha = remove_prefetching(aug.alpha)
aug.hysteresis_thresholds = remove_prefetching(
aug.hysteresis_thresholds)
aug.sobel_kernel_size = remove_prefetching(aug.sobel_kernel_size)
example_image = np.zeros((5, 5, 3), dtype=np.uint8)
samples = aug._draw_samples([example_image] * nb_images,
random_state=iarandom.RNG(seed))
alpha_samples = samples[0]
hthresh_samples = samples[1]
sobel_samples = samples[2]
rss = iarandom.RNG(seed).duplicate(4)
alpha_expected = iap.Deterministic(0.2).draw_samples((nb_images,),
rss[0])
hthresh_expected = iap.Choice(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).draw_samples((nb_images, 2),
rss[1])
sobel_expected = iap.Choice([3, 5, 7]).draw_samples((nb_images,),
rss[2])
invalid = hthresh_expected[:, 0] > hthresh_expected[:, 1]
assert np.any(invalid)
hthresh_expected[invalid, :] = hthresh_expected[invalid, :][:, [1, 0]]
assert hthresh_expected.shape == (nb_images, 2)
assert not np.any(hthresh_expected[:, 0] > hthresh_expected[:, 1])
assert np.allclose(alpha_samples, alpha_expected)
assert np.allclose(hthresh_samples, hthresh_expected)
assert np.allclose(sobel_samples, sobel_expected)
def test__draw_samples__tuple_as_hysteresis(self):
seed = 1
nb_images = 10
aug = iaa.Canny(
alpha=0.2,
hysteresis_thresholds=([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
iap.DiscreteUniform(5, 100)),
sobel_kernel_size=[3, 5, 7],
random_state=iarandom.RNG(seed))
aug.alpha = remove_prefetching(aug.alpha)
aug.hysteresis_thresholds = (
remove_prefetching(aug.hysteresis_thresholds[0]),
remove_prefetching(aug.hysteresis_thresholds[1])
)
aug.sobel_kernel_size = remove_prefetching(aug.sobel_kernel_size)
example_image = np.zeros((5, 5, 3), dtype=np.uint8)
samples = aug._draw_samples([example_image] * nb_images,
random_state=iarandom.RNG(seed))
alpha_samples = samples[0]
hthresh_samples = samples[1]
sobel_samples = samples[2]
rss = iarandom.RNG(seed).duplicate(4)
alpha_expected = iap.Deterministic(0.2).draw_samples((nb_images,),
rss[0])
hthresh_expected = [None, None]
hthresh_expected[0] = iap.Choice(
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]).draw_samples((nb_images,),
rss[1])
# TODO simplify this to rss[2].randint(5, 100+1)
# would currenlty be a bit more ugly, because DiscrUniform
# samples two values for a and b first from rss[2]
hthresh_expected[1] = iap.DiscreteUniform(5, 100).draw_samples(
(nb_images,), rss[2])
hthresh_expected = np.stack(hthresh_expected, axis=-1)
sobel_expected = iap.Choice([3, 5, 7]).draw_samples((nb_images,),
rss[3])
invalid = hthresh_expected[:, 0] > hthresh_expected[:, 1]
hthresh_expected[invalid, :] = hthresh_expected[invalid, :][:, [1, 0]]
assert hthresh_expected.shape == (nb_images, 2)
assert not np.any(hthresh_expected[:, 0] > hthresh_expected[:, 1])
assert np.allclose(alpha_samples, alpha_expected)
assert np.allclose(hthresh_samples, hthresh_expected)
assert np.allclose(sobel_samples, sobel_expected)
def test_augment_images__alpha_is_zero(self):
aug = iaa.Canny(
alpha=0.0,
hysteresis_thresholds=(0, 10),
sobel_kernel_size=[3, 5, 7],
random_state=1)
image = np.arange(5*5*3).astype(np.uint8).reshape((5, 5, 3))
image_aug = aug.augment_image(image)
assert np.array_equal(image_aug, image)
def test_augment_images__alpha_is_one(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=254,
color_false=1
)
aug = iaa.Canny(
alpha=1.0,
hysteresis_thresholds=100,
sobel_kernel_size=3,
colorizer=colorizer,
random_state=1)
image_single_chan = np.uint8([
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0, 0]
])
image = np.tile(image_single_chan[:, :, np.newaxis] * 128, (1, 1, 3))
# canny image, looks a bit unintuitive, but is what OpenCV returns
# can be checked via something like
# print("canny\n", cv2.Canny(image_single_chan*255, threshold1=100,
# threshold2=200,
# apertureSize=3,
# L2gradient=True))
image_canny = np.array([
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 0]
], dtype=bool)
image_aug_expected = np.copy(image)
image_aug_expected[image_canny] = 254
image_aug_expected[~image_canny] = 1
image_aug = aug.augment_image(image)
assert np.array_equal(image_aug, image_aug_expected)
def test_augment_images__single_channel(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=254,
color_false=1
)
aug = iaa.Canny(
alpha=1.0,
hysteresis_thresholds=100,
sobel_kernel_size=3,
colorizer=colorizer,
random_state=1)
image_single_chan = np.uint8([
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0, 0]
])
image = image_single_chan[:, :, np.newaxis] * 128
# canny image, looks a bit unintuitive, but is what OpenCV returns
# can be checked via something like
# print("canny\n", cv2.Canny(image_single_chan*255, threshold1=100,
# threshold2=200,
# apertureSize=3,
# L2gradient=True))
image_canny = np.array([
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 0]
], dtype=bool)
image_aug_expected = np.copy(image)
image_aug_expected[image_canny] = int(0.299*254
+ 0.587*254
+ 0.114*254)
image_aug_expected[~image_canny] = int(0.299*1 + 0.587*1 + 0.114*1)
image_aug = aug.augment_image(image)
assert np.array_equal(image_aug, image_aug_expected)
def test_augment_images__four_channels(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=254,
color_false=1
)
aug = iaa.Canny(
alpha=1.0,
hysteresis_thresholds=100,
sobel_kernel_size=3,
colorizer=colorizer,
random_state=1)
image_single_chan = np.uint8([
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0, 0]
])
image_alpha_channel = np.uint8([
[0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 0, 0],
[0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0]
]) * 255
image = np.tile(image_single_chan[:, :, np.newaxis] * 128, (1, 1, 3))
image = np.dstack([image, image_alpha_channel[:, :, np.newaxis]])
assert image.ndim == 3
assert image.shape[-1] == 4
# canny image, looks a bit unintuitive, but is what OpenCV returns
# can be checked via something like
# print("canny\n", cv2.Canny(image_single_chan*255, threshold1=100,
# threshold2=200,
# apertureSize=3,
# L2gradient=True))
image_canny = np.array([
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 0]
], dtype=bool)
image_aug_expected = np.copy(image)
image_aug_expected[image_canny, 0:3] = 254
image_aug_expected[~image_canny, 0:3] = 1
image_aug = aug.augment_image(image)
assert np.array_equal(image_aug, image_aug_expected)
def test_augment_images__random_color(self):
class _Color(iap.StochasticParameter):
def __init__(self, values):
super(_Color, self).__init__()
self.values = values
def _draw_samples(self, size, random_state):
v = random_state.choice(self.values)
return np.full(size, v, dtype=np.uint8)
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=_Color([253, 254]),
color_false=_Color([1, 2])
)
image_single_chan = np.uint8([
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 0, 0, 1, 0, 0, 0],
[0, 1, 1, 1, 0, 0, 0]
])
image = np.tile(image_single_chan[:, :, np.newaxis] * 128, (1, 1, 3))
# canny image, looks a bit unintuitive, but is what OpenCV returns
# can be checked via something like
# print("canny\n", cv2.Canny(image_single_chan*255, threshold1=100,
# threshold2=200,
# apertureSize=3,
# L2gradient=True))
image_canny = np.array([
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 0, 1, 0, 1, 0, 0],
[0, 1, 0, 0, 1, 0, 0]
], dtype=bool)
seen = {
(253, 1): False,
(253, 2): False,
(254, 1): False,
(254, 2): False
}
for i in range(100):
aug = iaa.Canny(
alpha=1.0,
hysteresis_thresholds=100,
sobel_kernel_size=3,
colorizer=colorizer,
seed=i)
image_aug = aug.augment_image(image)
color_true = np.unique(image_aug[image_canny])
color_false = np.unique(image_aug[~image_canny])
assert len(color_true) == 1
assert len(color_false) == 1
color_true = int(color_true[0])
color_false = int(color_false[0])
seen[(int(color_true), int(color_false))] = True
assert len(seen.keys()) == 4
if all(seen.values()):
break
assert np.all(seen.values())
def test_augment_images__random_values(self):
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=255,
color_false=0
)
image_single_chan = iarandom.RNG(1).integers(
0, 255, size=(100, 100), dtype="uint8")
image = np.tile(image_single_chan[:, :, np.newaxis], (1, 1, 3))
images_canny_uint8 = {}
for thresh1, thresh2, ksize in itertools.product([100],
[200],
[3, 5]):
if thresh1 > thresh2:
continue
image_canny = cv2.Canny(
image,
threshold1=thresh1,
threshold2=thresh2,
apertureSize=ksize,
L2gradient=True)
image_canny_uint8 = np.tile(
image_canny[:, :, np.newaxis], (1, 1, 3))
similar = 0
for key, image_expected in images_canny_uint8.items():
if np.array_equal(image_canny_uint8, image_expected):
similar += 1
assert similar == 0
images_canny_uint8[(thresh1, thresh2, ksize)] = image_canny_uint8
seen = {key: False for key in images_canny_uint8.keys()}
for i in range(500):
aug = iaa.Canny(
alpha=1.0,
hysteresis_thresholds=(iap.Deterministic(100),
iap.Deterministic(200)),
sobel_kernel_size=[3, 5],
colorizer=colorizer,
seed=i)
image_aug = aug.augment_image(image)
match_index = None
for key, image_expected in images_canny_uint8.items():
if np.array_equal(image_aug, image_expected):
match_index = key
break
assert match_index is not None
seen[match_index] = True
assert len(seen.keys()) == len(images_canny_uint8.keys())
if all(seen.values()):
break
assert np.all(seen.values())
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Canny(alpha=1)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_get_parameters(self):
alpha = iap.Deterministic(0.2)
hysteresis_thresholds = iap.Deterministic(10)
sobel_kernel_size = iap.Deterministic(3)
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=10, color_false=20)
aug = iaa.Canny(
alpha=alpha,
hysteresis_thresholds=hysteresis_thresholds,
sobel_kernel_size=sobel_kernel_size,
colorizer=colorizer
)
params = aug.get_parameters()
assert params[0] is aug.alpha
assert params[1] is aug.hysteresis_thresholds
assert params[2] is aug.sobel_kernel_size
assert params[3] is colorizer
def test___str___single_value_hysteresis(self):
alpha = iap.Deterministic(0.2)
hysteresis_thresholds = iap.Deterministic(10)
sobel_kernel_size = iap.Deterministic(3)
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=10, color_false=20)
aug = iaa.Canny(
alpha=alpha,
hysteresis_thresholds=hysteresis_thresholds,
sobel_kernel_size=sobel_kernel_size,
colorizer=colorizer
)
observed = aug.__str__()
expected = ("Canny(alpha=%s, hysteresis_thresholds=%s, "
"sobel_kernel_size=%s, colorizer=%s, name=UnnamedCanny, "
"deterministic=False)") % (
str(aug.alpha),
str(aug.hysteresis_thresholds),
str(aug.sobel_kernel_size),
colorizer)
assert observed == expected
def test___str___tuple_as_hysteresis(self):
alpha = iap.Deterministic(0.2)
hysteresis_thresholds = (
iap.Deterministic(10),
iap.Deterministic(11)
)
sobel_kernel_size = iap.Deterministic(3)
colorizer = iaa.RandomColorsBinaryImageColorizer(
color_true=10, color_false=20)
aug = iaa.Canny(
alpha=alpha,
hysteresis_thresholds=hysteresis_thresholds,
sobel_kernel_size=sobel_kernel_size,
colorizer=colorizer
)
observed = aug.__str__()
expected = ("Canny(alpha=%s, hysteresis_thresholds=(%s, %s), "
"sobel_kernel_size=%s, colorizer=%s, name=UnnamedCanny, "
"deterministic=False)") % (
str(aug.alpha),
str(aug.hysteresis_thresholds[0]),
str(aug.hysteresis_thresholds[1]),
str(aug.sobel_kernel_size),
colorizer)
assert observed == expected
def test_pickleable(self):
aug = iaa.Canny(seed=1)
runtest_pickleable_uint8_img(aug, iterations=20)
================================================
FILE: test/augmenters/test_flip.py
================================================
from __future__ import print_function, division, absolute_import
from abc import ABCMeta, abstractproperty, abstractmethod
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six
import six.moves as sm
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug.testutils import (reseed, assert_cbaois_equal,
runtest_pickleable_uint8_img,
is_parameter_instance)
from imgaug.augmentables.heatmaps import HeatmapsOnImage
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
import imgaug.augmenters.flip as fliplib
class TestHorizontalFlip(unittest.TestCase):
def test_returns_fliplr(self):
aug = iaa.HorizontalFlip(0.5)
assert isinstance(aug, iaa.Fliplr)
assert np.allclose(aug.p.p.value, 0.5)
class TestVerticalFlip(unittest.TestCase):
def test_returns_flipud(self):
aug = iaa.VerticalFlip(0.5)
assert isinstance(aug, iaa.Flipud)
assert np.allclose(aug.p.p.value, 0.5)
@six.add_metaclass(ABCMeta)
class _TestFliplrAndFlipudBase(object):
def setUp(self):
reseed()
@property
@abstractproperty
def image(self):
pass
@property
@abstractproperty
def image_flipped(self):
pass
@property
def images(self):
return np.array([self.image])
@property
def images_flipped(self):
return np.array([self.image_flipped])
@property
@abstractproperty
def heatmaps(self):
pass
@property
@abstractproperty
def heatmaps_flipped(self):
pass
@property
@abstractproperty
def segmaps(self):
pass
@property
@abstractproperty
def segmaps_flipped(self):
pass
@property
@abstractproperty
def kpsoi(self):
pass
@property
@abstractproperty
def kpsoi_flipped(self):
pass
@property
@abstractproperty
def psoi(self):
pass
@property
@abstractproperty
def psoi_flipped(self):
pass
@property
@abstractproperty
def lsoi(self):
pass
@property
@abstractproperty
def lsoi_flipped(self):
pass
@property
@abstractproperty
def bbsoi(self):
pass
@property
@abstractproperty
def bbsoi_flipped(self):
pass
@abstractmethod
def create_aug(self, *args, **kwargs):
pass
@abstractmethod
def create_arr(self, value, dtype):
pass
@abstractmethod
def create_arr_flipped(self, value, dtype):
pass
def test_images_p_is_0(self):
aug = self.create_aug(0)
for _ in sm.xrange(3):
observed = aug.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_images_p_is_0__deterministic(self):
aug = self.create_aug(0).to_deterministic()
for _ in sm.xrange(3):
observed = aug.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_keypoints_p_is_0(self):
self._test_cbaoi_p_is_0("augment_keypoints", self.kpsoi, False)
def test_keypoints_p_is_0__deterministic(self):
self._test_cbaoi_p_is_0("augment_keypoints", self.kpsoi, True)
def test_polygons_p_is_0(self):
self._test_cbaoi_p_is_0("augment_polygons", self.psoi, False)
def test_polygons_p_is_0__deterministic(self):
self._test_cbaoi_p_is_0("augment_polygons", self.psoi, True)
def test_line_strings_p_is_0(self):
self._test_cbaoi_p_is_0("augment_line_strings", self.lsoi, False)
def test_line_strings_p_is_0__deterministic(self):
self._test_cbaoi_p_is_0("augment_line_strings", self.lsoi, True)
def test_bounding_boxes_p_is_0(self):
self._test_cbaoi_p_is_0("augment_bounding_boxes", self.bbsoi, False)
def test_bounding_boxes_p_is_0__deterministic(self):
self._test_cbaoi_p_is_0("augment_bounding_boxes", self.bbsoi, True)
def _test_cbaoi_p_is_0(self, augf_name, cbaoi, deterministic):
aug = self.create_aug(0)
if deterministic:
aug = aug.to_deterministic()
for _ in sm.xrange(3):
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi)
def test_heatmaps_p_is_0(self):
aug = self.create_aug(0)
heatmaps = self.heatmaps
observed = aug.augment_heatmaps(heatmaps)
assert observed.shape == heatmaps.shape
assert np.isclose(observed.min_value, heatmaps.min_value,
rtol=0, atol=1e-6)
assert np.isclose(observed.max_value, heatmaps.max_value,
rtol=0, atol=1e-6)
assert np.array_equal(observed.get_arr(), heatmaps.get_arr())
def test_segmaps_p_is_0(self):
aug = self.create_aug(0)
observed = aug.augment_segmentation_maps(self.segmaps)
assert observed.shape == self.segmaps.shape
assert np.array_equal(observed.get_arr(), self.segmaps.get_arr())
def test_images_p_is_1(self):
aug = self.create_aug(1.0)
for _ in sm.xrange(3):
observed = aug.augment_images(self.images)
expected = self.images_flipped
assert np.array_equal(observed, expected)
def test_images_p_is_1__deterministic(self):
aug = self.create_aug(1.0).to_deterministic()
for _ in sm.xrange(3):
observed = aug.augment_images(self.images)
expected = self.images_flipped
assert np.array_equal(observed, expected)
def test_keypoints_p_is_1(self):
self._test_cbaoi_p_is_1(
"augment_keypoints", self.kpsoi, self.kpsoi_flipped, False)
def test_keypoints_p_is_1__deterministic(self):
self._test_cbaoi_p_is_1(
"augment_keypoints", self.kpsoi, self.kpsoi_flipped, True)
def test_polygons_p_is_1(self):
self._test_cbaoi_p_is_1(
"augment_polygons", self.psoi, self.psoi_flipped, False)
def test_polygons_p_is_1__deterministic(self):
self._test_cbaoi_p_is_1(
"augment_polygons", self.psoi, self.psoi_flipped, True)
def test_line_strings_p_is_1(self):
self._test_cbaoi_p_is_1(
"augment_line_strings", self.lsoi, self.lsoi_flipped, False)
def test_line_strings_p_is_1__deterministic(self):
self._test_cbaoi_p_is_1(
"augment_line_strings", self.lsoi, self.lsoi_flipped, True)
def test_bounding_boxes_p_is_1(self):
self._test_cbaoi_p_is_1(
"augment_bounding_boxes", self.bbsoi, self.bbsoi_flipped, False)
def test_bounding_boxes_p_is_1__deterministic(self):
self._test_cbaoi_p_is_1(
"augment_bounding_boxes", self.bbsoi, self.bbsoi_flipped, True)
def _test_cbaoi_p_is_1(self, augf_name, cbaoi, cbaoi_flipped,
deterministic):
aug = self.create_aug(1.0)
if deterministic:
aug = aug.to_deterministic()
for _ in sm.xrange(3):
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi_flipped)
def test_heatmaps_p_is_1(self):
aug = self.create_aug(1.0)
heatmaps = self.heatmaps
observed = aug.augment_heatmaps(heatmaps)
assert observed.shape == heatmaps.shape
assert np.isclose(observed.min_value, heatmaps.min_value,
rtol=0, atol=1e-6)
assert np.isclose(observed.max_value, heatmaps.max_value,
rtol=0, atol=1e-6)
assert np.array_equal(observed.get_arr(),
self.heatmaps_flipped.get_arr())
def test_segmaps_p_is_1(self):
aug = self.create_aug(1.0)
observed = aug.augment_segmentation_maps(self.segmaps)
assert observed.shape == self.segmaps.shape
assert np.array_equal(observed.get_arr(),
self.segmaps_flipped.get_arr())
def test_images_p_is_050(self):
aug = self.create_aug(0.5)
nb_iterations = 1000
nb_images_flipped = 0
for _ in sm.xrange(nb_iterations):
observed = aug.augment_images(self.images)
if np.array_equal(observed, self.images_flipped):
nb_images_flipped += 1
assert np.isclose(nb_images_flipped/nb_iterations,
0.5, rtol=0, atol=0.1)
def test_images_p_is_050__deterministic(self):
aug = self.create_aug(0.5).to_deterministic()
nb_iterations = 1000
nb_images_flipped_det = 0
for _ in sm.xrange(nb_iterations):
observed = aug.augment_images(self.images)
if np.array_equal(observed, self.images_flipped):
nb_images_flipped_det += 1
assert nb_images_flipped_det in [0, nb_iterations]
def test_keypoints_p_is_050(self):
self._test_cbaoi_p_is_050(
"augment_keypoints", self.kpsoi, self.kpsoi_flipped)
def test_keypoints_p_is_050__deterministic(self):
self._test_cbaoi_p_is_050__deterministic(
"augment_keypoints", self.kpsoi, self.kpsoi_flipped)
def test_polygons_p_is_050(self):
self._test_cbaoi_p_is_050(
"augment_polygons", self.psoi, self.psoi_flipped)
def test_polygons_p_is_050__deterministic(self):
self._test_cbaoi_p_is_050__deterministic(
"augment_polygons", self.psoi, self.psoi_flipped)
def test_line_strings_p_is_050(self):
self._test_cbaoi_p_is_050(
"augment_line_strings", self.lsoi, self.lsoi_flipped)
def test_line_strings_p_is_050__deterministic(self):
self._test_cbaoi_p_is_050__deterministic(
"augment_line_strings", self.lsoi, self.lsoi_flipped)
def test_bounding_boxes_p_is_050(self):
self._test_cbaoi_p_is_050(
"augment_bounding_boxes", self.bbsoi, self.bbsoi_flipped)
def test_bounding_boxes_p_is_050__deterministic(self):
self._test_cbaoi_p_is_050__deterministic(
"augment_bounding_boxes", self.bbsoi, self.bbsoi_flipped)
def _test_cbaoi_p_is_050(self, augf_name, cbaoi, cbaoi_flipped):
aug = self.create_aug(0.5)
nb_iterations = 250
nb_cbaoi_flipped = 0
for _ in sm.xrange(nb_iterations):
observed = getattr(aug, augf_name)(cbaoi)
if np.allclose(observed[0].items[0].coords,
cbaoi_flipped[0].items[0].coords):
nb_cbaoi_flipped += 1
assert np.isclose(nb_cbaoi_flipped/nb_iterations,
0.5, rtol=0, atol=0.2)
def _test_cbaoi_p_is_050__deterministic(self, augf_name, cbaoi,
cbaoi_flipped):
aug = self.create_aug(0.5).to_deterministic()
nb_iterations = 10
nb_cbaoi_flipped = 0
for _ in sm.xrange(nb_iterations):
observed = getattr(aug, augf_name)(cbaoi)
if np.allclose(observed[0].items[0].coords,
cbaoi_flipped[0].items[0].coords):
nb_cbaoi_flipped += 1
assert nb_cbaoi_flipped in [0, nb_iterations]
def test_list_of_images_p_is_050(self):
images_multi = [self.image, self.image]
aug = self.create_aug(0.5)
nb_iterations = 1000
nb_flipped_by_pos = [0] * len(images_multi)
for _ in sm.xrange(nb_iterations):
observed = aug.augment_images(images_multi)
for i in sm.xrange(len(images_multi)):
if np.array_equal(observed[i], self.image_flipped):
nb_flipped_by_pos[i] += 1
assert np.allclose(nb_flipped_by_pos,
500, rtol=0, atol=100)
def test_list_of_images_p_is_050__deterministic(self):
images_multi = [self.image, self.image]
aug = self.create_aug(0.5).to_deterministic()
nb_iterations = 10
nb_flipped_by_pos_det = [0] * len(images_multi)
for _ in sm.xrange(nb_iterations):
observed = aug.augment_images(images_multi)
for i in sm.xrange(len(images_multi)):
if np.array_equal(observed[i], self.image_flipped):
nb_flipped_by_pos_det[i] += 1
for val in nb_flipped_by_pos_det:
assert val in [0, nb_iterations]
def test_images_p_is_stochastic_parameter(self):
aug = self.create_aug(p=iap.Choice([0, 1], p=[0.7, 0.3]))
seen = [0, 0]
for _ in sm.xrange(1000):
observed = aug.augment_image(self.image)
if np.array_equal(observed, self.image):
seen[0] += 1
elif np.array_equal(observed, self.image_flipped):
seen[1] += 1
else:
assert False
assert np.allclose(seen, [700, 300], rtol=0, atol=75)
def test_invalid_datatype_for_p_results_in_failure(self):
with self.assertRaises(Exception):
_ = self.create_aug(p="test")
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = self.create_aug(1.0)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_get_parameters(self):
aug = self.create_aug(p=0.5)
params = aug.get_parameters()
assert is_parameter_instance(params[0], iap.Binomial)
assert is_parameter_instance(params[0].p, iap.Deterministic)
assert 0.5 - 1e-4 < params[0].p.value < 0.5 + 1e-4
def test_other_dtypes_bool(self):
aug = self.create_aug(1.0)
image = self.create_arr(True, bool)
expected = self.create_arr_flipped(True, bool)
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == image.dtype.type
assert np.all(image_aug == expected)
def test_other_dtypes_uint_int(self):
aug = self.create_aug(1.0)
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = self.create_arr(value, dtype)
expected = self.create_arr_flipped(value, dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, expected)
def test_other_dtypes_float(self):
aug = self.create_aug(1.0)
try:
f128 = [np.dtype("float128").name]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000**2, 1000**3, 1000**4]
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype):
atol = (1e-9 * value
if dtype != "float16"
else 1e-3 * value)
image = self.create_arr(value, dtype)
expected = self.create_arr_flipped(value, dtype)
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.allclose(image_aug, expected, atol=atol)
def test_pickleable(self):
aug = self.create_aug(0.5)
runtest_pickleable_uint8_img(aug, iterations=20)
class TestFliplr(_TestFliplrAndFlipudBase, unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
base_img = np.array([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]], dtype=np.uint8)
return base_img[:, :, np.newaxis]
@property
def image_flipped(self):
base_img_flipped = np.array([[1, 0, 0],
[1, 0, 0],
[1, 1, 0]], dtype=np.uint8)
return base_img_flipped[:, :, np.newaxis]
@property
def heatmaps(self):
heatmaps_arr = np.float32([
[0.00, 0.50, 0.75],
[0.00, 0.50, 0.75],
[0.75, 0.75, 0.75],
])
return HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def heatmaps_flipped(self):
heatmaps_arr = np.float32([
[0.75, 0.50, 0.00],
[0.75, 0.50, 0.00],
[0.75, 0.75, 0.75],
])
return HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def segmaps(self):
segmaps_arr = np.int32([
[0, 1, 2],
[0, 1, 2],
[2, 2, 2],
])
return SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def segmaps_flipped(self):
segmaps_arr = np.int32([
[2, 1, 0],
[2, 1, 0],
[2, 2, 2],
])
return SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=0),
ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
@property
def kpsoi_flipped(self):
kps = [ia.Keypoint(x=3-0, y=0),
ia.Keypoint(x=3-1, y=1),
ia.Keypoint(x=3-2, y=2)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
@property
def psoi(self):
polygons = [ia.Polygon([(0, 0), (2, 0), (2, 2)])]
return [ia.PolygonsOnImage(polygons, shape=self.image.shape)]
@property
def psoi_flipped(self):
polygons = [ia.Polygon([(3-0, 0), (3-2, 0), (3-2, 2)])]
return [ia.PolygonsOnImage(polygons, shape=self.image.shape)]
@property
def lsoi(self):
ls = [ia.LineString([(0, 0), (2, 0), (2, 2)])]
return [ia.LineStringsOnImage(ls, shape=self.image.shape)]
@property
def lsoi_flipped(self):
ls = [ia.LineString([(3-0, 0), (3-2, 0), (3-2, 2)])]
return [ia.LineStringsOnImage(ls, shape=self.image.shape)]
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)]
@property
def bbsoi_flipped(self):
# note that x1 and x2 were inverted (otherwise would be x1>x2)
bbs = [ia.BoundingBox(x1=3-2, y1=1, x2=3-0, y2=3)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)]
def create_aug(self, *args, **kwargs):
return iaa.Fliplr(*args, **kwargs)
def create_arr(self, value, dtype):
arr = np.zeros((3, 3), dtype=dtype)
arr[0, 0] = value
return arr
def create_arr_flipped(self, value, dtype):
arr = np.zeros((3, 3), dtype=dtype)
arr[0, 2] = value
return arr
class TestFlipud(_TestFliplrAndFlipudBase, unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
base_img = np.array([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]], dtype=np.uint8)
return base_img[:, :, np.newaxis]
@property
def image_flipped(self):
base_img_flipped = np.array([[0, 1, 1],
[0, 0, 1],
[0, 0, 1]], dtype=np.uint8)
return base_img_flipped[:, :, np.newaxis]
@property
def heatmaps(self):
heatmaps_arr = np.float32([
[0.00, 0.50, 0.75],
[0.00, 0.50, 0.75],
[0.75, 0.75, 0.75],
])
return HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def heatmaps_flipped(self):
heatmaps_arr = np.float32([
[0.75, 0.75, 0.75],
[0.00, 0.50, 0.75],
[0.00, 0.50, 0.75],
])
return HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def segmaps(self):
segmaps_arr = np.int32([
[0, 1, 2],
[0, 1, 2],
[2, 2, 2],
])
return SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def segmaps_flipped(self):
segmaps_arr = np.int32([
[2, 2, 2],
[0, 1, 2],
[0, 1, 2],
])
return SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=0),
ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
@property
def kpsoi_flipped(self):
kps = [ia.Keypoint(x=0, y=3-0),
ia.Keypoint(x=1, y=3-1),
ia.Keypoint(x=2, y=3-2)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
@property
def psoi(self):
polygons = [ia.Polygon([(0, 0), (2, 0), (2, 2)])]
return [ia.PolygonsOnImage(polygons, shape=self.image.shape)]
@property
def psoi_flipped(self):
polygons = [ia.Polygon([(0, 3-0), (2, 3-0), (2, 3-2)])]
return [ia.PolygonsOnImage(polygons, shape=self.image.shape)]
@property
def lsoi(self):
ls = [ia.LineString([(0, 0), (2, 0), (2, 2)])]
return [ia.LineStringsOnImage(ls, shape=self.image.shape)]
@property
def lsoi_flipped(self):
ls = [ia.LineString([(0, 3-0), (2, 3-0), (2, 3-2)])]
return [ia.LineStringsOnImage(ls, shape=self.image.shape)]
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)]
@property
def bbsoi_flipped(self):
# note that y1 and y2 were inverted (otherwise would be y1>y2)
bbs = [ia.BoundingBox(x1=0, y1=3-3, x2=2, y2=3-1)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)]
def create_aug(self, *args, **kwargs):
return iaa.Flipud(*args, **kwargs)
def create_arr(self, value, dtype):
arr = np.zeros((3, 3), dtype=dtype)
arr[0, 0] = value
return arr
def create_arr_flipped(self, value, dtype):
arr = np.zeros((3, 3), dtype=dtype)
arr[2, 0] = value
return arr
class Test_fliplr(unittest.TestCase):
def setUp(self):
reseed()
@mock.patch("imgaug.augmenters.flip._fliplr_sliced")
@mock.patch("imgaug.augmenters.flip._fliplr_cv2")
def test__fliplr_cv2_called_mocked(self, mock_cv2, mock_sliced):
for dtype in ["uint8", "uint16", "int8", "int16"]:
mock_cv2.reset_mock()
mock_sliced.reset_mock()
arr = np.zeros((1, 1), dtype=dtype)
_ = fliplib.fliplr(arr)
mock_cv2.assert_called_once_with(arr)
assert mock_sliced.call_count == 0
@mock.patch("imgaug.augmenters.flip._fliplr_sliced")
@mock.patch("imgaug.augmenters.flip._fliplr_cv2")
def test__fliplr_sliced_called_mocked(self, mock_cv2, mock_sliced):
try:
f128 = [np.dtype("float128").name]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = [
"bool",
"uint32", "uint64",
"int32", "int64",
"float16", "float32", "float64"
] + f128
for dtype in dtypes:
mock_cv2.reset_mock()
mock_sliced.reset_mock()
arr = np.zeros((1, 1), dtype=dtype)
_ = fliplib.fliplr(arr)
assert mock_cv2.call_count == 0
mock_sliced.assert_called_once_with(arr)
def test__fliplr_cv2_2d(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_cv2, None)
def test__fliplr_cv2_3d_single_channel(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_cv2, 1)
def test__fliplr_cv2_3d_three_channels(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_cv2, 3)
def test__fliplr_cv2_3d_four_channels(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_cv2, 4)
def test__fliplr_sliced_2d(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_sliced, None)
def test__fliplr_sliced_3d_single_channel(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_sliced, 1)
def test__fliplr_sliced_3d_three_channels(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_sliced, 3)
def test__fliplr_sliced_3d_four_channels(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_sliced, 4)
def test__fliplr_sliced_3d_513_channels(self):
self._test__fliplr_subfunc_n_channels(fliplib._fliplr_sliced, 513)
@classmethod
def _test__fliplr_subfunc_n_channels(cls, func, nb_channels):
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[10, 11, 12, 13]
])
if nb_channels is not None:
arr = np.tile(arr[..., np.newaxis], (1, 1, nb_channels))
for c in sm.xrange(nb_channels):
arr[..., c] += c
arr_flipped = func(arr)
expected = np.uint8([
[3, 2, 1, 0],
[7, 6, 5, 4],
[13, 12, 11, 10]
])
if nb_channels is not None:
expected = np.tile(expected[..., np.newaxis], (1, 1, nb_channels))
for c in sm.xrange(nb_channels):
expected[..., c] += c
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == arr.shape
assert np.array_equal(arr_flipped, expected)
def test_zero_height_arr_cv2(self):
arr = np.zeros((0, 4, 1), dtype=np.uint8)
arr_flipped = fliplib._fliplr_cv2(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (0, 4, 1)
def test_zero_width_arr_cv2(self):
arr = np.zeros((4, 0, 1), dtype=np.uint8)
arr_flipped = fliplib._fliplr_cv2(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (4, 0, 1)
def test_zero_channels_arr_cv2(self):
arr = np.zeros((4, 1, 0), dtype=np.uint8)
arr_flipped = fliplib._fliplr_cv2(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (4, 1, 0)
def test_513_channels_arr_cv2(self):
arr = np.zeros((1, 2, 513), dtype=np.uint8)
arr[:, 0, :] = 0
arr[:, 1, :] = 255
arr[0, 0, 0] = 1
arr[0, 1, 0] = 254
arr[0, 0, 512] = 2
arr[0, 1, 512] = 253
arr_flipped = fliplib._fliplr_cv2(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (1, 2, 513)
assert arr_flipped[0, 1, 0] == 1
assert arr_flipped[0, 0, 0] == 254
assert arr_flipped[0, 1, 512] == 2
assert arr_flipped[0, 0, 512] == 253
assert np.all(arr_flipped[0, 0, 1:-2] == 255)
assert np.all(arr_flipped[0, 1, 1:-2] == 0)
def test_zero_height_arr_sliced(self):
arr = np.zeros((0, 4, 1), dtype=np.uint8)
arr_flipped = fliplib._fliplr_sliced(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (0, 4, 1)
def test_zero_width_arr_sliced(self):
arr = np.zeros((4, 0, 1), dtype=np.uint8)
arr_flipped = fliplib._fliplr_sliced(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (4, 0, 1)
def test_zero_channels_arr_sliced(self):
arr = np.zeros((4, 1, 0), dtype=np.uint8)
arr_flipped = fliplib._fliplr_sliced(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (4, 1, 0)
def test_513_channels_arr_sliced(self):
arr = np.zeros((1, 2, 513), dtype=np.uint8)
arr[:, 0, :] = 0
arr[:, 1, :] = 255
arr[0, 0, 0] = 1
arr[0, 1, 0] = 254
arr[0, 0, 512] = 2
arr[0, 1, 512] = 253
arr_flipped = fliplib._fliplr_sliced(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (1, 2, 513)
assert arr_flipped[0, 1, 0] == 1
assert arr_flipped[0, 0, 0] == 254
assert arr_flipped[0, 1, 512] == 2
assert arr_flipped[0, 0, 512] == 253
assert np.all(arr_flipped[0, 0, 1:-2] == 255)
assert np.all(arr_flipped[0, 1, 1:-2] == 0)
def test_bool_faithful(self):
arr = np.array([[False, False, True]], dtype=bool)
arr_flipped = fliplib.fliplr(arr)
expected = np.array([[True, False, False]], dtype=bool)
assert arr_flipped.dtype.name == "bool"
assert arr_flipped.shape == (1, 3)
assert np.array_equal(arr_flipped, expected)
def test_uint_int_faithful(self):
dts = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dt in dts:
with self.subTest(dtype=dt):
dt = np.dtype(dt)
minv, center, maxv = iadt.get_value_range_of_dtype(dt)
center = int(center)
arr = np.array([[minv, center, maxv]], dtype=dt)
arr_flipped = fliplib.fliplr(arr)
expected = np.array([[maxv, center, minv]], dtype=dt)
assert arr_flipped.dtype.name == dt.name
assert arr_flipped.shape == (1, 3)
assert np.array_equal(arr_flipped, expected)
def test_float_faithful_to_min_max(self):
try:
f128 = [np.dtype("float128").name]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
for dt in dtypes:
with self.subTest(dtype=dt):
dt = np.dtype(dt)
minv, center, maxv = iadt.get_value_range_of_dtype(dt)
center = int(center)
atol = 1e-4 if dt.name == "float16" else 1e-8
arr = np.array([[minv, center, maxv]], dtype=dt)
arr_flipped = fliplib.fliplr(arr)
expected = np.array([[maxv, center, minv]], dtype=dt)
assert arr_flipped.dtype.name == dt.name
assert arr_flipped.shape == (1, 3)
assert np.allclose(arr_flipped, expected, rtol=0, atol=atol)
def test_float_faithful_to_large_values(self):
try:
f128 = [np.dtype("float128").name]
except TypeError:
f128 = [] # float128 not known by user system
dts = ["float16", "float32", "float64"] + f128
values = [
[0.01, 0.1, 1.0, 10.0**1, 10.0**2], # float16
[0.01, 0.1, 1.0, 10.0**1, 10.0**2, 10.0**4, 10.0**6], # float32
[0.01, 0.1, 1.0, 10.0**1, 10.0**2, 10.0**6, 10.0**10], # float64
[0.01, 0.1, 1.0, 10.0**1, 10.0**2, 10.0**7, 10.0**11], # float128
]
for dt, values_i in zip(dts, values):
for value in values_i:
with self.subTest(dtype=dt, value=value):
dt = np.dtype(dt)
minv, center, maxv = -value, 0.0, value
atol = 1e-4 if dt.name == "float16" else 1e-8
arr = np.array([[minv, center, maxv]], dtype=dt)
arr_flipped = fliplib.fliplr(arr)
expected = np.array([[maxv, center, minv]], dtype=dt)
assert arr_flipped.dtype.name == dt.name
assert arr_flipped.shape == (1, 3)
assert np.allclose(arr_flipped, expected, rtol=0, atol=atol)
class Test_flipud(unittest.TestCase):
def setUp(self):
reseed()
def test__flipud_2d(self):
self._test__flipud_subfunc_n_channels(fliplib.flipud, None)
def test__flipud_3d_single_channel(self):
self._test__flipud_subfunc_n_channels(fliplib.flipud, 1)
def test__flipud_3d_three_channels(self):
self._test__flipud_subfunc_n_channels(fliplib.flipud, 3)
def test__flipud_3d_four_channels(self):
self._test__flipud_subfunc_n_channels(fliplib.flipud, 4)
@classmethod
def _test__flipud_subfunc_n_channels(cls, func, nb_channels):
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[10, 11, 12, 13]
])
if nb_channels is not None:
arr = np.tile(arr[..., np.newaxis], (1, 1, nb_channels))
for c in sm.xrange(nb_channels):
arr[..., c] += c
arr_flipped = func(arr)
expected = np.uint8([
[10, 11, 12, 13],
[4, 5, 6, 7],
[0, 1, 2, 3]
])
if nb_channels is not None:
expected = np.tile(expected[..., np.newaxis], (1, 1, nb_channels))
for c in sm.xrange(nb_channels):
expected[..., c] += c
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == arr.shape
assert np.array_equal(arr_flipped, expected)
def test_zero_width_arr(self):
arr = np.zeros((4, 0, 1), dtype=np.uint8)
arr_flipped = fliplib.flipud(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (4, 0, 1)
def test_zero_height_arr(self):
arr = np.zeros((0, 4, 1), dtype=np.uint8)
arr_flipped = fliplib.flipud(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (0, 4, 1)
def test_zero_channels_arr(self):
arr = np.zeros((4, 1, 0), dtype=np.uint8)
arr_flipped = fliplib.flipud(arr)
assert arr_flipped.dtype.name == "uint8"
assert arr_flipped.shape == (4, 1, 0)
def test_bool_faithful(self):
arr = np.array([[False], [False], [True]], dtype=bool)
arr_flipped = fliplib.flipud(arr)
expected = np.array([[True], [False], [False]], dtype=bool)
assert arr_flipped.dtype.name == "bool"
assert arr_flipped.shape == (3, 1)
assert np.array_equal(arr_flipped, expected)
def test_uint_int_faithful(self):
dts = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dt in dts:
with self.subTest(dtype=dt):
dt = np.dtype(dt)
minv, center, maxv = iadt.get_value_range_of_dtype(dt)
center = int(center)
arr = np.array([[minv], [center], [maxv]], dtype=dt)
arr_flipped = fliplib.flipud(arr)
expected = np.array([[maxv], [center], [minv]], dtype=dt)
assert arr_flipped.dtype.name == dt.name
assert arr_flipped.shape == (3, 1)
assert np.array_equal(arr_flipped, expected)
def test_float_faithful_to_min_max(self):
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dts = ["float16", "float32", "float64"] + f128
for dt in dts:
with self.subTest(dtype=dt):
dt = np.dtype(dt)
minv, center, maxv = iadt.get_value_range_of_dtype(dt)
center = int(center)
atol = 1e-4 if dt.name == "float16" else 1e-8
arr = np.array([[minv], [center], [maxv]], dtype=dt)
arr_flipped = fliplib.flipud(arr)
expected = np.array([[maxv], [center], [minv]], dtype=dt)
assert arr_flipped.dtype.name == dt.name
assert arr_flipped.shape == (3, 1)
assert np.allclose(arr_flipped, expected, rtol=0, atol=atol)
def test_float_faithful_to_large_values(self):
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dts = ["float16", "float32", "float64"] + f128
values = [
[0.01, 0.1, 1.0, 10.0**1, 10.0**2], # float16
[0.01, 0.1, 1.0, 10.0**1, 10.0**2, 10.0**4, 10.0**6], # float32
[0.01, 0.1, 1.0, 10.0**1, 10.0**2, 10.0**6, 10.0**10], # float64
[0.01, 0.1, 1.0, 10.0**1, 10.0**2, 10.0**7, 10.0**11], # float128
]
for dt, values_i in zip(dts, values):
for value in values_i:
with self.subTest(dtype=dt, value=value):
dt = np.dtype(dt)
minv, center, maxv = -value, 0.0, value
atol = 1e-4 if dt.name == "float16" else 1e-8
arr = np.array([[minv], [center], [maxv]], dtype=dt)
arr_flipped = fliplib.flipud(arr)
expected = np.array([[maxv], [center], [minv]], dtype=dt)
assert arr_flipped.dtype.name == dt.name
assert arr_flipped.shape == (3, 1)
assert np.allclose(arr_flipped, expected, rtol=0, atol=atol)
================================================
FILE: test/augmenters/test_geometric.py
================================================
from __future__ import print_function, division, absolute_import
import itertools
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
import skimage.morphology
import cv2
import imgaug as ia
from imgaug import random as iarandom
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug.testutils import (
array_equal_lists, keypoints_equal, reseed, assert_cbaois_equal,
runtest_pickleable_uint8_img, assertWarns, is_parameter_instance)
from imgaug.augmentables.heatmaps import HeatmapsOnImage
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
import imgaug.augmenters.geometric as geometriclib
def _assert_same_min_max(observed, actual):
assert np.isclose(observed.min_value, actual.min_value, rtol=0, atol=1e-6)
assert np.isclose(observed.max_value, actual.max_value, rtol=0, atol=1e-6)
def _assert_same_shape(observed, actual):
assert observed.shape == actual.shape
# TODO add more tests for Affine .mode
# TODO add more tests for Affine shear
class TestAffine(unittest.TestCase):
def test_get_parameters(self):
aug = iaa.Affine(scale=1, translate_px=2, rotate=3, shear=4,
order=1, cval=0, mode="constant", backend="cv2",
fit_output=True)
params = aug.get_parameters()
assert is_parameter_instance(params[0], iap.Deterministic) # scale
assert isinstance(params[1], tuple) # translate
assert is_parameter_instance(params[2], iap.Deterministic) # rotate
assert is_parameter_instance(params[3], iap.Deterministic) # shear
assert params[0].value == 1 # scale
assert params[1][0].value == 2 # translate
assert params[2].value == 3 # rotate
assert params[3].value == 4 # shear
assert params[4].value == 1 # order
assert params[5].value == 0 # cval
assert params[6].value == "constant" # mode
assert params[7] == "cv2" # backend
assert params[8] is True # fit_output
class TestAffine___init__(unittest.TestCase):
def test___init___scale_is_stochastic_parameter(self):
aug = iaa.Affine(scale=iap.Uniform(0.7, 0.9))
assert is_parameter_instance(aug.scale, iap.Uniform)
assert is_parameter_instance(aug.scale.a, iap.Deterministic)
assert is_parameter_instance(aug.scale.b, iap.Deterministic)
assert 0.7 - 1e-8 < aug.scale.a.value < 0.7 + 1e-8
assert 0.9 - 1e-8 < aug.scale.b.value < 0.9 + 1e-8
def test___init___translate_percent_is_stochastic_parameter(self):
aug = iaa.Affine(translate_percent=iap.Uniform(0.7, 0.9))
assert isinstance(aug.translate, tuple)
assert is_parameter_instance(aug.translate[0], iap.Uniform)
assert is_parameter_instance(aug.translate[0].a, iap.Deterministic)
assert is_parameter_instance(aug.translate[0].b, iap.Deterministic)
assert 0.7 - 1e-8 < aug.translate[0].a.value < 0.7 + 1e-8
assert 0.9 - 1e-8 < aug.translate[0].b.value < 0.9 + 1e-8
assert aug.translate[1] is None
assert aug.translate[2] == "percent"
def test___init___translate_px_is_stochastic_parameter(self):
aug = iaa.Affine(translate_px=iap.DiscreteUniform(1, 10))
assert isinstance(aug.translate, tuple)
assert is_parameter_instance(aug.translate[0], iap.DiscreteUniform)
assert is_parameter_instance(aug.translate[0].a, iap.Deterministic)
assert is_parameter_instance(aug.translate[0].b, iap.Deterministic)
assert aug.translate[0].a.value == 1
assert aug.translate[0].b.value == 10
assert aug.translate[1] is None
assert aug.translate[2] == "px"
def test___init___rotate_is_stochastic_parameter(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=iap.Uniform(10, 20),
shear=0)
assert is_parameter_instance(aug.rotate, iap.Uniform)
assert is_parameter_instance(aug.rotate.a, iap.Deterministic)
assert aug.rotate.a.value == 10
assert is_parameter_instance(aug.rotate.b, iap.Deterministic)
assert aug.rotate.b.value == 20
def test___init___shear_is_stochastic_parameter(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=0,
shear=iap.Uniform(10, 20))
assert is_parameter_instance(aug.shear, iap.Uniform)
assert is_parameter_instance(aug.shear.a, iap.Deterministic)
assert aug.shear.a.value == 10
assert is_parameter_instance(aug.shear.b, iap.Deterministic)
assert aug.shear.b.value == 20
def test___init___cval_is_all(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=ia.ALL)
assert is_parameter_instance(aug.cval, iap.Uniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 0
assert aug.cval.b.value == 255
def test___init___cval_is_stochastic_parameter(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=iap.DiscreteUniform(1, 5))
assert is_parameter_instance(aug.cval, iap.DiscreteUniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 1
assert aug.cval.b.value == 5
def test___init___mode_is_all(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0, mode=ia.ALL)
assert is_parameter_instance(aug.mode, iap.Choice)
def test___init___mode_is_string(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0, mode="edge")
assert is_parameter_instance(aug.mode, iap.Deterministic)
assert aug.mode.value == "edge"
def test___init___mode_is_list(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0, mode=["constant", "edge"])
assert is_parameter_instance(aug.mode, iap.Choice)
assert (
len(aug.mode.a) == 2
and "constant" in aug.mode.a
and "edge" in aug.mode.a)
def test___init___mode_is_stochastic_parameter(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0, mode=iap.Choice(["constant", "edge"]))
assert is_parameter_instance(aug.mode, iap.Choice)
assert (
len(aug.mode.a) == 2
and "constant" in aug.mode.a
and "edge" in aug.mode.a)
def test___init___fit_output_is_true(self):
aug = iaa.Affine(fit_output=True)
assert aug.fit_output is True
# ------------
# exceptions for bad inputs
# ------------
def test___init___bad_datatype_for_scale_fails(self):
with self.assertRaises(Exception):
_ = iaa.Affine(scale=False)
def test___init___bad_datatype_for_translate_px_fails(self):
with self.assertRaises(Exception):
_ = iaa.Affine(translate_px=False)
def test___init___bad_datatype_for_translate_percent_fails(self):
with self.assertRaises(Exception):
_ = iaa.Affine(translate_percent=False)
def test___init___bad_datatype_for_rotate_fails(self):
with self.assertRaises(Exception):
_ = iaa.Affine(scale=1.0, translate_px=0, rotate=False, shear=0,
cval=0)
def test___init___bad_datatype_for_shear_fails(self):
with self.assertRaises(Exception):
_ = iaa.Affine(scale=1.0, translate_px=0, rotate=0, shear=False,
cval=0)
def test___init___bad_datatype_for_cval_fails(self):
with self.assertRaises(Exception):
_ = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=None)
def test___init___bad_datatype_for_mode_fails(self):
with self.assertRaises(Exception):
_ = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0, mode=False)
def test___init___bad_datatype_for_order_fails(self):
# bad order datatype in case of backend=cv2
with self.assertRaises(Exception):
_ = iaa.Affine(backend="cv2", order="test")
def test___init___nonexistent_order_for_cv2_fails(self):
# non-existent order in case of backend=cv2
with self.assertRaises(AssertionError):
_ = iaa.Affine(backend="cv2", order=-1)
# TODO add test with multiple images
class TestAffine_noop(unittest.TestCase):
def setUp(self):
reseed()
@property
def base_img(self):
base_img = np.array([[0, 0, 0],
[0, 255, 0],
[0, 0, 0]], dtype=np.uint8)
return base_img[:, :, np.newaxis]
@property
def images(self):
return np.array([self.base_img])
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
return [ia.KeypointsOnImage(kps, shape=self.base_img.shape)]
@property
def psoi(self):
polygons = [ia.Polygon([(0, 0), (2, 0), (2, 2)])]
return [ia.PolygonsOnImage(polygons, shape=self.base_img.shape)]
@property
def lsoi(self):
ls = [ia.LineString([(0, 0), (2, 0), (2, 2)])]
return [ia.LineStringsOnImage(ls, shape=self.base_img.shape)]
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.base_img.shape)]
def test_image_noop(self):
# no translation/scale/rotate/shear, shouldnt change nothing
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=0, shear=0)
observed = aug.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_image_noop__deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_image_noop__list(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=0, shear=0)
observed = aug.augment_images([self.base_img])
expected = [self.base_img]
assert array_equal_lists(observed, expected)
def test_image_noop__list_and_deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.base_img])
expected = [self.base_img]
assert array_equal_lists(observed, expected)
def test_keypoints_noop(self):
self._test_cba_noop("augment_keypoints", self.kpsoi, False)
def test_keypoints_noop__deterministic(self):
self._test_cba_noop("augment_keypoints", self.kpsoi, True)
def test_polygons_noop(self):
self._test_cba_noop("augment_polygons", self.psoi, False)
def test_polygons_noop__deterministic(self):
self._test_cba_noop("augment_polygons", self.psoi, True)
def test_line_strings_noop(self):
self._test_cba_noop("augment_line_strings", self.lsoi, False)
def test_line_strings_noop__deterministic(self):
self._test_cba_noop("augment_line_strings", self.lsoi, True)
def test_bounding_boxes_noop(self):
self._test_cba_noop("augment_bounding_boxes", self.bbsoi, False)
def test_bounding_boxes_noop__deterministic(self):
self._test_cba_noop("augment_bounding_boxes", self.bbsoi, True)
@classmethod
def _test_cba_noop(cls, augf_name, cbaoi, deterministic):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=0, shear=0)
if deterministic:
aug = aug.to_deterministic()
observed = getattr(aug, augf_name)(cbaoi)
expected = cbaoi
assert_cbaois_equal(observed, expected)
# TODO add test with multiple images
class TestAffine_scale(unittest.TestCase):
def setUp(self):
reseed()
# ---------------------
# scale: zoom in
# ---------------------
@property
def base_img(self):
base_img = np.array([[0, 0, 0],
[0, 255, 0],
[0, 0, 0]], dtype=np.uint8)
return base_img[:, :, np.newaxis]
@property
def images(self):
return np.array([self.base_img])
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
return [ia.KeypointsOnImage(kps, shape=self.base_img.shape)]
def kpsoi_scaled(self, scale_y, scale_x):
coords = np.array([
[0, 0],
[1, 1],
[2, 2]
], dtype=np.float32)
coords_scaled = self._scale_coordinates(coords, scale_y, scale_x)
return [ia.KeypointsOnImage.from_xy_array(
coords_scaled,
shape=self.base_img.shape)]
@property
def psoi(self):
polys = [ia.Polygon([(0, 0), (0, 2), (2, 2)])]
return [ia.PolygonsOnImage(polys, shape=self.base_img.shape)]
def psoi_scaled(self, scale_y, scale_x):
coords = np.array([
[0, 0],
[0, 2],
[2, 2]
], dtype=np.float32)
coords_scaled = self._scale_coordinates(coords, scale_y, scale_x)
return [ia.PolygonsOnImage(
[ia.Polygon(coords_scaled)],
shape=self.base_img.shape)]
@property
def lsoi(self):
ls = [ia.LineString([(0, 0), (0, 2), (2, 2)])]
return [ia.LineStringsOnImage(ls, shape=self.base_img.shape)]
def lsoi_scaled(self, scale_y, scale_x):
coords = np.array([
[0, 0],
[0, 2],
[2, 2]
], dtype=np.float32)
coords_scaled = self._scale_coordinates(coords, scale_y, scale_x)
return [ia.LineStringsOnImage(
[ia.LineString(coords_scaled)],
shape=self.base_img.shape)]
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.base_img.shape)]
def bbsoi_scaled(self, scale_y, scale_x):
coords = np.array([
[0, 1],
[2, 3]
], dtype=np.float32)
coords_scaled = self._scale_coordinates(coords, scale_y, scale_x)
return [ia.BoundingBoxesOnImage.from_xyxy_array(
coords_scaled.reshape((1, 4)),
shape=self.base_img.shape)]
def _scale_coordinates(self, coords, scale_y, scale_x):
height, width = self.base_img.shape[0:2]
coords_scaled = []
for x, y in coords:
# the additional +0.5 and -0.5 here makes up for the shift factor
# used in the affine matrix generation
offset = 0.0
x_centered = x - width/2 + offset
y_centered = y - height/2 + offset
x_new = x_centered * scale_x + width/2 - offset
y_new = y_centered * scale_y + height/2 - offset
coords_scaled.append((x_new, y_new))
return np.float32(coords_scaled)
@property
def scale_zoom_in_outer_pixels(self):
base_img = self.base_img
outer_pixels = ([], [])
for i in sm.xrange(base_img.shape[0]):
for j in sm.xrange(base_img.shape[1]):
if i != j:
outer_pixels[0].append(i)
outer_pixels[1].append(j)
return outer_pixels
def test_image_scale_zoom_in(self):
aug = iaa.Affine(scale=1.75, translate_px=0, rotate=0, shear=0)
observed = aug.augment_images(self.images)
outer_pixels = self.scale_zoom_in_outer_pixels
assert observed[0][1, 1] > 250
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 20).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 150).all()
def test_image_scale_zoom_in__deterministic(self):
aug = iaa.Affine(scale=1.75, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
outer_pixels = self.scale_zoom_in_outer_pixels
assert observed[0][1, 1] > 250
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 20).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 150).all()
def test_image_scale_zoom_in__list(self):
aug = iaa.Affine(scale=1.75, translate_px=0, rotate=0, shear=0)
observed = aug.augment_images([self.base_img])
outer_pixels = self.scale_zoom_in_outer_pixels
assert observed[0][1, 1] > 250
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 20).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 150).all()
def test_image_scale_zoom_in__list_and_deterministic(self):
aug = iaa.Affine(scale=1.75, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.base_img])
outer_pixels = self.scale_zoom_in_outer_pixels
assert observed[0][1, 1] > 250
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 20).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 150).all()
def test_keypoints_scale_zoom_in(self):
self._test_cba_scale(
"augment_keypoints", 1.75,
self.kpsoi, self.kpsoi_scaled(1.75, 1.75), False)
def test_keypoints_scale_zoom_in__deterministic(self):
self._test_cba_scale(
"augment_keypoints", 1.75,
self.kpsoi, self.kpsoi_scaled(1.75, 1.75), True)
def test_polygons_scale_zoom_in(self):
self._test_cba_scale(
"augment_polygons", 1.75,
self.psoi, self.psoi_scaled(1.75, 1.75), False)
def test_polygons_scale_zoom_in__deterministic(self):
self._test_cba_scale(
"augment_polygons", 1.75,
self.psoi, self.psoi_scaled(1.75, 1.75), True)
def test_line_strings_scale_zoom_in(self):
self._test_cba_scale(
"augment_line_strings", 1.75,
self.lsoi, self.lsoi_scaled(1.75, 1.75), False)
def test_line_strings_scale_zoom_in__deterministic(self):
self._test_cba_scale(
"augment_line_strings", 1.75,
self.lsoi, self.lsoi_scaled(1.75, 1.75), True)
def test_bounding_boxes_scale_zoom_in(self):
self._test_cba_scale(
"augment_bounding_boxes", 1.75,
self.bbsoi, self.bbsoi_scaled(1.75, 1.75), False)
def test_bounding_boxes_scale_zoom_in__deterministic(self):
self._test_cba_scale(
"augment_bounding_boxes", 1.75,
self.bbsoi, self.bbsoi_scaled(1.75, 1.75), True)
@classmethod
def _test_cba_scale(cls, augf_name, scale, cbaoi, cbaoi_scaled,
deterministic):
aug = iaa.Affine(scale=scale, translate_px=0, rotate=0, shear=0)
if deterministic:
aug = aug.to_deterministic()
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi_scaled)
# ---------------------
# scale: zoom in only on x axis
# ---------------------
def test_image_scale_zoom_in_only_x_axis(self):
aug = iaa.Affine(scale={"x": 1.75, "y": 1.0},
translate_px=0, rotate=0, shear=0)
observed = aug.augment_images(self.images)
assert observed[0][1, 1] > 250
assert (observed[0][[1, 1], [0, 2]] > 20).all()
assert (observed[0][[1, 1], [0, 2]] < 150).all()
assert (observed[0][0, :] < 5).all()
assert (observed[0][2, :] < 5).all()
def test_image_scale_zoom_in_only_x_axis__deterministic(self):
aug = iaa.Affine(scale={"x": 1.75, "y": 1.0},
translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert observed[0][1, 1] > 250
assert (observed[0][[1, 1], [0, 2]] > 20).all()
assert (observed[0][[1, 1], [0, 2]] < 150).all()
assert (observed[0][0, :] < 5).all()
assert (observed[0][2, :] < 5).all()
def test_image_scale_zoom_in_only_x_axis__list(self):
aug = iaa.Affine(scale={"x": 1.75, "y": 1.0},
translate_px=0, rotate=0, shear=0)
observed = aug.augment_images([self.base_img])
assert observed[0][1, 1] > 250
assert (observed[0][[1, 1], [0, 2]] > 20).all()
assert (observed[0][[1, 1], [0, 2]] < 150).all()
assert (observed[0][0, :] < 5).all()
assert (observed[0][2, :] < 5).all()
def test_image_scale_zoom_in_only_x_axis__deterministic_and_list(self):
aug = iaa.Affine(scale={"x": 1.75, "y": 1.0},
translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.base_img])
assert observed[0][1, 1] > 250
assert (observed[0][[1, 1], [0, 2]] > 20).all()
assert (observed[0][[1, 1], [0, 2]] < 150).all()
assert (observed[0][0, :] < 5).all()
assert (observed[0][2, :] < 5).all()
def test_keypoints_scale_zoom_in_only_x_axis(self):
self._test_cba_scale(
"augment_keypoints", {"y": 1.0, "x": 1.75}, self.kpsoi,
self.kpsoi_scaled(1.0, 1.75), False)
def test_keypoints_scale_zoom_in_only_x_axis__deterministic(self):
self._test_cba_scale(
"augment_keypoints", {"y": 1.0, "x": 1.75}, self.kpsoi,
self.kpsoi_scaled(1.0, 1.75), True)
def test_polygons_scale_zoom_in_only_x_axis(self):
self._test_cba_scale(
"augment_polygons", {"y": 1.0, "x": 1.75}, self.psoi,
self.psoi_scaled(1.0, 1.75), False)
def test_polygons_scale_zoom_in_only_x_axis__deterministic(self):
self._test_cba_scale(
"augment_polygons", {"y": 1.0, "x": 1.75}, self.psoi,
self.psoi_scaled(1.0, 1.75), True)
def test_line_strings_scale_zoom_in_only_x_axis(self):
self._test_cba_scale(
"augment_line_strings", {"y": 1.0, "x": 1.75}, self.lsoi,
self.lsoi_scaled(1.0, 1.75), False)
def test_line_strings_scale_zoom_in_only_x_axis__deterministic(self):
self._test_cba_scale(
"augment_line_strings", {"y": 1.0, "x": 1.75}, self.lsoi,
self.lsoi_scaled(1.0, 1.75), True)
def test_bounding_boxes_scale_zoom_in_only_x_axis(self):
self._test_cba_scale(
"augment_bounding_boxes", {"y": 1.0, "x": 1.75}, self.bbsoi,
self.bbsoi_scaled(1.0, 1.75), False)
def test_bounding_boxes_scale_zoom_in_only_x_axis__deterministic(self):
self._test_cba_scale(
"augment_bounding_boxes", {"y": 1.0, "x": 1.75}, self.bbsoi,
self.bbsoi_scaled(1.0, 1.75), True)
# ---------------------
# scale: zoom in only on y axis
# ---------------------
def test_image_scale_zoom_in_only_y_axis(self):
aug = iaa.Affine(scale={"x": 1.0, "y": 1.75},
translate_px=0, rotate=0, shear=0)
observed = aug.augment_images(self.images)
assert observed[0][1, 1] > 250
assert (observed[0][[0, 2], [1, 1]] > 20).all()
assert (observed[0][[0, 2], [1, 1]] < 150).all()
assert (observed[0][:, 0] < 5).all()
assert (observed[0][:, 2] < 5).all()
def test_image_scale_zoom_in_only_y_axis__deterministic(self):
aug = iaa.Affine(scale={"x": 1.0, "y": 1.75},
translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert observed[0][1, 1] > 250
assert (observed[0][[0, 2], [1, 1]] > 20).all()
assert (observed[0][[0, 2], [1, 1]] < 150).all()
assert (observed[0][:, 0] < 5).all()
assert (observed[0][:, 2] < 5).all()
def test_image_scale_zoom_in_only_y_axis__list(self):
aug = iaa.Affine(scale={"x": 1.0, "y": 1.75},
translate_px=0, rotate=0, shear=0)
observed = aug.augment_images([self.base_img])
assert observed[0][1, 1] > 250
assert (observed[0][[0, 2], [1, 1]] > 20).all()
assert (observed[0][[0, 2], [1, 1]] < 150).all()
assert (observed[0][:, 0] < 5).all()
assert (observed[0][:, 2] < 5).all()
def test_image_scale_zoom_in_only_y_axis__deterministic_and_list(self):
aug = iaa.Affine(scale={"x": 1.0, "y": 1.75},
translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.base_img])
assert observed[0][1, 1] > 250
assert (observed[0][[0, 2], [1, 1]] > 20).all()
assert (observed[0][[0, 2], [1, 1]] < 150).all()
assert (observed[0][:, 0] < 5).all()
assert (observed[0][:, 2] < 5).all()
def test_keypoints_scale_zoom_in_only_y_axis(self):
self._test_cba_scale(
"augment_keypoints", {"y": 1.75, "x": 1.0}, self.kpsoi,
self.kpsoi_scaled(1.75, 1.0), False)
def test_keypoints_scale_zoom_in_only_y_axis__deterministic(self):
self._test_cba_scale(
"augment_keypoints", {"y": 1.75, "x": 1.0}, self.kpsoi,
self.kpsoi_scaled(1.75, 1.0), True)
def test_polygons_scale_zoom_in_only_y_axis(self):
self._test_cba_scale(
"augment_polygons", {"y": 1.75, "x": 1.0}, self.psoi,
self.psoi_scaled(1.75, 1.0), False)
def test_polygons_scale_zoom_in_only_y_axis__deterministic(self):
self._test_cba_scale(
"augment_polygons", {"y": 1.75, "x": 1.0}, self.psoi,
self.psoi_scaled(1.75, 1.0), True)
def test_line_strings_scale_zoom_in_only_y_axis(self):
self._test_cba_scale(
"augment_polygons", {"y": 1.75, "x": 1.0}, self.psoi,
self.psoi_scaled(1.75, 1.0), False)
def test_line_strings_scale_zoom_in_only_y_axis__deterministic(self):
self._test_cba_scale(
"augment_line_strings", {"y": 1.75, "x": 1.0}, self.lsoi,
self.lsoi_scaled(1.75, 1.0), True)
def test_bounding_boxes_scale_zoom_in_only_y_axis(self):
self._test_cba_scale(
"augment_bounding_boxes", {"y": 1.75, "x": 1.0}, self.bbsoi,
self.bbsoi_scaled(1.75, 1.0), False)
def test_bounding_boxes_scale_zoom_in_only_y_axis__deterministic(self):
self._test_cba_scale(
"augment_bounding_boxes", {"y": 1.75, "x": 1.0}, self.bbsoi,
self.bbsoi_scaled(1.75, 1.0), True)
# ---------------------
# scale: zoom out
# ---------------------
# these tests use a 4x4 area of all 255, which is zoomed out to a 4x4 area
# in which the center 2x2 area is 255
# zoom in should probably be adapted to this style
# no separate tests here for x/y axis, should work fine if zoom in works
# with that
@property
def scale_zoom_out_base_img(self):
return np.ones((4, 4, 1), dtype=np.uint8) * 255
@property
def scale_zoom_out_images(self):
return np.array([self.scale_zoom_out_base_img])
@property
def scale_zoom_out_outer_pixels(self):
outer_pixels = ([], [])
for y in sm.xrange(4):
xs = sm.xrange(4) if y in [0, 3] else [0, 3]
for x in xs:
outer_pixels[0].append(y)
outer_pixels[1].append(x)
return outer_pixels
@property
def scale_zoom_out_inner_pixels(self):
return [1, 1, 2, 2], [1, 2, 1, 2]
@property
def scale_zoom_out_kpsoi(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=3, y=0),
ia.Keypoint(x=0, y=3), ia.Keypoint(x=3, y=3)]
return [ia.KeypointsOnImage(kps,
shape=self.scale_zoom_out_base_img.shape)]
@property
def scale_zoom_out_kpsoi_aug(self):
kps_aug = [ia.Keypoint(x=0.765, y=0.765),
ia.Keypoint(x=2.235, y=0.765),
ia.Keypoint(x=0.765, y=2.235),
ia.Keypoint(x=2.235, y=2.235)]
return [ia.KeypointsOnImage(kps_aug,
shape=self.scale_zoom_out_base_img.shape)]
def test_image_scale_zoom_out(self):
aug = iaa.Affine(scale=0.49, translate_px=0, rotate=0, shear=0)
observed = aug.augment_images(self.scale_zoom_out_images)
outer_pixels = self.scale_zoom_out_outer_pixels
inner_pixels = self.scale_zoom_out_inner_pixels
assert (observed[0][outer_pixels] < 25).all()
assert (observed[0][inner_pixels] > 200).all()
def test_image_scale_zoom_out__deterministic(self):
aug = iaa.Affine(scale=0.49, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.scale_zoom_out_images)
outer_pixels = self.scale_zoom_out_outer_pixels
inner_pixels = self.scale_zoom_out_inner_pixels
assert (observed[0][outer_pixels] < 25).all()
assert (observed[0][inner_pixels] > 200).all()
def test_image_scale_zoom_out__list(self):
aug = iaa.Affine(scale=0.49, translate_px=0, rotate=0, shear=0)
observed = aug.augment_images([self.scale_zoom_out_base_img])
outer_pixels = self.scale_zoom_out_outer_pixels
inner_pixels = self.scale_zoom_out_inner_pixels
assert (observed[0][outer_pixels] < 25).all()
assert (observed[0][inner_pixels] > 200).all()
def test_image_scale_zoom_out__list_and_deterministic(self):
aug = iaa.Affine(scale=0.49, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.scale_zoom_out_base_img])
outer_pixels = self.scale_zoom_out_outer_pixels
inner_pixels = self.scale_zoom_out_inner_pixels
assert (observed[0][outer_pixels] < 25).all()
assert (observed[0][inner_pixels] > 200).all()
def test_keypoints_scale_zoom_out(self):
self._test_cba_scale(
"augment_keypoints", 0.49, self.kpsoi,
self.kpsoi_scaled(0.49, 0.49), False)
def test_keypoints_scale_zoom_out__deterministic(self):
self._test_cba_scale(
"augment_keypoints", 0.49, self.kpsoi,
self.kpsoi_scaled(0.49, 0.49), True)
def test_polygons_scale_zoom_out(self):
self._test_cba_scale(
"augment_polygons", 0.49, self.psoi,
self.psoi_scaled(0.49, 0.49), False)
def test_polygons_scale_zoom_out__deterministic(self):
self._test_cba_scale(
"augment_polygons", 0.49, self.psoi,
self.psoi_scaled(0.49, 0.49), True)
def test_line_strings_scale_zoom_out(self):
self._test_cba_scale(
"augment_line_strings", 0.49, self.lsoi,
self.lsoi_scaled(0.49, 0.49), False)
def test_line_strings_scale_zoom_out__deterministic(self):
self._test_cba_scale(
"augment_line_strings", 0.49, self.lsoi,
self.lsoi_scaled(0.49, 0.49), True)
def test_bounding_boxes_scale_zoom_out(self):
self._test_cba_scale(
"augment_bounding_boxes", 0.49, self.bbsoi,
self.bbsoi_scaled(0.49, 0.49), False)
def test_bounding_boxes_scale_zoom_out__deterministic(self):
self._test_cba_scale(
"augment_bounding_boxes", 0.49, self.bbsoi,
self.bbsoi_scaled(0.49, 0.49), True)
# ---------------------
# scale: x and y axis are both tuples
# ---------------------
def test_image_x_and_y_axis_are_tuples(self):
aug = iaa.Affine(scale={"x": (0.5, 1.5), "y": (0.5, 1.5)},
translate_px=0, rotate=0, shear=0)
image = np.array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 2, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]], dtype=np.uint8) * 100
image = image[:, :, np.newaxis]
images = np.array([image])
last_aug = None
nb_changed_aug = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
if i == 0:
last_aug = observed_aug
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
last_aug = observed_aug
assert nb_changed_aug >= int(nb_iterations * 0.8)
def test_image_x_and_y_axis_are_tuples__deterministic(self):
aug = iaa.Affine(scale={"x": (0.5, 1.5), "y": (0.5, 1.5)},
translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
image = np.array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 2, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]], dtype=np.uint8) * 100
image = image[:, :, np.newaxis]
images = np.array([image])
last_aug_det = None
nb_changed_aug_det = 0
nb_iterations = 10
for i in sm.xrange(nb_iterations):
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug_det = observed_aug_det
assert nb_changed_aug_det == 0
# ------------
# alignment
# TODO add alignment tests for: BBs, Polys, LS
# ------------
def test_keypoint_alignment(self):
image = np.zeros((100, 100), dtype=np.uint8)
image[40-1:40+2, 40-1:40+2] = 255
image[40-1:40+2, 60-1:60+2] = 255
kps = [ia.Keypoint(x=40, y=40), ia.Keypoint(x=60, y=40)]
kpsoi = ia.KeypointsOnImage(kps, shape=image.shape)
images = [image, image, image]
kpsois = [kpsoi.deepcopy(),
ia.KeypointsOnImage([], shape=image.shape),
kpsoi.deepcopy()]
aug = iaa.Affine(scale=[0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.4, 1.5,
1.6, 1.7],
order=0)
for iter in sm.xrange(40):
images_aug, kpsois_aug = aug(images=images, keypoints=kpsois)
assert kpsois_aug[1].empty
for i in [0, 2]:
image_aug = images_aug[i]
kpsoi_aug = kpsois_aug[i]
for kp in kpsoi_aug.keypoints:
value = image_aug[int(kp.y), int(kp.x)]
assert value > 200
# ------------
# make sure that polygons stay valid upon extreme scaling
# ------------
def test_polygons_stay_valid_when_using_extreme_scalings(self):
scales = [1e-4, 1e-2, 1e2, 1e4]
backends = ["auto", "cv2", "skimage"]
orders = [0, 1, 3]
gen = itertools.product(scales, backends, orders)
for scale, backend, order in gen:
with self.subTest(scale=scale, backend=backend, order=order):
aug = iaa.Affine(scale=scale, order=order)
psoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (10, 0), (5, 5)])],
shape=(10, 10))
psoi_aug = aug.augment_polygons(psoi)
poly = psoi_aug.polygons[0]
ext = poly.exterior
assert poly.is_valid
assert ext[0][0] < ext[2][0] < ext[1][0]
assert ext[0][1] < ext[2][1]
assert np.allclose(ext[0][1], ext[1][1])
class TestAffine_translate(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
return np.uint8([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
])[:, :, np.newaxis]
@property
def image_1px_right(self):
return np.uint8([
[0, 0, 0],
[0, 0, 1],
[0, 0, 0]
])[:, :, np.newaxis]
@property
def image_1px_bottom(self):
return np.uint8([
[0, 0, 0],
[0, 0, 0],
[0, 1, 0]
])[:, :, np.newaxis]
@property
def images(self):
return np.array([self.image])
@property
def images_1px_right(self):
return np.array([self.image_1px_right])
@property
def images_1px_bottom(self):
return np.array([self.image_1px_bottom])
@property
def kpsoi(self):
kps = [ia.Keypoint(x=1, y=1)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
@property
def kpsoi_1px_right(self):
kps = [ia.Keypoint(x=2, y=1)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
@property
def kpsoi_1px_bottom(self):
kps = [ia.Keypoint(x=1, y=2)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
@property
def psoi(self):
polys = [ia.Polygon([(0, 0), (2, 0), (2, 2)])]
return [ia.PolygonsOnImage(polys, shape=self.image.shape)]
@property
def psoi_1px_right(self):
polys = [ia.Polygon([(0+1, 0), (2+1, 0), (2+1, 2)])]
return [ia.PolygonsOnImage(polys, shape=self.image.shape)]
@property
def psoi_1px_bottom(self):
polys = [ia.Polygon([(0, 0+1), (2, 0+1), (2, 2+1)])]
return [ia.PolygonsOnImage(polys, shape=self.image.shape)]
@property
def lsoi(self):
ls = [ia.LineString([(0, 0), (2, 0), (2, 2)])]
return [ia.LineStringsOnImage(ls, shape=self.image.shape)]
@property
def lsoi_1px_right(self):
ls = [ia.LineString([(0+1, 0), (2+1, 0), (2+1, 2)])]
return [ia.LineStringsOnImage(ls, shape=self.image.shape)]
@property
def lsoi_1px_bottom(self):
ls = [ia.LineString([(0, 0+1), (2, 0+1), (2, 2+1)])]
return [ia.LineStringsOnImage(ls, shape=self.image.shape)]
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)]
@property
def bbsoi_1px_right(self):
bbs = [ia.BoundingBox(x1=0+1, y1=1, x2=2+1, y2=3)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)]
@property
def bbsoi_1px_bottom(self):
bbs = [ia.BoundingBox(x1=0, y1=1+1, x2=2, y2=3+1)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)]
# ---------------------
# translate: move one pixel to the right
# ---------------------
def test_image_translate_1px_right(self):
# move one pixel to the right
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0)
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
def test_image_translate_1px_right__deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
def test_image_translate_1px_right__list(self):
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0)
observed = aug.augment_images([self.image])
assert array_equal_lists(observed, [self.image_1px_right])
def test_image_translate_1px_right__list_and_deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.image])
assert array_equal_lists(observed, [self.image_1px_right])
def test_keypoints_translate_1px_right(self):
self._test_cba_translate_px(
"augment_keypoints", {"x": 1, "y": 0},
self.kpsoi, self.kpsoi_1px_right, False)
def test_keypoints_translate_1px_right__deterministic(self):
self._test_cba_translate_px(
"augment_keypoints", {"x": 1, "y": 0},
self.kpsoi, self.kpsoi_1px_right, True)
def test_polygons_translate_1px_right(self):
self._test_cba_translate_px(
"augment_polygons", {"x": 1, "y": 0},
self.psoi, self.psoi_1px_right, False)
def test_polygons_translate_1px_right__deterministic(self):
self._test_cba_translate_px(
"augment_polygons", {"x": 1, "y": 0},
self.psoi, self.psoi_1px_right, True)
def test_line_strings_translate_1px_right(self):
self._test_cba_translate_px(
"augment_line_strings", {"x": 1, "y": 0},
self.lsoi, self.lsoi_1px_right, False)
def test_line_strings_translate_1px_right__deterministic(self):
self._test_cba_translate_px(
"augment_line_strings", {"x": 1, "y": 0},
self.lsoi, self.lsoi_1px_right, True)
def test_bounding_boxes_translate_1px_right(self):
self._test_cba_translate_px(
"augment_bounding_boxes", {"x": 1, "y": 0},
self.bbsoi, self.bbsoi_1px_right, False)
def test_bounding_boxes_translate_1px_right__deterministic(self):
self._test_cba_translate_px(
"augment_bounding_boxes", {"x": 1, "y": 0},
self.bbsoi, self.bbsoi_1px_right, True)
@classmethod
def _test_cba_translate_px(cls, augf_name, px, cbaoi, cbaoi_translated,
deterministic):
aug = iaa.Affine(scale=1.0, translate_px=px, rotate=0, shear=0)
if deterministic:
aug = aug.to_deterministic()
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi_translated)
def test_image_translate_1px_right_skimage(self):
# move one pixel to the right
# with backend = skimage
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0, backend="skimage")
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
def test_image_translate_1px_right_skimage_order_all(self):
# move one pixel to the right
# with backend = skimage, order=ALL
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0, backend="skimage", order=ia.ALL)
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
def test_image_translate_1px_right_skimage_order_is_list(self):
# move one pixel to the right
# with backend = skimage, order=list
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0, backend="skimage", order=[0, 1, 3])
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
def test_image_translate_1px_right_cv2_order_is_list(self):
# move one pixel to the right
# with backend = cv2, order=list
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0, backend="cv2", order=[0, 1, 3])
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
def test_image_translate_1px_right_cv2_order_is_stoch_param(self):
# move one pixel to the right
# with backend = cv2, order=StochasticParameter
aug = iaa.Affine(scale=1.0, translate_px={"x": 1, "y": 0}, rotate=0,
shear=0, backend="cv2", order=iap.Choice([0, 1, 3]))
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
# ---------------------
# translate: move one pixel to the bottom
# ---------------------
def test_image_translate_1px_bottom(self):
aug = iaa.Affine(scale=1.0, translate_px={"x": 0, "y": 1}, rotate=0,
shear=0)
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_bottom)
def test_image_translate_1px_bottom__deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px={"x": 0, "y": 1}, rotate=0,
shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_bottom)
def test_image_translate_1px_bottom__list(self):
aug = iaa.Affine(scale=1.0, translate_px={"x": 0, "y": 1}, rotate=0,
shear=0)
observed = aug.augment_images([self.image])
assert array_equal_lists(observed, [self.image_1px_bottom])
def test_image_translate_1px_bottom__list_and_deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px={"x": 0, "y": 1}, rotate=0,
shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.image])
assert array_equal_lists(observed, [self.image_1px_bottom])
def test_keypoints_translate_1px_bottom(self):
self._test_cba_translate_px(
"augment_keypoints", {"x": 0, "y": 1},
self.kpsoi, self.kpsoi_1px_bottom, False)
def test_keypoints_translate_1px_bottom__deterministic(self):
self._test_cba_translate_px(
"augment_keypoints", {"x": 0, "y": 1},
self.kpsoi, self.kpsoi_1px_bottom, True)
def test_polygons_translate_1px_bottom(self):
self._test_cba_translate_px(
"augment_polygons", {"x": 0, "y": 1},
self.psoi, self.psoi_1px_bottom, False)
def test_polygons_translate_1px_bottom__deterministic(self):
self._test_cba_translate_px(
"augment_polygons", {"x": 0, "y": 1},
self.psoi, self.psoi_1px_bottom, True)
def test_line_strings_translate_1px_bottom(self):
self._test_cba_translate_px(
"augment_line_strings", {"x": 0, "y": 1},
self.lsoi, self.lsoi_1px_bottom, False)
def test_line_strings_translate_1px_bottom__deterministic(self):
self._test_cba_translate_px(
"augment_line_strings", {"x": 0, "y": 1},
self.lsoi, self.lsoi_1px_bottom, True)
def test_bounding_boxes_translate_1px_bottom(self):
self._test_cba_translate_px(
"augment_bounding_boxes", {"x": 0, "y": 1},
self.bbsoi, self.bbsoi_1px_bottom, False)
def test_bounding_boxes_translate_1px_bottom__deterministic(self):
self._test_cba_translate_px(
"augment_bounding_boxes", {"x": 0, "y": 1},
self.bbsoi, self.bbsoi_1px_bottom, True)
# ---------------------
# translate: fraction of the image size (towards the right)
# ---------------------
def test_image_translate_33percent_right(self):
aug = iaa.Affine(scale=1.0, translate_percent={"x": 0.3333, "y": 0},
rotate=0, shear=0)
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
def test_image_translate_33percent_right__deterministic(self):
aug = iaa.Affine(scale=1.0, translate_percent={"x": 0.3333, "y": 0},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_right)
def test_image_translate_33percent_right__list(self):
aug = iaa.Affine(scale=1.0, translate_percent={"x": 0.3333, "y": 0},
rotate=0, shear=0)
observed = aug.augment_images([self.image])
assert array_equal_lists(observed, [self.image_1px_right])
def test_image_translate_33percent_right__list_and_deterministic(self):
aug = iaa.Affine(scale=1.0, translate_percent={"x": 0.3333, "y": 0},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.image])
assert array_equal_lists(observed, [self.image_1px_right])
def test_keypoints_translate_33percent_right(self):
self._test_cba_translate_percent(
"augment_keypoints", {"x": 0.3333, "y": 0},
self.kpsoi, self.kpsoi_1px_right, False)
def test_keypoints_translate_33percent_right__deterministic(self):
self._test_cba_translate_percent(
"augment_keypoints", {"x": 0.3333, "y": 0},
self.kpsoi, self.kpsoi_1px_right, True)
def test_polygons_translate_33percent_right(self):
self._test_cba_translate_percent(
"augment_polygons", {"x": 0.3333, "y": 0},
self.psoi, self.psoi_1px_right, False)
def test_polygons_translate_33percent_right__deterministic(self):
self._test_cba_translate_percent(
"augment_polygons", {"x": 0.3333, "y": 0},
self.psoi, self.psoi_1px_right, True)
def test_line_strings_translate_33percent_right(self):
self._test_cba_translate_percent(
"augment_line_strings", {"x": 0.3333, "y": 0},
self.lsoi, self.lsoi_1px_right, False)
def test_line_strings_translate_33percent_right__deterministic(self):
self._test_cba_translate_percent(
"augment_line_strings", {"x": 0.3333, "y": 0},
self.lsoi, self.lsoi_1px_right, True)
def test_bounding_boxes_translate_33percent_right(self):
self._test_cba_translate_percent(
"augment_bounding_boxes", {"x": 0.3333, "y": 0},
self.bbsoi, self.bbsoi_1px_right, False)
def test_bounding_boxes_translate_33percent_right__deterministic(self):
self._test_cba_translate_percent(
"augment_bounding_boxes", {"x": 0.3333, "y": 0},
self.bbsoi, self.bbsoi_1px_right, True)
def test_keypoints_with_continuous_param_results_in_absolute_shift(self):
# This test ensures that t ~ uniform(a, b) results in a translation
# by t pixels and not t%
# see issue #505
# use iap.Uniform() here to ensure that is really a float value that
# is sampled and not accidentally DisceteUniform
aug = iaa.Affine(translate_px=iap.Uniform(10, 20))
kps = [ia.Keypoint(x=10, y=10)]
kpsoi = ia.KeypointsOnImage(kps, shape=(1000, 1000))
for _ in np.arange(5):
kpsoi_aug = aug.augment_keypoints(kpsoi)
kp_aug = kpsoi_aug.keypoints[0]
assert 10+10 <= kp_aug.x <= 10+20
assert 10+10 <= kp_aug.y <= 10+20
@classmethod
def _test_cba_translate_percent(cls, augf_name, percent, cbaoi,
cbaoi_translated, deterministic):
aug = iaa.Affine(scale=1.0, translate_percent=percent, rotate=0,
shear=0)
if deterministic:
aug = aug.to_deterministic()
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi_translated, max_distance=1e-3)
# ---------------------
# translate: fraction of the image size (towards the bottom)
# ---------------------
def test_image_translate_33percent_bottom(self):
# move 33% (one pixel) to the bottom
aug = iaa.Affine(scale=1.0, translate_percent={"x": 0, "y": 0.3333},
rotate=0, shear=0)
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_bottom)
def test_image_translate_33percent_bottom__deterministic(self):
aug = iaa.Affine(scale=1.0, translate_percent={"x": 0, "y": 0.3333},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert np.array_equal(observed, self.images_1px_bottom)
def test_image_translate_33percent_bottom__list(self):
aug = iaa.Affine(scale=1.0, translate_percent={"x": 0, "y": 0.3333},
rotate=0, shear=0)
observed = aug.augment_images([self.image])
assert array_equal_lists(observed, [self.image_1px_bottom])
def test_image_translate_33percent_bottom__list_and_deterministic(self):
aug = iaa.Affine(scale=1.0, translate_percent={"x": 0, "y": 0.3333},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.image])
assert array_equal_lists(observed, [self.image_1px_bottom])
def test_keypoints_translate_33percent_bottom(self):
self._test_cba_translate_percent(
"augment_keypoints", {"x": 0, "y": 0.3333},
self.kpsoi, self.kpsoi_1px_bottom, False)
def test_keypoints_translate_33percent_bottom__deterministic(self):
self._test_cba_translate_percent(
"augment_keypoints", {"x": 0, "y": 0.3333},
self.kpsoi, self.kpsoi_1px_bottom, True)
def test_polygons_translate_33percent_bottom(self):
self._test_cba_translate_percent(
"augment_polygons", {"x": 0, "y": 0.3333},
self.psoi, self.psoi_1px_bottom, False)
def test_polygons_translate_33percent_bottom__deterministic(self):
self._test_cba_translate_percent(
"augment_polygons", {"x": 0, "y": 0.3333},
self.psoi, self.psoi_1px_bottom, True)
def test_line_strings_translate_33percent_bottom(self):
self._test_cba_translate_percent(
"augment_line_strings", {"x": 0, "y": 0.3333},
self.lsoi, self.lsoi_1px_bottom, False)
def test_line_strings_translate_33percent_bottom__deterministic(self):
self._test_cba_translate_percent(
"augment_line_strings", {"x": 0, "y": 0.3333},
self.lsoi, self.lsoi_1px_bottom, True)
def test_bounding_boxes_translate_33percent_bottom(self):
self._test_cba_translate_percent(
"augment_bounding_boxes", {"x": 0, "y": 0.3333},
self.bbsoi, self.bbsoi_1px_bottom, False)
def test_bounding_boxes_translate_33percent_bottom__deterministic(self):
self._test_cba_translate_percent(
"augment_bounding_boxes", {"x": 0, "y": 0.3333},
self.bbsoi, self.bbsoi_1px_bottom, True)
# ---------------------
# translate: axiswise uniform distributions
# ---------------------
def test_image_translate_by_axiswise_uniform_distributions(self):
# 0-1px to left/right and 0-1px to top/bottom
aug = iaa.Affine(scale=1.0, translate_px={"x": (-1, 1), "y": (-1, 1)},
rotate=0, shear=0)
last_aug = None
nb_changed_aug = 0
nb_iterations = 1000
centers_aug = self.image.astype(np.int32) * 0
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(self.images)
if i == 0:
last_aug = observed_aug
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
last_aug = observed_aug
assert len(observed_aug[0].nonzero()[0]) == 1
centers_aug += (observed_aug[0] > 0)
assert nb_changed_aug >= int(nb_iterations * 0.7)
assert (centers_aug > int(nb_iterations * (1/9 * 0.6))).all()
assert (centers_aug < int(nb_iterations * (1/9 * 1.4))).all()
def test_image_translate_by_axiswise_uniform_distributions__det(self):
# 0-1px to left/right and 0-1px to top/bottom
aug = iaa.Affine(scale=1.0, translate_px={"x": (-1, 1), "y": (-1, 1)},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
last_aug_det = None
nb_changed_aug_det = 0
nb_iterations = 10
centers_aug_det = self.image.astype(np.int32) * 0
for i in sm.xrange(nb_iterations):
observed_aug_det = aug_det.augment_images(self.images)
if i == 0:
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug_det = observed_aug_det
assert len(observed_aug_det[0].nonzero()[0]) == 1
centers_aug_det += (observed_aug_det[0] > 0)
assert nb_changed_aug_det == 0
# ---------------------
# translate heatmaps
# ---------------------
@property
def heatmaps(self):
return ia.HeatmapsOnImage(
np.float32([
[0.0, 0.5, 0.75],
[0.0, 0.5, 0.75],
[0.75, 0.75, 0.75],
]),
shape=(3, 3, 3)
)
@property
def heatmaps_1px_right(self):
return ia.HeatmapsOnImage(
np.float32([
[0.0, 0.0, 0.5],
[0.0, 0.0, 0.5],
[0.0, 0.75, 0.75],
]),
shape=(3, 3, 3)
)
def test_heatmaps_translate_1px_right(self):
aug = iaa.Affine(translate_px={"x": 1})
observed = aug.augment_heatmaps([self.heatmaps])[0]
_assert_same_shape(observed, self.heatmaps)
_assert_same_min_max(observed, self.heatmaps)
assert np.array_equal(observed.get_arr(),
self.heatmaps_1px_right.get_arr())
def test_heatmaps_translate_1px_right_should_ignore_cval(self):
# should still use mode=constant cval=0 even when other settings chosen
aug = iaa.Affine(translate_px={"x": 1}, cval=255)
observed = aug.augment_heatmaps([self.heatmaps])[0]
_assert_same_shape(observed, self.heatmaps)
_assert_same_min_max(observed, self.heatmaps)
assert np.array_equal(observed.get_arr(),
self.heatmaps_1px_right.get_arr())
def test_heatmaps_translate_1px_right_should_ignore_mode(self):
aug = iaa.Affine(translate_px={"x": 1}, mode="edge", cval=255)
observed = aug.augment_heatmaps([self.heatmaps])[0]
_assert_same_shape(observed, self.heatmaps)
_assert_same_min_max(observed, self.heatmaps)
assert np.array_equal(observed.get_arr(),
self.heatmaps_1px_right.get_arr())
# ---------------------
# translate segmaps
# ---------------------
@property
def segmaps(self):
return SegmentationMapsOnImage(
np.int32([
[0, 1, 2],
[0, 1, 2],
[2, 2, 2],
]),
shape=(3, 3, 3)
)
@property
def segmaps_1px_right(self):
return SegmentationMapsOnImage(
np.int32([
[0, 0, 1],
[0, 0, 1],
[0, 2, 2],
]),
shape=(3, 3, 3)
)
def test_segmaps_translate_1px_right(self):
aug = iaa.Affine(translate_px={"x": 1})
observed = aug.augment_segmentation_maps([self.segmaps])[0]
_assert_same_shape(observed, self.segmaps)
assert np.array_equal(observed.get_arr(),
self.segmaps_1px_right.get_arr())
def test_segmaps_translate_1px_right_should_ignore_cval(self):
# should still use mode=constant cval=0 even when other settings chosen
aug = iaa.Affine(translate_px={"x": 1}, cval=255)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
_assert_same_shape(observed, self.segmaps)
assert np.array_equal(observed.get_arr(),
self.segmaps_1px_right.get_arr())
def test_segmaps_translate_1px_right_should_ignore_mode(self):
aug = iaa.Affine(translate_px={"x": 1}, mode="edge", cval=255)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
_assert_same_shape(observed, self.segmaps)
assert np.array_equal(observed.get_arr(),
self.segmaps_1px_right.get_arr())
class TestAffine_rotate(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
return np.uint8([
[0, 0, 0],
[255, 255, 255],
[0, 0, 0]
])[:, :, np.newaxis]
@property
def image_rot90(self):
return np.uint8([
[0, 255, 0],
[0, 255, 0],
[0, 255, 0]
])[:, :, np.newaxis]
@property
def images(self):
return np.array([self.image])
@property
def images_rot90(self):
return np.array([self.image_rot90])
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=1), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=1)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
@property
def kpsoi_rot90(self):
kps = [ia.Keypoint(x=3-1, y=0), ia.Keypoint(x=3-1, y=1),
ia.Keypoint(x=3-1, y=2)]
return [ia.KeypointsOnImage(kps, shape=self.image_rot90.shape)]
@property
def psoi(self):
polys = [ia.Polygon([(0, 0), (3, 0), (3, 3)])]
return [ia.PolygonsOnImage(polys, shape=self.image.shape)]
@property
def psoi_rot90(self):
polys = [ia.Polygon([(3-0, 0), (3-0, 3), (3-3, 3)])]
return [ia.PolygonsOnImage(polys, shape=self.image_rot90.shape)]
@property
def lsoi(self):
ls = [ia.LineString([(0, 0), (3, 0), (3, 3)])]
return [ia.LineStringsOnImage(ls, shape=self.image.shape)]
@property
def lsoi_rot90(self):
ls = [ia.LineString([(3-0, 0), (3-0, 3), (3-3, 3)])]
return [ia.LineStringsOnImage(ls, shape=self.image_rot90.shape)]
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)]
@property
def bbsoi_rot90(self):
bbs = [ia.BoundingBox(x1=0, y1=0, x2=2, y2=2)]
return [ia.BoundingBoxesOnImage(bbs, shape=self.image_rot90.shape)]
def test_image_rot90(self):
# rotate by 90 degrees
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=90, shear=0)
observed = aug.augment_images(self.images)
observed[observed >= 100] = 255
observed[observed < 100] = 0
assert np.array_equal(observed, self.images_rot90)
def test_image_rot90__deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=90, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
observed[observed >= 100] = 255
observed[observed < 100] = 0
assert np.array_equal(observed, self.images_rot90)
def test_image_rot90__list(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=90, shear=0)
observed = aug.augment_images([self.image])
observed[0][observed[0] >= 100] = 255
observed[0][observed[0] < 100] = 0
assert array_equal_lists(observed, [self.image_rot90])
def test_image_rot90__list_and_deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=90, shear=0)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.image])
observed[0][observed[0] >= 100] = 255
observed[0][observed[0] < 100] = 0
assert array_equal_lists(observed, [self.image_rot90])
def test_keypoints_rot90(self):
self._test_cba_rotate(
"augment_keypoints", 90, self.kpsoi, self.kpsoi_rot90, False)
def test_keypoints_rot90__deterministic(self):
self._test_cba_rotate(
"augment_keypoints", 90, self.kpsoi, self.kpsoi_rot90, True)
def test_polygons_rot90(self):
self._test_cba_rotate(
"augment_polygons", 90, self.psoi, self.psoi_rot90, False)
def test_polygons_rot90__deterministic(self):
self._test_cba_rotate(
"augment_polygons", 90, self.psoi, self.psoi_rot90, True)
def test_line_strings_rot90(self):
self._test_cba_rotate(
"augment_line_strings", 90, self.lsoi, self.lsoi_rot90, False)
def test_line_strings_rot90__deterministic(self):
self._test_cba_rotate(
"augment_line_strings", 90, self.lsoi, self.lsoi_rot90, True)
def test_bounding_boxes_rot90(self):
self._test_cba_rotate(
"augment_bounding_boxes", 90, self.bbsoi, self.bbsoi_rot90, False)
def test_bounding_boxes_rot90__deterministic(self):
self._test_cba_rotate(
"augment_bounding_boxes", 90, self.bbsoi, self.bbsoi_rot90, True)
@classmethod
def _test_cba_rotate(cls, augf_name, rotate, cbaoi,
cbaoi_rotated, deterministic):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=rotate,
shear=0)
if deterministic:
aug = aug.to_deterministic()
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi_rotated)
def test_image_rotate_is_tuple_0_to_364_deg(self):
# random rotation 0-364 degrees
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=(0, 364), shear=0)
last_aug = None
nb_changed_aug = 0
nb_iterations = 1000
pixels_sums_aug = self.image.astype(np.int32) * 0
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(self.images)
if i == 0:
last_aug = observed_aug
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
last_aug = observed_aug
pixels_sums_aug += (observed_aug[0] > 100)
assert nb_changed_aug >= int(nb_iterations * 0.9)
# center pixel, should always be white when rotating line around center
assert pixels_sums_aug[1, 1] > (nb_iterations * 0.98)
assert pixels_sums_aug[1, 1] < (nb_iterations * 1.02)
# outer pixels, should sometimes be white
# the values here had to be set quite tolerant, the middle pixels at
# top/left/bottom/right get more activation than expected
outer_pixels = ([0, 0, 0, 1, 1, 2, 2, 2],
[0, 1, 2, 0, 2, 0, 1, 2])
assert (
pixels_sums_aug[outer_pixels] > int(nb_iterations * (2/8 * 0.4))
).all()
assert (
pixels_sums_aug[outer_pixels] < int(nb_iterations * (2/8 * 2.0))
).all()
def test_image_rotate_is_tuple_0_to_364_deg__deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px=0, rotate=(0, 364), shear=0)
aug_det = aug.to_deterministic()
last_aug_det = None
nb_changed_aug_det = 0
nb_iterations = 10
pixels_sums_aug_det = self.image.astype(np.int32) * 0
for i in sm.xrange(nb_iterations):
observed_aug_det = aug_det.augment_images(self.images)
if i == 0:
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug_det = observed_aug_det
pixels_sums_aug_det += (observed_aug_det[0] > 100)
assert nb_changed_aug_det == 0
# center pixel, should always be white when rotating line around center
assert pixels_sums_aug_det[1, 1] > (nb_iterations * 0.98)
assert pixels_sums_aug_det[1, 1] < (nb_iterations * 1.02)
def test_alignment_between_images_and_heatmaps_for_fixed_rot(self):
# measure alignment between images and heatmaps when rotating
for backend in ["auto", "cv2", "skimage"]:
aug = iaa.Affine(rotate=45, backend=backend)
image = np.zeros((7, 6), dtype=np.uint8)
image[:, 2:3+1] = 255
hm = ia.HeatmapsOnImage(image.astype(np.float32)/255, shape=(7, 6))
img_aug = aug.augment_image(image)
hm_aug = aug.augment_heatmaps([hm])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = hm_aug.arr_0to1 > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert hm_aug.shape == (7, 6)
assert hm_aug.arr_0to1.shape == (7, 6, 1)
assert (same / img_aug_mask.size) >= 0.95
def test_alignment_between_images_and_smaller_heatmaps_for_fixed_rot(self):
# measure alignment between images and heatmaps when rotating
# here with smaller heatmaps
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=45, backend=backend)
image = np.zeros((56, 48), dtype=np.uint8)
image[:, 16:24+1] = 255
hm = ia.HeatmapsOnImage(
ia.imresize_single_image(
image, (28, 24), interpolation="cubic"
).astype(np.float32)/255,
shape=(56, 48)
)
img_aug = aug.augment_image(image)
hm_aug = aug.augment_heatmaps([hm])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = ia.imresize_single_image(
hm_aug.arr_0to1, img_aug.shape[0:2], interpolation="cubic"
) > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert hm_aug.shape == (56, 48)
assert hm_aug.arr_0to1.shape == (28, 24, 1)
assert (same / img_aug_mask.size) >= 0.9
def test_bounding_boxes_have_expected_shape_after_augmentation(self):
image = np.zeros((100, 100), dtype=np.uint8)
image[20:80, 20:80] = 255
bb = ia.BoundingBox(x1=20, y1=20, x2=80, y2=80)
bbsoi = ia.BoundingBoxesOnImage([bb], shape=image.shape)
for rotate in [10, 20, 40, 80, 120]:
with self.subTest(rotate=rotate):
aug = iaa.Affine(rotate=rotate, order=0)
image_aug, bbsoi_aug = aug(image=image, bounding_boxes=bbsoi)
xx = np.nonzero(np.max(image_aug > 100, axis=0))[0]
yy = np.nonzero(np.max(image_aug > 100, axis=1))[0]
bb_exp_x1 = xx[0]
bb_exp_x2 = xx[-1]
bb_exp_y1 = yy[0]
bb_exp_y2 = yy[-1]
bb_expected = ia.BoundingBox(x1=bb_exp_x1, y1=bb_exp_y1,
x2=bb_exp_x2, y2=bb_exp_y2)
assert bbsoi_aug.bounding_boxes[0].iou(bb_expected) > 0.95
class TestAffine_cval(unittest.TestCase):
@property
def image(self):
return np.ones((3, 3, 1), dtype=np.uint8) * 255
@property
def images(self):
return np.array([self.image])
def test_image_fixed_cval(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=128)
observed = aug.augment_images(self.images)
assert (observed[0] > 128 - 30).all()
assert (observed[0] < 128 + 30).all()
def test_image_fixed_cval__deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=128)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert (observed[0] > 128 - 30).all()
assert (observed[0] < 128 + 30).all()
def test_image_fixed_cval__list(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=128)
observed = aug.augment_images([self.image])
assert (observed[0] > 128 - 30).all()
assert (observed[0] < 128 + 30).all()
def test_image_fixed_cval__list_and_deterministic(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=128)
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.image])
assert (observed[0] > 128 - 30).all()
assert (observed[0] < 128 + 30).all()
def test_image_cval_is_tuple(self):
# random cvals
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=(0, 255))
last_aug = None
nb_changed_aug = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(self.images)
if i == 0:
last_aug = observed_aug
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
last_aug = observed_aug
assert nb_changed_aug >= int(nb_iterations * 0.9)
def test_image_cval_is_tuple__deterministic(self):
# random cvals
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=(0, 255))
aug_det = aug.to_deterministic()
last_aug_det = None
nb_changed_aug_det = 0
nb_iterations = 10
for i in sm.xrange(nb_iterations):
observed_aug_det = aug_det.augment_images(self.images)
if i == 0:
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug_det = observed_aug_det
assert nb_changed_aug_det == 0
def test_float_cval_on_float_image(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0.25)
image = np.full((10, 10, 3), 0.75, dtype=np.float32)
image_aug = aug(image=image)
assert np.allclose(image_aug, 0.25)
def test_float_cval_on_int_image(self):
aug = iaa.Affine(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=2.75)
image = np.full((10, 10, 3), 10, dtype=np.uint8)
image_aug = aug(image=image)
assert np.allclose(image_aug, 2) # cval is casted to int, no rounding
class TestAffine_fit_output(unittest.TestCase):
@property
def image(self):
return np.ones((3, 3, 1), dtype=np.uint8) * 255
@property
def images(self):
return np.array([self.image])
@property
def heatmaps(self):
return ia.HeatmapsOnImage(
np.float32([
[0.0, 0.5, 0.75],
[0.0, 0.5, 0.75],
[0.75, 0.75, 0.75],
]),
shape=(3, 3, 3)
)
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=1), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=1)]
return [ia.KeypointsOnImage(kps, shape=self.image.shape)]
def test_image_translate(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(translate_px=100, fit_output=True,
backend=backend)
observed = aug.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_keypoints_translate(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(translate_px=100, fit_output=True,
backend=backend)
observed = aug.augment_keypoints(self.kpsoi)
expected = self.kpsoi
assert keypoints_equal(observed, expected)
def test_heatmaps_translate(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(translate_px=100, fit_output=True,
backend=backend)
observed = aug.augment_heatmaps([self.heatmaps])[0]
expected = self.heatmaps
assert np.allclose(observed.arr_0to1, expected.arr_0to1)
def test_image_rot45(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=45, fit_output=True,
backend=backend)
img = np.zeros((10, 10), dtype=np.uint8)
img[0:2, 0:2] = 255
img[-2:, 0:2] = 255
img[0:2, -2:] = 255
img[-2:, -2:] = 255
img_aug = aug.augment_image(img)
_labels, nb_labels = skimage.morphology.label(
img_aug > 240, return_num=True, connectivity=2)
assert nb_labels == 4
def test_heatmaps_rot45(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=45, fit_output=True,
backend=backend)
img = np.zeros((10, 10), dtype=np.uint8)
img[0:2, 0:2] = 255
img[-2:, 0:2] = 255
img[0:2, -2:] = 255
img[-2:, -2:] = 255
hm = ia.HeatmapsOnImage(img.astype(np.float32)/255,
shape=(10, 10))
hm_aug = aug.augment_heatmaps([hm])[0]
_labels, nb_labels = skimage.morphology.label(
hm_aug.arr_0to1 > 240/255, return_num=True, connectivity=2)
assert nb_labels == 4
def test_heatmaps_rot45__heatmaps_smaller_than_image(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=45, fit_output=True,
backend=backend)
img = np.zeros((80, 80), dtype=np.uint8)
img[0:5, 0:5] = 255
img[-5:, 0:5] = 255
img[0:5, -5:] = 255
img[-5:, -5:] = 255
hm = HeatmapsOnImage(
ia.imresize_single_image(
img, (40, 40), interpolation="cubic"
).astype(np.float32)/255,
shape=(80, 80)
)
hm_aug = aug.augment_heatmaps([hm])[0]
# these asserts are deactivated because the image size can
# change under fit_output=True
# assert hm_aug.shape == (80, 80)
# assert hm_aug.arr_0to1.shape == (40, 40, 1)
_labels, nb_labels = skimage.morphology.label(
hm_aug.arr_0to1 > 200/255, return_num=True, connectivity=2)
assert nb_labels == 4
def test_image_heatmap_alignment_random_rots(self):
nb_iterations = 50
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
for _ in sm.xrange(nb_iterations):
aug = iaa.Affine(rotate=(0, 364), fit_output=True,
backend=backend)
img = np.zeros((80, 80), dtype=np.uint8)
img[0:5, 0:5] = 255
img[-5:, 0:5] = 255
img[0:5, -5:] = 255
img[-5:, -5:] = 255
hm = HeatmapsOnImage(
img.astype(np.float32)/255,
shape=(80, 80)
)
img_aug = aug.augment_image(img)
hm_aug = aug.augment_heatmaps([hm])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = ia.imresize_single_image(
hm_aug.arr_0to1, img_aug.shape[0:2],
interpolation="cubic"
) > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert (same / img_aug_mask.size) >= 0.95
def test_image_heatmap_alignment_random_rots__hms_smaller_than_img(self):
nb_iterations = 50
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
for _ in sm.xrange(nb_iterations):
aug = iaa.Affine(rotate=(0, 364), fit_output=True,
backend=backend)
img = np.zeros((80, 80), dtype=np.uint8)
img[0:5, 0:5] = 255
img[-5:, 0:5] = 255
img[0:5, -5:] = 255
img[-5:, -5:] = 255
hm = HeatmapsOnImage(
ia.imresize_single_image(
img, (40, 40), interpolation="cubic"
).astype(np.float32)/255,
shape=(80, 80)
)
img_aug = aug.augment_image(img)
hm_aug = aug.augment_heatmaps([hm])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = ia.imresize_single_image(
hm_aug.arr_0to1, img_aug.shape[0:2],
interpolation="cubic"
) > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert (same / img_aug_mask.size) >= 0.95
def test_segmaps_rot45(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=45, fit_output=True,
backend=backend)
img = np.zeros((80, 80), dtype=np.uint8)
img[0:5, 0:5] = 255
img[-5:, 0:5] = 255
img[0:5, -5:] = 255
img[-5:, -5:] = 255
segmap = SegmentationMapsOnImage(
(img > 100).astype(np.int32),
shape=(80, 80)
)
segmap_aug = aug.augment_segmentation_maps([segmap])[0]
# these asserts are deactivated because the image size can
# change under fit_output=True
# assert segmap_aug.shape == (80, 80)
# assert segmap_aug.arr_0to1.shape == (40, 40, 1)
_labels, nb_labels = skimage.morphology.label(
segmap_aug.arr > 0, return_num=True, connectivity=2)
assert nb_labels == 4
def test_segmaps_rot45__segmaps_smaller_than_img(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=45, fit_output=True,
backend=backend)
img = np.zeros((80, 80), dtype=np.uint8)
img[0:5, 0:5] = 255
img[-5:, 0:5] = 255
img[0:5, -5:] = 255
img[-5:, -5:] = 255
segmap = SegmentationMapsOnImage(
(
ia.imresize_single_image(
img, (40, 40), interpolation="cubic"
) > 100
).astype(np.int32),
shape=(80, 80)
)
segmap_aug = aug.augment_segmentation_maps([segmap])[0]
# these asserts are deactivated because the image size can
# change under fit_output=True
# assert segmap_aug.shape == (80, 80)
# assert segmap_aug.arr_0to1.shape == (40, 40, 1)
_labels, nb_labels = skimage.morphology.label(
segmap_aug.arr > 0, return_num=True, connectivity=2)
assert nb_labels == 4
def test_image_segmap_alignment_random_rots(self):
nb_iterations = 50
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
for _ in sm.xrange(nb_iterations):
aug = iaa.Affine(rotate=(0, 364), fit_output=True,
backend=backend)
img = np.zeros((80, 80), dtype=np.uint8)
img[0:5, 0:5] = 255
img[-5:, 0:5] = 255
img[0:5, -5:] = 255
img[-5:, -5:] = 255
segmap = SegmentationMapsOnImage(
(img > 100).astype(np.int32),
shape=(80, 80)
)
img_aug = aug.augment_image(img)
segmap_aug = aug.augment_segmentation_maps([segmap])[0]
img_aug_mask = img_aug > 100
segmap_aug_mask = ia.imresize_single_image(
segmap_aug.arr,
img_aug.shape[0:2],
interpolation="nearest"
) > 0
same = np.sum(img_aug_mask == segmap_aug_mask[:, :, 0])
assert (same / img_aug_mask.size) >= 0.95
def test_image_segmap_alignment_random_rots__sms_smaller_than_img(self):
nb_iterations = 50
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
for _ in sm.xrange(nb_iterations):
aug = iaa.Affine(rotate=(0, 364), fit_output=True,
backend=backend)
img = np.zeros((80, 80), dtype=np.uint8)
img[0:5, 0:5] = 255
img[-5:, 0:5] = 255
img[0:5, -5:] = 255
img[-5:, -5:] = 255
segmap = SegmentationMapsOnImage(
(
ia.imresize_single_image(
img, (40, 40), interpolation="cubic"
) > 100
).astype(np.int32),
shape=(80, 80)
)
img_aug = aug.augment_image(img)
segmap_aug = aug.augment_segmentation_maps([segmap])[0]
img_aug_mask = img_aug > 100
segmap_aug_mask = ia.imresize_single_image(
segmap_aug.arr,
img_aug.shape[0:2],
interpolation="nearest"
) > 0
same = np.sum(img_aug_mask == segmap_aug_mask[:, :, 0])
assert (same / img_aug_mask.size) >= 0.95
def test_keypoints_rot90_without_fit_output(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=90, backend=backend)
kps = ia.KeypointsOnImage([ia.Keypoint(10, 10)],
shape=(100, 200, 3))
kps_aug = aug.augment_keypoints(kps)
assert kps_aug.shape == (100, 200, 3)
assert not np.allclose(
[kps_aug.keypoints[0].x, kps_aug.keypoints[0].y],
[kps.keypoints[0].x, kps.keypoints[0].y],
atol=1e-2, rtol=0)
def test_keypoints_rot90(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=90, fit_output=True, backend=backend)
kps = ia.KeypointsOnImage([ia.Keypoint(10, 10)],
shape=(100, 200, 3))
kps_aug = aug.augment_keypoints(kps)
assert kps_aug.shape == (200, 100, 3)
assert not np.allclose(
[kps_aug.keypoints[0].x, kps_aug.keypoints[0].y],
[kps.keypoints[0].x, kps.keypoints[0].y],
atol=1e-2, rtol=0)
def test_empty_keypoints_rot90(self):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=90, fit_output=True, backend=backend)
kps = ia.KeypointsOnImage([], shape=(100, 200, 3))
kps_aug = aug.augment_keypoints(kps)
assert kps_aug.shape == (200, 100, 3)
assert len(kps_aug.keypoints) == 0
def _test_cbaoi_rot90_without_fit_output(self, cbaoi, augf_name):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
# verify that shape in PolygonsOnImages changes
aug = iaa.Affine(rotate=90, backend=backend)
cbaoi_aug = getattr(aug, augf_name)([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
if isinstance(cbaoi, (ia.PolygonsOnImage,
ia.LineStringsOnImage)):
assert cbaoi_aug_i.shape == cbaoi.shape
assert not cbaoi_aug_i.items[0].coords_almost_equals(
cbaoi.items[0].coords, max_distance=1e-2)
else:
assert_cbaois_equal(cbaoi_aug_i, cbaoi)
def test_polygons_rot90_without_fit_output(self):
psoi = ia.PolygonsOnImage([
ia.Polygon([(10, 10), (20, 10), (20, 20)])
], shape=(100, 200, 3))
self._test_cbaoi_rot90_without_fit_output(psoi, "augment_polygons")
def test_line_strings_rot90_without_fit_output(self):
lsoi = ia.LineStringsOnImage([
ia.LineString([(10, 10), (20, 10), (20, 20), (10, 10)])
], shape=(100, 200, 3))
self._test_cbaoi_rot90_without_fit_output(lsoi, "augment_line_strings")
def _test_cbaoi_rot90(self, cbaoi, expected, augf_name):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=90, fit_output=True, backend=backend)
cbaoi_aug = getattr(aug, augf_name)([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert_cbaois_equal(cbaoi_aug_i, expected)
def test_polygons_rot90(self):
psoi = ia.PolygonsOnImage([
ia.Polygon([(10, 10), (20, 10), (20, 20)])
], shape=(100, 200, 3))
expected = ia.PolygonsOnImage([
ia.Polygon([(100-10-1, 10), (100-10-1, 20), (100-20-1, 20)])
], shape=(200, 100, 3))
self._test_cbaoi_rot90(psoi, expected, "augment_polygons")
def test_line_strings_rot90(self):
lsoi = ia.LineStringsOnImage([
ia.LineString([(10, 10), (20, 10), (20, 20), (10, 10)])
], shape=(100, 200, 3))
expected = ia.LineStringsOnImage([
ia.LineString([(100-10-1, 10), (100-10-1, 20), (100-20-1, 20),
(100-10-1, 10)])
], shape=(200, 100, 3))
self._test_cbaoi_rot90(lsoi, expected, "augment_line_strings")
def test_bounding_boxes_rot90(self):
lsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=10, y1=10, x2=20, y2=20)
], shape=(100, 200, 3))
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=100-20-1, y1=10, x2=100-10-1, y2=20)
], shape=(200, 100, 3))
self._test_cbaoi_rot90(lsoi, expected, "augment_bounding_boxes")
def _test_empty_cbaoi_rot90(self, cbaoi, expected, augf_name):
for backend in ["auto", "cv2", "skimage"]:
with self.subTest(backend=backend):
aug = iaa.Affine(rotate=90, fit_output=True, backend=backend)
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(cbaoi_aug, expected)
def test_empty_polygons_rot90(self):
psoi = ia.PolygonsOnImage([], shape=(100, 200, 3))
expected = ia.PolygonsOnImage([], shape=(200, 100, 3))
self._test_empty_cbaoi_rot90(psoi, expected, "augment_polygons")
def test_empty_line_strings_rot90(self):
lsoi = ia.LineStringsOnImage([], shape=(100, 200, 3))
expected = ia.LineStringsOnImage([], shape=(200, 100, 3))
self._test_empty_cbaoi_rot90(lsoi, expected, "augment_line_strings")
def test_empty_bounding_boxes_rot90(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(100, 200, 3))
expected = ia.BoundingBoxesOnImage([], shape=(200, 100, 3))
self._test_empty_cbaoi_rot90(bbsoi, expected, "augment_bounding_boxes")
# TODO merge these into TestAffine_rotate since they are rotations?
# or extend to contain other affine params too?
class TestAffine_alignment(unittest.TestCase):
def setUp(self):
reseed()
def test_image_segmap_alignment_with_translate_px(self):
image = np.zeros((80, 100, 3), dtype=np.uint8)
image[40-10:40+10, 50-10:50+10, :] = 255
hm = np.zeros((40, 50, 1), dtype=np.float32)
hm[20-5:20+5, 25-5:25+5, 0] = 1.0
hm = ia.HeatmapsOnImage(hm, shape=image.shape)
# note that if x is an odd value (e.g. 1), the projection is a bit
# less accurate as x=1 projected to a half-sized segmap is x=0.5,
# leading to interpolation effects
xvals = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, [0, 10, 20]]
for xvals_i in xvals:
with self.subTest(x=xvals_i):
aug = iaa.Affine(translate_px={"x": xvals_i})
iterations = 2 if ia.is_single_number(xvals_i) else 20
for _ in np.arange(iterations):
image_aug, hm_aug = aug(image=image, heatmaps=hm)
hm_aug_arr_rs = ia.imresize_single_image(
hm_aug.get_arr(), (80, 100), interpolation="nearest")
overlap_true = np.sum(
np.logical_and(
(image_aug[..., 0] > 220),
(hm_aug_arr_rs[..., 0] > 0.9)
)
)
p_same_on_zero_cells = np.average(
(image_aug[..., 0] > 220)
== (hm_aug_arr_rs[..., 0] > 0.9))
assert overlap_true > 19*19
assert p_same_on_zero_cells > 0.98
def test_image_segmap_alignment_with_translate_percent(self):
image = np.zeros((80, 100, 3), dtype=np.uint8)
image[40-10:40+10, 50-10:50+10, :] = 255
hm = np.zeros((40, 50, 1), dtype=np.float32)
hm[20-5:20+5, 25-5:25+5, 0] = 1.0
hm = ia.HeatmapsOnImage(hm, shape=image.shape)
# note that if x is an odd value (e.g. 1), the projection is a bit
# less accurate as x=1 projected to a half-sized segmap is x=0.5,
# leading to interpolation effects
width = image.shape[1]
xvals = [0/width, 2/width, 4/width, 6/width, 8/width, 10/width,
12/width, 14/width, 16/width, 18/width, 20/width,
[0/width, 10/width, 20/width]]
for xvals_i in xvals:
with self.subTest(x=xvals_i):
aug = iaa.Affine(translate_percent={"x": xvals_i})
iterations = 2 if ia.is_single_number(xvals_i) else 20
for _ in np.arange(iterations):
image_aug, hm_aug = aug(image=image, heatmaps=hm)
hm_aug_arr_rs = ia.imresize_single_image(
hm_aug.get_arr(), (80, 100), interpolation="nearest")
overlap_true = np.sum(
np.logical_and(
(image_aug[..., 0] > 220),
(hm_aug_arr_rs[..., 0] > 0.9)
)
)
p_same_on_zero_cells = np.average(
(image_aug[..., 0] > 220)
== (hm_aug_arr_rs[..., 0] > 0.9))
assert overlap_true > 19*19
assert p_same_on_zero_cells > 0.98
def test_image_keypoint_alignment(self):
aug = iaa.Affine(rotate=[0, 180], order=0)
img = np.zeros((10, 10), dtype=np.uint8)
img[0:5, 5] = 255
img[2, 4:6] = 255
img_rot = [np.copy(img), np.copy(np.flipud(np.fliplr(img)))]
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=5, y=2)], shape=img.shape)
kpsoi_rot = [(5, 2), (5, 10-2)]
img_aug_indices = []
kpsois_aug_indices = []
for _ in sm.xrange(40):
aug_det = aug.to_deterministic()
imgs_aug = aug_det.augment_images([img, img])
kpsois_aug = aug_det.augment_keypoints([kpsoi, kpsoi])
assert kpsois_aug[0].shape == img.shape
assert kpsois_aug[1].shape == img.shape
for img_aug in imgs_aug:
if np.array_equal(img_aug, img_rot[0]):
img_aug_indices.append(0)
elif np.array_equal(img_aug, img_rot[1]):
img_aug_indices.append(1)
else:
assert False
for kpsoi_aug in kpsois_aug:
similar_to_rot_0 = np.allclose(
[kpsoi_aug.keypoints[0].x, kpsoi_aug.keypoints[0].y],
kpsoi_rot[0])
similar_to_rot_180 = np.allclose(
[kpsoi_aug.keypoints[0].x, kpsoi_aug.keypoints[0].y],
kpsoi_rot[1])
if similar_to_rot_0:
kpsois_aug_indices.append(0)
elif similar_to_rot_180:
kpsois_aug_indices.append(1)
else:
assert False
assert np.array_equal(img_aug_indices, kpsois_aug_indices)
assert len(set(img_aug_indices)) == 2
assert len(set(kpsois_aug_indices)) == 2
@classmethod
def _test_image_cbaoi_alignment(cls, cbaoi, cbaoi_rot, augf_name):
aug = iaa.Affine(rotate=[0, 180], order=0)
img = np.zeros((10, 10), dtype=np.uint8)
img[0:5, 5] = 255
img[2, 4:6] = 255
img_rot = [np.copy(img), np.copy(np.flipud(np.fliplr(img)))]
img_aug_indices = []
cbaois_aug_indices = []
for _ in sm.xrange(40):
aug_det = aug.to_deterministic()
imgs_aug = aug_det.augment_images([img, img])
cbaois_aug = getattr(aug_det, augf_name)([cbaoi, cbaoi])
assert cbaois_aug[0].shape == img.shape
assert cbaois_aug[1].shape == img.shape
if hasattr(cbaois_aug[0].items[0], "is_valid"):
assert cbaois_aug[0].items[0].is_valid
assert cbaois_aug[1].items[0].is_valid
for img_aug in imgs_aug:
if np.array_equal(img_aug, img_rot[0]):
img_aug_indices.append(0)
elif np.array_equal(img_aug, img_rot[1]):
img_aug_indices.append(1)
else:
assert False
for cbaoi_aug in cbaois_aug:
if cbaoi_aug.items[0].coords_almost_equals(cbaoi_rot[0]):
cbaois_aug_indices.append(0)
elif cbaoi_aug.items[0].coords_almost_equals(cbaoi_rot[1]):
cbaois_aug_indices.append(1)
else:
assert False
assert np.array_equal(img_aug_indices, cbaois_aug_indices)
assert len(set(img_aug_indices)) == 2
assert len(set(cbaois_aug_indices)) == 2
def test_image_polygon_alignment(self):
psoi = ia.PolygonsOnImage([ia.Polygon([(1, 1), (9, 1), (5, 5)])],
shape=(10, 10))
psoi_rot = [
psoi.polygons[0].deepcopy(),
ia.Polygon([(10-1, 10-1), (10-9, 10-1), (10-5, 10-5)])
]
self._test_image_cbaoi_alignment(psoi, psoi_rot,
"augment_polygons")
def test_image_line_string_alignment(self):
lsoi = ia.LineStringsOnImage([ia.LineString([(1, 1), (9, 1), (5, 5)])],
shape=(10, 10))
lsoi_rot = [
lsoi.items[0].deepcopy(),
ia.LineString([(10-1, 10-1), (10-9, 10-1), (10-5, 10-5)])
]
self._test_image_cbaoi_alignment(lsoi, lsoi_rot,
"augment_line_strings")
def test_image_bounding_box_alignment(self):
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=1, y1=1, x2=9, y2=5)], shape=(10, 10))
bbsoi_rot = [
bbsoi.items[0].deepcopy(),
ia.BoundingBox(x1=10-9, y1=10-5, x2=10-1, y2=10-1)]
self._test_image_cbaoi_alignment(bbsoi, bbsoi_rot,
"augment_bounding_boxes")
class TestAffine_other_dtypes(unittest.TestCase):
@property
def translate_mask(self):
mask = np.zeros((3, 3), dtype=bool)
mask[1, 2] = True
return mask
@property
def image(self):
image = np.zeros((17, 17), dtype=bool)
image[2:15, 5:13] = True
return image
@property
def rot_mask_inner(self):
img_flipped = iaa.Fliplr(1.0)(image=self.image)
return img_flipped == 1
@property
def rot_mask_outer(self):
img_flipped = iaa.Fliplr(1.0)(image=self.image)
return img_flipped == 0
@property
def rot_thresh_inner(self):
return 0.9
@property
def rot_thresh_outer(self):
return 0.9
def rot_thresh_inner_float(self, order):
return 0.85 if order == 1 else 0.7
def rot_thresh_outer_float(self, order):
return 0.85 if order == 1 else 0.4
def test_translate_skimage_order_0_bool(self):
aug = iaa.Affine(translate_px={"x": 1}, order=0, mode="constant",
backend="skimage")
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert np.all(image_aug[~self.translate_mask] == 0)
assert np.all(image_aug[self.translate_mask] == 1)
def test_translate_skimage_order_0_uint_int(self):
dtypes = ["uint8", "uint16", "uint32", "int8", "int16", "int32"]
for dtype in dtypes:
aug = iaa.Affine(translate_px={"x": 1}, order=0, mode="constant",
backend="skimage")
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
values = values + [(-1) * value for value in values]
else:
values = [1, 5, 10, 100, int(center_value),
int(0.1 * max_value), int(0.2 * max_value),
int(0.5 * max_value), max_value - 100, max_value]
for value in values:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug[~self.translate_mask] == 0)
assert np.all(image_aug[self.translate_mask] == value)
def test_translate_skimage_order_0_float(self):
# float
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
aug = iaa.Affine(translate_px={"x": 1}, order=0, mode="constant",
backend="skimage")
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [
0.01,
1.0,
10.0,
100.0,
500 ** (isize - 1),
float(np.float64(1000 ** (isize - 1)))
]
values = values + [(-1) * value for value in values]
values = values + [min_value, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(_isclose(image_aug[~self.translate_mask], 0))
assert np.all(_isclose(image_aug[self.translate_mask],
value))
def test_rotate_skimage_order_not_0_bool(self):
# skimage, order!=0 and rotate=180
for order in [1, 3, 4, 5]:
aug = iaa.Affine(rotate=180, order=order, mode="constant",
backend="skimage")
aug_flip = iaa.Sequential([iaa.Flipud(1.0), iaa.Fliplr(1.0)])
image = np.zeros((17, 17), dtype=bool)
image[2:15, 5:13] = True
image_aug = aug.augment_image(image)
image_exp = aug_flip.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert (
np.sum(image_aug == image_exp)/image.size
) > self.rot_thresh_inner
def test_rotate_skimage_order_not_0_uint_int(self):
def _compute_matching(image_aug, image_exp, mask):
return np.sum(
np.isclose(image_aug[mask], image_exp[mask], rtol=0,
atol=1.001)
) / np.sum(mask)
dtypes = ["uint8", "uint16", "uint32", "int8", "int16", "int32"]
for dtype in dtypes:
for order in [1, 3, 4, 5]:
aug = iaa.Affine(rotate=180, order=order, mode="constant",
backend="skimage")
aug_flip = iaa.Sequential([iaa.Flipud(1.0), iaa.Fliplr(1.0)])
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
values = values + [(-1) * value for value in values]
else:
values = [1, 5, 10, 100, int(center_value),
int(0.1 * max_value), int(0.2 * max_value),
int(0.5 * max_value), max_value - 100, max_value]
for value in values:
with self.subTest(dtype=dtype, order=order, value=value):
image = np.zeros((17, 17), dtype=dtype)
image[2:15, 5:13] = value
image_aug = aug.augment_image(image)
image_exp = aug_flip.augment_image(image)
assert image_aug.dtype.name == dtype
assert _compute_matching(
image_aug, image_exp, self.rot_mask_inner
) > self.rot_thresh_inner
assert _compute_matching(
image_aug, image_exp, self.rot_mask_outer
) > self.rot_thresh_outer
def test_rotate_skimage_order_not_0_float(self):
def _compute_matching(image_aug, image_exp, mask):
return np.sum(
_isclose(image_aug[mask], image_exp[mask])
) / np.sum(mask)
for order in [1, 3, 4, 5]:
dtypes = ["float16", "float32", "float64"]
if order == 5:
# float64 caused too many interpolation inaccuracies for
# order=5, not wrong but harder to test
dtypes = ["float16", "float32"]
for dtype in dtypes:
aug = iaa.Affine(rotate=180, order=order, mode="constant",
backend="skimage")
aug_flip = iaa.Sequential([iaa.Flipud(1.0), iaa.Fliplr(1.0)])
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
if order not in [0, 1]:
atol = 1e-2
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [0.01, 1.0, 10.0, 100.0, 500 ** (isize - 1),
1000 ** (isize - 1)]
values = values + [(-1) * value for value in values]
if order not in [3, 4]: # results in NaNs otherwise
values = values + [min_value, max_value]
for value in values:
with self.subTest(order=order, dtype=dtype, value=value):
image = np.zeros((17, 17), dtype=dtype)
image[2:15, 5:13] = value
image_aug = aug.augment_image(image)
image_exp = aug_flip.augment_image(image)
assert image_aug.dtype.name == dtype
assert _compute_matching(
image_aug, image_exp, self.rot_mask_inner
) > self.rot_thresh_inner_float(order)
assert _compute_matching(
image_aug, image_exp, self.rot_mask_outer
) > self.rot_thresh_outer_float(order)
def test_translate_cv2_order_0_bool(self):
aug = iaa.Affine(translate_px={"x": 1}, order=0, mode="constant",
backend="cv2")
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert np.all(image_aug[~self.translate_mask] == 0)
assert np.all(image_aug[self.translate_mask] == 1)
def test_translate_cv2_order_0_uint_int(self):
aug = iaa.Affine(translate_px={"x": 1}, order=0, mode="constant",
backend="cv2")
dtypes = ["uint8", "uint16", "int8", "int16", "int32"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
values = values + [(-1) * value for value in values]
else:
values = [1, 5, 10, 100, int(center_value),
int(0.1 * max_value), int(0.2 * max_value),
int(0.5 * max_value), max_value - 100, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug[~self.translate_mask] == 0)
assert np.all(image_aug[self.translate_mask] == value)
def test_translate_cv2_order_0_float(self):
aug = iaa.Affine(translate_px={"x": 1}, order=0, mode="constant",
backend="cv2")
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [
0.01,
1.0,
10.0,
100.0,
500 ** (isize - 1),
float(np.float64(1000 ** (isize - 1)))
]
values = values + [(-1) * value for value in values]
values = values + [min_value, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(_isclose(image_aug[~self.translate_mask], 0))
assert np.all(_isclose(image_aug[self.translate_mask],
value))
def test_rotate_cv2_order_1_and_3_bool(self):
# cv2, order=1 and rotate=180
for order in [1, 3]:
aug = iaa.Affine(rotate=180, order=order, mode="constant",
backend="cv2")
aug_flip = iaa.Sequential([iaa.Flipud(1.0), iaa.Fliplr(1.0)])
image = np.zeros((17, 17), dtype=bool)
image[2:15, 5:13] = True
image_aug = aug.augment_image(image)
image_exp = aug_flip.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert (np.sum(image_aug == image_exp) / image.size) > 0.9
def test_rotate_cv2_order_1_and_3_uint_int(self):
# cv2, order=1 and rotate=180
for order in [1, 3]:
aug = iaa.Affine(rotate=180, order=order, mode="constant",
backend="cv2")
aug_flip = iaa.Sequential([iaa.Flipud(1.0), iaa.Fliplr(1.0)])
dtypes = ["uint8", "uint16", "int8", "int16"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
values = values + [(-1) * value for value in values]
else:
values = [1, 5, 10, 100, int(center_value),
int(0.1 * max_value), int(0.2 * max_value),
int(0.5 * max_value), max_value - 100, max_value]
for value in values:
with self.subTest(order=order, dtype=dtype, value=value):
image = np.zeros((17, 17), dtype=dtype)
image[2:15, 5:13] = value
image_aug = aug.augment_image(image)
image_exp = aug_flip.augment_image(image)
assert image_aug.dtype.name == dtype
assert (
np.sum(image_aug == image_exp) / image.size
) > 0.9
def test_rotate_cv2_order_1_and_3_float(self):
# cv2, order=1 and rotate=180
for order in [1, 3]:
aug = iaa.Affine(rotate=180, order=order, mode="constant",
backend="cv2")
aug_flip = iaa.Sequential([iaa.Flipud(1.0), iaa.Fliplr(1.0)])
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [0.01, 1.0, 10.0, 100.0, 500 ** (isize - 1),
1000 ** (isize - 1)]
values = values + [(-1) * value for value in values]
values = values + [min_value, max_value]
for value in values:
with self.subTest(order=order, dtype=dtype, value=value):
image = np.zeros((17, 17), dtype=dtype)
image[2:15, 5:13] = value
image_aug = aug.augment_image(image)
image_exp = aug_flip.augment_image(image)
assert image_aug.dtype.name == dtype
assert (
np.sum(_isclose(image_aug, image_exp)) / image.size
) > 0.9
class TestAffine_other(unittest.TestCase):
def test_unusual_channel_numbers(self):
with assertWarns(self, iaa.SuspiciousSingleImageShapeWarning):
nb_channels_lst = [4, 5, 512, 513]
orders = [0, 1, 3]
backends = ["auto", "skimage", "cv2"]
gen = itertools.product(nb_channels_lst, orders, backends)
for nb_channels, order, backend in gen:
with self.subTest(nb_channels=nb_channels, order=order,
backend=backend):
aug = iaa.Affine(translate_px={"x": -1}, mode="constant",
cval=255, order=order, backend=backend)
image = np.full((3, 3, nb_channels), 128, dtype=np.uint8)
heatmap_arr = np.full((3, 3, nb_channels), 0.5,
dtype=np.float32)
heatmap = ia.HeatmapsOnImage(heatmap_arr, shape=image.shape)
image_aug, heatmap_aug = aug(image=image, heatmaps=heatmap)
hm_aug_arr = heatmap_aug.arr_0to1
assert image_aug.shape == (3, 3, nb_channels)
assert heatmap_aug.arr_0to1.shape == (3, 3, nb_channels)
assert heatmap_aug.shape == image.shape
assert np.allclose(image_aug[:, 0:2, :], 128, rtol=0,
atol=2)
assert np.allclose(image_aug[:, 2:3, 0:3], 255, rtol=0,
atol=2)
assert np.allclose(image_aug[:, 2:3, 3:], 255, rtol=0,
atol=2)
assert np.allclose(hm_aug_arr[:, 0:2, :], 0.5, rtol=0,
atol=0.025)
assert np.allclose(hm_aug_arr[:, 2:3, :], 0.0, rtol=0,
atol=0.025)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 1),
(1, 0, 1)
]
for fit_output in [False, True]:
for shape in shapes:
with self.subTest(shape=shape, fit_output=fit_output):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Affine(rotate=45, fit_output=fit_output)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.Affine(scale=(0.9, 1.1), translate_px=(-4, 4),
rotate=(-10, 10), shear=(-10, 10), order=[0, 1])
runtest_pickleable_uint8_img(aug, iterations=20)
class TestScaleX(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.ScaleX(1.5)
assert isinstance(aug, iaa.Affine)
assert np.isclose(aug.scale[0].value, 1.5)
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test_integrationtest(self):
image = np.zeros((10, 10), dtype=np.uint8)
image[5, 5] = 255
aug = iaa.ScaleX(4.0, order=0)
image_aug = aug(image=image)
xx = np.nonzero(np.max(image_aug, axis=0) > 200)[0]
yy = np.nonzero(np.max(image_aug, axis=1) > 200)[0]
x1, x2 = xx[0], xx[-1]
y1, y2 = yy[0], yy[-1]
# not >=3, because if e.g. index 1 is spread to 0 to 3 after scaling,
# it covers four cells (0, 1, 2, 3), but 3-0 is 3
assert x2 - x1 >= 3
assert y2 - y1 < 1
class TestScaleY(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.ScaleY(1.5)
assert isinstance(aug, iaa.Affine)
assert np.isclose(aug.scale[1].value, 1.5)
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test_integrationtest(self):
image = np.zeros((10, 10), dtype=np.uint8)
image[5, 5] = 255
aug = iaa.ScaleY(4.0, order=0)
image_aug = aug(image=image)
xx = np.nonzero(np.max(image_aug, axis=0) > 200)[0]
yy = np.nonzero(np.max(image_aug, axis=1) > 200)[0]
x1, x2 = xx[0], xx[-1]
y1, y2 = yy[0], yy[-1]
# not >=3, because if e.g. index 1 is spread to 0 to 3 after scaling,
# it covers four cells (0, 1, 2, 3), but 3-0 is 3
assert y2 - y1 >= 3
assert x2 - x1 < 1
class TestTranslateX(unittest.TestCase):
def setUp(self):
reseed()
def test___init___translate_percent(self):
aug = iaa.TranslateX(percent=0.5)
assert isinstance(aug, iaa.Affine)
assert np.isclose(aug.translate[0].value, 0.5)
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test___init___translate_px(self):
aug = iaa.TranslateX(px=2)
assert isinstance(aug, iaa.Affine)
assert np.isclose(aug.translate[0].value, 2)
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test___init___both_none(self):
aug = iaa.TranslateX()
assert np.isclose(aug.translate[0].a.value, -0.25)
assert np.isclose(aug.translate[0].b.value, 0.25)
def test_integrationtest_translate_percent(self):
image = np.full((50, 50), 255, dtype=np.uint8)
aug = iaa.TranslateX(percent=0.5, order=1, cval=0)
image_aug = aug(image=image)
expected = np.copy(image)
expected[:, 0:25] = 0
overlap = np.average(np.isclose(image_aug, expected, atol=1.01))
assert overlap > (1.0 - (1/50) - 1e-4)
def test_integrationtest_translate_px(self):
image = np.full((50, 50), 255, dtype=np.uint8)
aug = iaa.TranslateX(px=25, order=1, cval=0)
image_aug = aug(image=image)
expected = np.copy(image)
expected[:, 0:25] = 0
overlap = np.average(np.isclose(image_aug, expected, atol=1.01))
assert overlap > (1.0 - (1/50) - 1e-4)
class TestTranslateY(unittest.TestCase):
def setUp(self):
reseed()
def test___init___translate_percent(self):
aug = iaa.TranslateY(percent=0.5)
assert isinstance(aug, iaa.Affine)
assert np.isclose(aug.translate[1].value, 0.5)
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test___init___translate_px(self):
aug = iaa.TranslateY(px=2)
assert isinstance(aug, iaa.Affine)
assert np.isclose(aug.translate[1].value, 2)
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test___init___both_none(self):
aug = iaa.TranslateY()
assert np.isclose(aug.translate[1].a.value, -0.25)
assert np.isclose(aug.translate[1].b.value, 0.25)
def test_integrationtest_translate_percent(self):
image = np.full((50, 50), 255, dtype=np.uint8)
aug = iaa.TranslateY(percent=0.5, order=1, cval=0)
image_aug = aug(image=image)
expected = np.copy(image)
expected[0:25, :] = 0
overlap = np.average(np.isclose(image_aug, expected, atol=1.01))
assert overlap > (1.0 - (1/50) - 1e-4)
def test_integrationtest_translate_px(self):
image = np.full((50, 50), 255, dtype=np.uint8)
aug = iaa.TranslateY(px=25, order=1, cval=0)
image_aug = aug(image=image)
expected = np.copy(image)
expected[0:25, :] = 0
overlap = np.average(np.isclose(image_aug, expected, atol=1.01))
assert overlap > (1.0 - (1/50) - 1e-4)
class TestRotate(unittest.TestCase):
def setUp(self):
reseed()
def test___init___(self):
aug = iaa.Rotate(rotate=45)
assert isinstance(aug, iaa.Affine)
assert np.isclose(aug.rotate.value, 45)
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test_integrationtest(self):
image = np.zeros((40, 20), dtype=np.uint8)
image[:, 10:10+1] = 255
aug = iaa.Rotate(90, order=0)
image_aug = aug(image=image)
assert image_aug.shape == (40, 20)
assert np.isclose(np.sum(image_aug[20-1:20+2, :]), 255*20, atol=1)
class TestShearX(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.ShearX(40)
assert isinstance(aug, iaa.Affine)
assert aug.shear[0].value == 40
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test_integrationtest(self):
def _find_coords(arr):
xx = np.nonzero(np.max(arr, axis=0) > 200)[0]
yy = np.nonzero(np.max(arr, axis=1) > 200)[0]
x1 = xx[0]
x2 = xx[-1]
y1 = yy[0]
y2 = yy[-1]
return x1+(x2-x1)/2, y1+(y2-y1)/2
image = np.zeros((50, 50, 4), dtype=np.uint8)
image[10:10+1, 20:20+1, 0] = 255
image[10:10+1, 30:30+1, 1] = 255
image[40:40+1, 30:30+1, 2] = 255
image[40:40+1, 20:20+1, 3] = 255
aug = iaa.ShearX(30, order=0)
image_aug = aug(image=image)
x1, y1 = _find_coords(image_aug[..., 0])
x2, y2 = _find_coords(image_aug[..., 1])
x3, y3 = _find_coords(image_aug[..., 2])
x4, y4 = _find_coords(image_aug[..., 3])
assert x1 > 20
assert np.isclose(y1, 10.0)
assert np.isclose(y2, 10.0)
assert x3 < 30
assert np.isclose(y3, 40.0)
assert np.isclose(y4, 40.0)
assert not np.isclose(x1, x4)
assert not np.isclose(x2, x3)
class TestShearY(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.ShearY(40)
assert isinstance(aug, iaa.Affine)
assert aug.shear[1].value == 40
assert aug.order.value == 1
assert aug.cval.value == 0
assert aug.mode.value == "constant"
assert aug.fit_output is False
def test_integrationtest(self):
def _find_coords(arr):
xx = np.nonzero(np.max(arr, axis=0) > 200)[0]
yy = np.nonzero(np.max(arr, axis=1) > 200)[0]
x1 = xx[0]
x2 = xx[-1]
y1 = yy[0]
y2 = yy[-1]
return x1+(x2-x1)/2, y1+(y2-y1)/2
image = np.zeros((50, 50, 4), dtype=np.uint8)
image[20:20+1, 10:10+1, 0] = 255
image[20:20+1, 40:40+1, 1] = 255
image[30:30+1, 40:40+1, 2] = 255
image[30:30+1, 10:10+1, 3] = 255
aug = iaa.ShearY(30, order=0)
image_aug = aug(image=image)
x1, y1 = _find_coords(image_aug[..., 0])
x2, y2 = _find_coords(image_aug[..., 1])
x3, y3 = _find_coords(image_aug[..., 2])
x4, y4 = _find_coords(image_aug[..., 3])
assert y1 < 20
assert np.isclose(x1, 10.0)
assert np.isclose(x4, 10.0)
assert y2 > 20
assert np.isclose(x2, 40.0)
assert np.isclose(x3, 40.0)
assert not np.isclose(y1, y2)
assert not np.isclose(y3, y4)
# TODO migrate to unittest and split up tests or remove AffineCv2
def test_AffineCv2():
reseed()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = iaa.AffineCv2()
assert "is deprecated" in str(caught_warnings[0].message)
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=ia.DeprecationWarning)
base_img = np.array([[0, 0, 0],
[0, 255, 0],
[0, 0, 0]], dtype=np.uint8)
base_img = base_img[:, :, np.newaxis]
images = np.array([base_img])
images_list = [base_img]
outer_pixels = ([], [])
for i in sm.xrange(base_img.shape[0]):
for j in sm.xrange(base_img.shape[1]):
if i != j:
outer_pixels[0].append(i)
outer_pixels[1].append(j)
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
keypoints = [ia.KeypointsOnImage(kps, shape=base_img.shape)]
# no translation/scale/rotate/shear, shouldnt change nothing
aug = iaa.AffineCv2(scale=1.0, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
observed = aug_det.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
observed = aug.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
observed = aug_det.augment_images(images_list)
expected = images_list
assert array_equal_lists(observed, expected)
observed = aug.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
observed = aug_det.augment_keypoints(keypoints)
expected = keypoints
assert keypoints_equal(observed, expected)
# ---------------------
# scale
# ---------------------
# zoom in
aug = iaa.AffineCv2(scale=1.75, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
assert observed[0][1, 1] > 250
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 20).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 150).all()
observed = aug_det.augment_images(images)
assert observed[0][1, 1] > 250
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 20).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 150).all()
observed = aug.augment_images(images_list)
assert observed[0][1, 1] > 250
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 20).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 150).all()
observed = aug_det.augment_images(images_list)
assert observed[0][1, 1] > 250
assert (observed[0][outer_pixels[0], outer_pixels[1]] > 20).all()
assert (observed[0][outer_pixels[0], outer_pixels[1]] < 150).all()
observed = aug.augment_keypoints(keypoints)
assert observed[0].keypoints[0].x < 0
assert observed[0].keypoints[0].y < 0
assert observed[0].keypoints[1].x == 1
assert observed[0].keypoints[1].y == 1
assert observed[0].keypoints[2].x > 2
assert observed[0].keypoints[2].y > 2
observed = aug_det.augment_keypoints(keypoints)
assert observed[0].keypoints[0].x < 0
assert observed[0].keypoints[0].y < 0
assert observed[0].keypoints[1].x == 1
assert observed[0].keypoints[1].y == 1
assert observed[0].keypoints[2].x > 2
assert observed[0].keypoints[2].y > 2
# zoom in only on x axis
aug = iaa.AffineCv2(scale={"x": 1.75, "y": 1.0}, translate_px=0,
rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
assert observed[0][1, 1] > 250
assert (observed[0][[1, 1], [0, 2]] > 20).all()
assert (observed[0][[1, 1], [0, 2]] < 150).all()
assert (observed[0][0, :] < 5).all()
assert (observed[0][2, :] < 5).all()
observed = aug_det.augment_images(images)
assert observed[0][1, 1] > 250
assert (observed[0][[1, 1], [0, 2]] > 20).all()
assert (observed[0][[1, 1], [0, 2]] < 150).all()
assert (observed[0][0, :] < 5).all()
assert (observed[0][2, :] < 5).all()
observed = aug.augment_images(images_list)
assert observed[0][1, 1] > 250
assert (observed[0][[1, 1], [0, 2]] > 20).all()
assert (observed[0][[1, 1], [0, 2]] < 150).all()
assert (observed[0][0, :] < 5).all()
assert (observed[0][2, :] < 5).all()
observed = aug_det.augment_images(images_list)
assert observed[0][1, 1] > 250
assert (observed[0][[1, 1], [0, 2]] > 20).all()
assert (observed[0][[1, 1], [0, 2]] < 150).all()
assert (observed[0][0, :] < 5).all()
assert (observed[0][2, :] < 5).all()
observed = aug.augment_keypoints(keypoints)
assert observed[0].keypoints[0].x < 0
assert observed[0].keypoints[0].y == 0
assert observed[0].keypoints[1].x == 1
assert observed[0].keypoints[1].y == 1
assert observed[0].keypoints[2].x > 2
assert observed[0].keypoints[2].y == 2
observed = aug_det.augment_keypoints(keypoints)
assert observed[0].keypoints[0].x < 0
assert observed[0].keypoints[0].y == 0
assert observed[0].keypoints[1].x == 1
assert observed[0].keypoints[1].y == 1
assert observed[0].keypoints[2].x > 2
assert observed[0].keypoints[2].y == 2
# zoom in only on y axis
aug = iaa.AffineCv2(scale={"x": 1.0, "y": 1.75}, translate_px=0,
rotate=0, shear=0)
aug_det = aug.to_deterministic()
observed = aug.augment_images(images)
assert observed[0][1, 1] > 250
assert (observed[0][[0, 2], [1, 1]] > 20).all()
assert (observed[0][[0, 2], [1, 1]] < 150).all()
assert (observed[0][:, 0] < 5).all()
assert (observed[0][:, 2] < 5).all()
observed = aug_det.augment_images(images)
assert observed[0][1, 1] > 250
assert (observed[0][[0, 2], [1, 1]] > 20).all()
assert (observed[0][[0, 2], [1, 1]] < 150).all()
assert (observed[0][:, 0] < 5).all()
assert (observed[0][:, 2] < 5).all()
observed = aug.augment_images(images_list)
assert observed[0][1, 1] > 250
assert (observed[0][[0, 2], [1, 1]] > 20).all()
assert (observed[0][[0, 2], [1, 1]] < 150).all()
assert (observed[0][:, 0] < 5).all()
assert (observed[0][:, 2] < 5).all()
observed = aug_det.augment_images(images_list)
assert observed[0][1, 1] > 250
assert (observed[0][[0, 2], [1, 1]] > 20).all()
assert (observed[0][[0, 2], [1, 1]] < 150).all()
assert (observed[0][:, 0] < 5).all()
assert (observed[0][:, 2] < 5).all()
observed = aug.augment_keypoints(keypoints)
assert observed[0].keypoints[0].x == 0
assert observed[0].keypoints[0].y < 0
assert observed[0].keypoints[1].x == 1
assert observed[0].keypoints[1].y == 1
assert observed[0].keypoints[2].x == 2
assert observed[0].keypoints[2].y > 2
observed = aug_det.augment_keypoints(keypoints)
assert observed[0].keypoints[0].x == 0
assert observed[0].keypoints[0].y < 0
assert observed[0].keypoints[1].x == 1
assert observed[0].keypoints[1].y == 1
assert observed[0].keypoints[2].x == 2
assert observed[0].keypoints[2].y > 2
# zoom out
# this one uses a 4x4 area of all 255, which is zoomed out to a 4x4
# area in which the center 2x2 area is 255
# zoom in should probably be adapted to this style
# no separate tests here for x/y axis, should work fine if zoom in
# works with that
aug = iaa.AffineCv2(scale=0.49, translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
image = np.ones((4, 4, 1), dtype=np.uint8) * 255
images = np.array([image])
images_list = [image]
outer_pixels = ([], [])
for y in sm.xrange(4):
xs = sm.xrange(4) if y in [0, 3] else [0, 3]
for x in xs:
outer_pixels[0].append(y)
outer_pixels[1].append(x)
inner_pixels = ([1, 1, 2, 2], [1, 2, 1, 2])
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=3, y=0),
ia.Keypoint(x=0, y=3), ia.Keypoint(x=3, y=3)]
keypoints = [ia.KeypointsOnImage(kps, shape=image.shape)]
kps_aug = [ia.Keypoint(x=0.765, y=0.765),
ia.Keypoint(x=2.235, y=0.765),
ia.Keypoint(x=0.765, y=2.235),
ia.Keypoint(x=2.235, y=2.235)]
keypoints_aug = [ia.KeypointsOnImage(kps_aug, shape=image.shape)]
observed = aug.augment_images(images)
assert (observed[0][outer_pixels] < 25).all()
assert (observed[0][inner_pixels] > 200).all()
observed = aug_det.augment_images(images)
assert (observed[0][outer_pixels] < 25).all()
assert (observed[0][inner_pixels] > 200).all()
observed = aug.augment_images(images_list)
assert (observed[0][outer_pixels] < 25).all()
assert (observed[0][inner_pixels] > 200).all()
observed = aug_det.augment_images(images_list)
assert (observed[0][outer_pixels] < 25).all()
assert (observed[0][inner_pixels] > 200).all()
observed = aug.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
observed = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
# varying scales
aug = iaa.AffineCv2(scale={"x": (0.5, 1.5), "y": (0.5, 1.5)},
translate_px=0, rotate=0, shear=0)
aug_det = aug.to_deterministic()
image = np.array([[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 1, 2, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]], dtype=np.uint8) * 100
image = image[:, :, np.newaxis]
images = np.array([image])
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert nb_changed_aug >= int(nb_iterations * 0.8)
assert nb_changed_aug_det == 0
aug = iaa.AffineCv2(scale=iap.Uniform(0.7, 0.9))
assert is_parameter_instance(aug.scale, iap.Uniform)
assert is_parameter_instance(aug.scale.a, iap.Deterministic)
assert is_parameter_instance(aug.scale.b, iap.Deterministic)
assert 0.7 - 1e-8 < aug.scale.a.value < 0.7 + 1e-8
assert 0.9 - 1e-8 < aug.scale.b.value < 0.9 + 1e-8
# ---------------------
# translate
# ---------------------
# move one pixel to the right
aug = iaa.AffineCv2(scale=1.0, translate_px={"x": 1, "y": 0},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
image = np.zeros((3, 3, 1), dtype=np.uint8)
image_aug = np.copy(image)
image[1, 1] = 255
image_aug[1, 2] = 255
images = np.array([image])
images_aug = np.array([image_aug])
images_list = [image]
images_aug_list = [image_aug]
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)],
shape=base_img.shape)]
keypoints_aug = [ia.KeypointsOnImage([ia.Keypoint(x=2, y=1)],
shape=base_img.shape)]
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
observed = aug_det.augment_images(images)
assert np.array_equal(observed, images_aug)
observed = aug.augment_images(images_list)
assert array_equal_lists(observed, images_aug_list)
observed = aug_det.augment_images(images_list)
assert array_equal_lists(observed, images_aug_list)
observed = aug.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
observed = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
# move one pixel to the right
aug = iaa.AffineCv2(scale=1.0, translate_px={"x": 1, "y": 0},
rotate=0, shear=0)
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
# move one pixel to the right
aug = iaa.AffineCv2(scale=1.0, translate_px={"x": 1, "y": 0},
rotate=0, shear=0)
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
# move one pixel to the right
# with order=ALL
aug = iaa.AffineCv2(scale=1.0, translate_px={"x": 1, "y": 0},
rotate=0, shear=0, order=ia.ALL)
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
# move one pixel to the right
# with order=list
aug = iaa.AffineCv2(scale=1.0, translate_px={"x": 1, "y": 0},
rotate=0, shear=0, order=[0, 1, 2])
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
# move one pixel to the right
# with order=StochasticParameter
aug = iaa.AffineCv2(scale=1.0, translate_px={"x": 1, "y": 0},
rotate=0, shear=0, order=iap.Choice([0, 1, 2]))
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
# move one pixel to the bottom
aug = iaa.AffineCv2(scale=1.0, translate_px={"x": 0, "y": 1},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
image = np.zeros((3, 3, 1), dtype=np.uint8)
image_aug = np.copy(image)
image[1, 1] = 255
image_aug[2, 1] = 255
images = np.array([image])
images_aug = np.array([image_aug])
images_list = [image]
images_aug_list = [image_aug]
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)],
shape=base_img.shape)]
keypoints_aug = [ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)],
shape=base_img.shape)]
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
observed = aug_det.augment_images(images)
assert np.array_equal(observed, images_aug)
observed = aug.augment_images(images_list)
assert array_equal_lists(observed, images_aug_list)
observed = aug_det.augment_images(images_list)
assert array_equal_lists(observed, images_aug_list)
observed = aug.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
observed = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
# move 33% (one pixel) to the right
aug = iaa.AffineCv2(scale=1.0, translate_percent={"x": 0.3333, "y": 0},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
image = np.zeros((3, 3, 1), dtype=np.uint8)
image_aug = np.copy(image)
image[1, 1] = 255
image_aug[1, 2] = 255
images = np.array([image])
images_aug = np.array([image_aug])
images_list = [image]
images_aug_list = [image_aug]
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)],
shape=base_img.shape)]
keypoints_aug = [ia.KeypointsOnImage([ia.Keypoint(x=2, y=1)],
shape=base_img.shape)]
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
observed = aug_det.augment_images(images)
assert np.array_equal(observed, images_aug)
observed = aug.augment_images(images_list)
assert array_equal_lists(observed, images_aug_list)
observed = aug_det.augment_images(images_list)
assert array_equal_lists(observed, images_aug_list)
observed = aug.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
observed = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
# move 33% (one pixel) to the bottom
aug = iaa.AffineCv2(scale=1.0, translate_percent={"x": 0, "y": 0.3333},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
image = np.zeros((3, 3, 1), dtype=np.uint8)
image_aug = np.copy(image)
image[1, 1] = 255
image_aug[2, 1] = 255
images = np.array([image])
images_aug = np.array([image_aug])
images_list = [image]
images_aug_list = [image_aug]
keypoints = [ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)],
shape=base_img.shape)]
keypoints_aug = [ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)],
shape=base_img.shape)]
observed = aug.augment_images(images)
assert np.array_equal(observed, images_aug)
observed = aug_det.augment_images(images)
assert np.array_equal(observed, images_aug)
observed = aug.augment_images(images_list)
assert array_equal_lists(observed, images_aug_list)
observed = aug_det.augment_images(images_list)
assert array_equal_lists(observed, images_aug_list)
observed = aug.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
observed = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
# 0-1px to left/right and 0-1px to top/bottom
aug = iaa.AffineCv2(scale=1.0,
translate_px={"x": (-1, 1), "y": (-1, 1)},
rotate=0, shear=0)
aug_det = aug.to_deterministic()
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
centers_aug = np.copy(image).astype(np.int32) * 0
centers_aug_det = np.copy(image).astype(np.int32) * 0
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
assert len(observed_aug[0].nonzero()[0]) == 1
assert len(observed_aug_det[0].nonzero()[0]) == 1
centers_aug += (observed_aug[0] > 0)
centers_aug_det += (observed_aug_det[0] > 0)
assert nb_changed_aug >= int(nb_iterations * 0.7)
assert nb_changed_aug_det == 0
assert (centers_aug > int(nb_iterations * (1/9 * 0.6))).all()
assert (centers_aug < int(nb_iterations * (1/9 * 1.4))).all()
aug = iaa.AffineCv2(translate_percent=iap.Uniform(0.7, 0.9))
assert is_parameter_instance(aug.translate, iap.Uniform)
assert is_parameter_instance(aug.translate.a, iap.Deterministic)
assert is_parameter_instance(aug.translate.b, iap.Deterministic)
assert 0.7 - 1e-8 < aug.translate.a.value < 0.7 + 1e-8
assert 0.9 - 1e-8 < aug.translate.b.value < 0.9 + 1e-8
aug = iaa.AffineCv2(translate_px=iap.DiscreteUniform(1, 10))
assert is_parameter_instance(aug.translate, iap.DiscreteUniform)
assert is_parameter_instance(aug.translate.a, iap.Deterministic)
assert is_parameter_instance(aug.translate.b, iap.Deterministic)
assert aug.translate.a.value == 1
assert aug.translate.b.value == 10
# ---------------------
# translate heatmaps
# ---------------------
heatmaps = HeatmapsOnImage(
np.float32([
[0.0, 0.5, 0.75],
[0.0, 0.5, 0.75],
[0.75, 0.75, 0.75],
]),
shape=(3, 3, 3)
)
arr_expected_1px_right = np.float32([
[0.0, 0.0, 0.5],
[0.0, 0.0, 0.5],
[0.0, 0.75, 0.75],
])
aug = iaa.AffineCv2(translate_px={"x": 1})
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == heatmaps.shape
assert np.isclose(observed.min_value, heatmaps.min_value,
rtol=0, atol=1e-6)
assert np.isclose(observed.max_value, heatmaps.max_value,
rtol=0, atol=1e-6)
assert np.array_equal(observed.get_arr(), arr_expected_1px_right)
# should still use mode=constant cval=0 even when other settings chosen
aug = iaa.AffineCv2(translate_px={"x": 1}, cval=255)
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == heatmaps.shape
assert np.isclose(observed.min_value, heatmaps.min_value,
rtol=0, atol=1e-6)
assert np.isclose(observed.max_value, heatmaps.max_value,
rtol=0, atol=1e-6)
assert np.array_equal(observed.get_arr(), arr_expected_1px_right)
aug = iaa.AffineCv2(translate_px={"x": 1}, mode="replicate", cval=255)
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == heatmaps.shape
assert np.isclose(observed.min_value, heatmaps.min_value,
rtol=0, atol=1e-6)
assert np.isclose(observed.max_value, heatmaps.max_value,
rtol=0, atol=1e-6)
assert np.array_equal(observed.get_arr(), arr_expected_1px_right)
# ---------------------
# translate segmaps
# ---------------------
segmaps = SegmentationMapsOnImage(
np.int32([
[0, 1, 2],
[0, 1, 2],
[2, 2, 2],
]),
shape=(3, 3, 3)
)
arr_expected_1px_right = np.int32([
[0, 0, 1],
[0, 0, 1],
[0, 2, 2],
])
aug = iaa.AffineCv2(translate_px={"x": 1})
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == segmaps.shape
assert np.array_equal(observed.get_arr(), arr_expected_1px_right)
# should still use mode=constant cval=0 even when other settings chosen
aug = iaa.AffineCv2(translate_px={"x": 1}, cval=255)
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == segmaps.shape
assert np.array_equal(observed.get_arr(), arr_expected_1px_right)
aug = iaa.AffineCv2(translate_px={"x": 1}, mode="replicate", cval=255)
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == segmaps.shape
assert np.array_equal(observed.get_arr(), arr_expected_1px_right)
# ---------------------
# rotate
# ---------------------
# rotate by 45 degrees
aug = iaa.AffineCv2(scale=1.0, translate_px=0, rotate=90, shear=0)
aug_det = aug.to_deterministic()
image = np.zeros((3, 3, 1), dtype=np.uint8)
image_aug = np.copy(image)
image[1, :] = 255
image_aug[0, 1] = 255
image_aug[1, 1] = 255
image_aug[2, 1] = 255
images = np.array([image])
images_aug = np.array([image_aug])
images_list = [image]
images_aug_list = [image_aug]
kps = [ia.Keypoint(x=0, y=1), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=1)]
keypoints = [ia.KeypointsOnImage(kps, shape=base_img.shape)]
kps_aug = [ia.Keypoint(x=1, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=1, y=2)]
keypoints_aug = [ia.KeypointsOnImage(kps_aug, shape=base_img.shape)]
observed = aug.augment_images(images)
observed[observed >= 100] = 255
observed[observed < 100] = 0
assert np.array_equal(observed, images_aug)
observed = aug_det.augment_images(images)
observed[observed >= 100] = 255
observed[observed < 100] = 0
assert np.array_equal(observed, images_aug)
observed = aug.augment_images(images_list)
observed[0][observed[0] >= 100] = 255
observed[0][observed[0] < 100] = 0
assert array_equal_lists(observed, images_aug_list)
observed = aug_det.augment_images(images_list)
observed[0][observed[0] >= 100] = 255
observed[0][observed[0] < 100] = 0
assert array_equal_lists(observed, images_aug_list)
observed = aug.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
observed = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(observed, keypoints_aug)
# rotate by StochasticParameter
aug = iaa.AffineCv2(scale=1.0, translate_px=0,
rotate=iap.Uniform(10, 20), shear=0)
assert is_parameter_instance(aug.rotate, iap.Uniform)
assert is_parameter_instance(aug.rotate.a, iap.Deterministic)
assert aug.rotate.a.value == 10
assert is_parameter_instance(aug.rotate.b, iap.Deterministic)
assert aug.rotate.b.value == 20
# random rotation 0-364 degrees
aug = iaa.AffineCv2(scale=1.0, translate_px=0, rotate=(0, 364),
shear=0)
aug_det = aug.to_deterministic()
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
pixels_sums_aug = np.copy(image).astype(np.int32) * 0
pixels_sums_aug_det = np.copy(image).astype(np.int32) * 0
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
pixels_sums_aug += (observed_aug[0] > 100)
pixels_sums_aug_det += (observed_aug_det[0] > 100)
assert nb_changed_aug >= int(nb_iterations * 0.9)
assert nb_changed_aug_det == 0
# center pixel, should always be white when rotating line around center
assert pixels_sums_aug[1, 1] > (nb_iterations * 0.98)
assert pixels_sums_aug[1, 1] < (nb_iterations * 1.02)
# outer pixels, should sometimes be white
# the values here had to be set quite tolerant, the middle pixels at
# top/left/bottom/right get more activation than expected
outer_pixels = ([0, 0, 0, 1, 1, 2, 2, 2], [0, 1, 2, 0, 2, 0, 1, 2])
assert (
pixels_sums_aug[outer_pixels] > int(nb_iterations * (2/8 * 0.4))
).all()
assert (
pixels_sums_aug[outer_pixels] < int(nb_iterations * (2/8 * 2.0))
).all()
# ---------------------
# shear
# ---------------------
# TODO
# shear by StochasticParameter
aug = iaa.AffineCv2(scale=1.0, translate_px=0, rotate=0,
shear=iap.Uniform(10, 20))
assert is_parameter_instance(aug.shear, iap.Uniform)
assert is_parameter_instance(aug.shear.a, iap.Deterministic)
assert aug.shear.a.value == 10
assert is_parameter_instance(aug.shear.b, iap.Deterministic)
assert aug.shear.b.value == 20
# ---------------------
# cval
# ---------------------
aug = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=128)
aug_det = aug.to_deterministic()
image = np.ones((3, 3, 1), dtype=np.uint8) * 255
image_aug = np.copy(image)
images = np.array([image])
images_list = [image]
observed = aug.augment_images(images)
assert (observed[0] > 128 - 30).all()
assert (observed[0] < 128 + 30).all()
observed = aug_det.augment_images(images)
assert (observed[0] > 128 - 30).all()
assert (observed[0] < 128 + 30).all()
observed = aug.augment_images(images_list)
assert (observed[0] > 128 - 30).all()
assert (observed[0] < 128 + 30).all()
observed = aug_det.augment_images(images_list)
assert (observed[0] > 128 - 30).all()
assert (observed[0] < 128 + 30).all()
# random cvals
aug = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=(0, 255))
aug_det = aug.to_deterministic()
last_aug = None
last_aug_det = None
nb_changed_aug = 0
nb_changed_aug_det = 0
nb_iterations = 1000
averages = []
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(images)
observed_aug_det = aug_det.augment_images(images)
if i == 0:
last_aug = observed_aug
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug = observed_aug
last_aug_det = observed_aug_det
averages.append(int(np.average(observed_aug)))
assert nb_changed_aug >= int(nb_iterations * 0.9)
assert nb_changed_aug_det == 0
# center pixel, should always be white when rotating line around center
assert pixels_sums_aug[1, 1] > (nb_iterations * 0.98)
assert pixels_sums_aug[1, 1] < (nb_iterations * 1.02)
assert len(set(averages)) > 200
aug = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=ia.ALL)
assert is_parameter_instance(aug.cval, iap.DiscreteUniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 0
assert aug.cval.b.value == 255
aug = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=iap.DiscreteUniform(1, 5))
assert is_parameter_instance(aug.cval, iap.DiscreteUniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 1
assert aug.cval.b.value == 5
# ------------
# mode
# ------------
aug = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0, mode=ia.ALL)
assert is_parameter_instance(aug.mode, iap.Choice)
aug = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0, mode="replicate")
assert is_parameter_instance(aug.mode, iap.Deterministic)
assert aug.mode.value == "replicate"
aug = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0, mode=["replicate", "reflect"])
assert is_parameter_instance(aug.mode, iap.Choice)
assert (
len(aug.mode.a) == 2
and "replicate" in aug.mode.a
and "reflect" in aug.mode.a)
aug = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0, shear=0,
cval=0,
mode=iap.Choice(["replicate", "reflect"]))
assert is_parameter_instance(aug.mode, iap.Choice)
assert (
len(aug.mode.a) == 2
and "replicate" in aug.mode.a
and "reflect" in aug.mode.a)
# ------------
# exceptions for bad inputs
# ------------
# scale
got_exception = False
try:
_ = iaa.AffineCv2(scale=False)
except Exception:
got_exception = True
assert got_exception
# translate_px
got_exception = False
try:
_ = iaa.AffineCv2(translate_px=False)
except Exception:
got_exception = True
assert got_exception
# translate_percent
got_exception = False
try:
_ = iaa.AffineCv2(translate_percent=False)
except Exception:
got_exception = True
assert got_exception
# rotate
got_exception = False
try:
_ = iaa.AffineCv2(scale=1.0, translate_px=0, rotate=False,
shear=0, cval=0)
except Exception:
got_exception = True
assert got_exception
# shear
got_exception = False
try:
_ = iaa.AffineCv2(scale=1.0, translate_px=0, rotate=0,
shear=False, cval=0)
except Exception:
got_exception = True
assert got_exception
# cval
got_exception = False
try:
_ = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0,
shear=0, cval=None)
except Exception:
got_exception = True
assert got_exception
# mode
got_exception = False
try:
_ = iaa.AffineCv2(scale=1.0, translate_px=100, rotate=0,
shear=0, cval=0, mode=False)
except Exception:
got_exception = True
assert got_exception
# non-existent order
got_exception = False
try:
_ = iaa.AffineCv2(order=-1)
except Exception:
got_exception = True
assert got_exception
# bad order datatype
got_exception = False
try:
_ = iaa.AffineCv2(order="test")
except Exception:
got_exception = True
assert got_exception
# ----------
# get_parameters
# ----------
aug = iaa.AffineCv2(scale=1, translate_px=2, rotate=3, shear=4,
order=1, cval=0, mode="constant")
params = aug.get_parameters()
assert is_parameter_instance(params[0], iap.Deterministic) # scale
assert is_parameter_instance(params[1], iap.Deterministic) # translate
assert is_parameter_instance(params[2], iap.Deterministic) # rotate
assert is_parameter_instance(params[3], iap.Deterministic) # shear
assert params[0].value == 1 # scale
assert params[1].value == 2 # translate
assert params[2].value == 3 # rotate
assert params[3].value == 4 # shear
assert params[4].value == 1 # order
assert params[5].value == 0 # cval
assert params[6].value == "constant" # mode
class TestPiecewiseAffine(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
img = np.zeros((60, 80), dtype=np.uint8)
img[:, 9:11+1] = 255
img[:, 69:71+1] = 255
return img
@property
def mask(self):
return self.image > 0
@property
def heatmaps(self):
return HeatmapsOnImage((self.image / 255.0).astype(np.float32),
shape=(60, 80, 3))
@property
def segmaps(self):
return SegmentationMapsOnImage(self.mask.astype(np.int32),
shape=(60, 80, 3))
# -----
# __init__
# -----
def test___init___scale_is_list(self):
# scale as list
aug = iaa.PiecewiseAffine(scale=[0.01, 0.10], nb_rows=12, nb_cols=4)
assert is_parameter_instance(aug.scale, iap.Choice)
assert 0.01 - 1e-8 < aug.scale.a[0] < 0.01 + 1e-8
assert 0.10 - 1e-8 < aug.scale.a[1] < 0.10 + 1e-8
def test___init___scale_is_tuple(self):
# scale as tuple
aug = iaa.PiecewiseAffine(scale=(0.01, 0.10), nb_rows=12, nb_cols=4)
assert is_parameter_instance(aug.jitter.scale, iap.Uniform)
assert is_parameter_instance(aug.jitter.scale.a, iap.Deterministic)
assert is_parameter_instance(aug.jitter.scale.b, iap.Deterministic)
assert 0.01 - 1e-8 < aug.jitter.scale.a.value < 0.01 + 1e-8
assert 0.10 - 1e-8 < aug.jitter.scale.b.value < 0.10 + 1e-8
def test___init___scale_is_stochastic_parameter(self):
# scale as StochasticParameter
aug = iaa.PiecewiseAffine(scale=iap.Uniform(0.01, 0.10), nb_rows=12,
nb_cols=4)
assert is_parameter_instance(aug.jitter.scale, iap.Uniform)
assert is_parameter_instance(aug.jitter.scale.a, iap.Deterministic)
assert is_parameter_instance(aug.jitter.scale.b, iap.Deterministic)
assert 0.01 - 1e-8 < aug.jitter.scale.a.value < 0.01 + 1e-8
assert 0.10 - 1e-8 < aug.jitter.scale.b.value < 0.10 + 1e-8
def test___init___bad_datatype_for_scale_leads_to_failure(self):
# bad datatype for scale
got_exception = False
try:
_ = iaa.PiecewiseAffine(scale=False, nb_rows=12, nb_cols=4)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___nb_rows_is_list(self):
# rows as list
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=[4, 20], nb_cols=4)
assert is_parameter_instance(aug.nb_rows, iap.Choice)
assert aug.nb_rows.a[0] == 4
assert aug.nb_rows.a[1] == 20
def test___init___nb_rows_is_tuple(self):
# rows as tuple
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=(4, 20), nb_cols=4)
assert is_parameter_instance(aug.nb_rows, iap.DiscreteUniform)
assert is_parameter_instance(aug.nb_rows.a, iap.Deterministic)
assert is_parameter_instance(aug.nb_rows.b, iap.Deterministic)
assert aug.nb_rows.a.value == 4
assert aug.nb_rows.b.value == 20
def test___init___nb_rows_is_stochastic_parameter(self):
# rows as StochasticParameter
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=iap.DiscreteUniform(4, 20),
nb_cols=4)
assert is_parameter_instance(aug.nb_rows, iap.DiscreteUniform)
assert is_parameter_instance(aug.nb_rows.a, iap.Deterministic)
assert is_parameter_instance(aug.nb_rows.b, iap.Deterministic)
assert aug.nb_rows.a.value == 4
assert aug.nb_rows.b.value == 20
def test___init___bad_datatype_for_nb_rows_leads_to_failure(self):
# bad datatype for rows
got_exception = False
try:
_ = iaa.PiecewiseAffine(scale=0.05, nb_rows=False, nb_cols=4)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___nb_cols_is_list(self):
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=[4, 20])
assert is_parameter_instance(aug.nb_cols, iap.Choice)
assert aug.nb_cols.a[0] == 4
assert aug.nb_cols.a[1] == 20
def test___init___nb_cols_is_tuple(self):
# cols as tuple
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=(4, 20))
assert is_parameter_instance(aug.nb_cols, iap.DiscreteUniform)
assert is_parameter_instance(aug.nb_cols.a, iap.Deterministic)
assert is_parameter_instance(aug.nb_cols.b, iap.Deterministic)
assert aug.nb_cols.a.value == 4
assert aug.nb_cols.b.value == 20
def test___init___nb_cols_is_stochastic_parameter(self):
# cols as StochasticParameter
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=4,
nb_cols=iap.DiscreteUniform(4, 20))
assert is_parameter_instance(aug.nb_cols, iap.DiscreteUniform)
assert is_parameter_instance(aug.nb_cols.a, iap.Deterministic)
assert is_parameter_instance(aug.nb_cols.b, iap.Deterministic)
assert aug.nb_cols.a.value == 4
assert aug.nb_cols.b.value == 20
def test___init___bad_datatype_for_nb_cols_leads_to_failure(self):
# bad datatype for cols
got_exception = False
try:
_aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___order_is_int(self):
# single int for order
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8, order=0)
assert is_parameter_instance(aug.order, iap.Deterministic)
assert aug.order.value == 0
def test___init___order_is_list(self):
# list for order
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
order=[0, 1, 3])
assert is_parameter_instance(aug.order, iap.Choice)
assert all([v in aug.order.a for v in [0, 1, 3]])
def test___init___order_is_stochastic_parameter(self):
# StochasticParameter for order
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
order=iap.Choice([0, 1, 3]))
assert is_parameter_instance(aug.order, iap.Choice)
assert all([v in aug.order.a for v in [0, 1, 3]])
def test___init___order_is_all(self):
# ALL for order
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
order=ia.ALL)
assert is_parameter_instance(aug.order, iap.Choice)
assert all([v in aug.order.a for v in [0, 1, 3, 4, 5]])
def test___init___bad_datatype_for_order_leads_to_failure(self):
# bad datatype for order
got_exception = False
try:
_ = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
order=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___cval_is_list(self):
# cval as list
aug = iaa.PiecewiseAffine(scale=0.7, nb_rows=5, nb_cols=5,
mode="constant", cval=[0, 10])
assert is_parameter_instance(aug.cval, iap.Choice)
assert aug.cval.a[0] == 0
assert aug.cval.a[1] == 10
def test___init___cval_is_tuple(self):
# cval as tuple
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
mode="constant", cval=(0, 10))
assert is_parameter_instance(aug.cval, iap.Uniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 0
assert aug.cval.b.value == 10
def test___init___cval_is_stochastic_parameter(self):
# cval as StochasticParameter
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
mode="constant",
cval=iap.DiscreteUniform(0, 10))
assert is_parameter_instance(aug.cval, iap.DiscreteUniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 0
assert aug.cval.b.value == 10
def test___init___cval_is_all(self):
# ALL as cval
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
mode="constant", cval=ia.ALL)
assert is_parameter_instance(aug.cval, iap.Uniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 0
assert aug.cval.b.value == 255
def test___init___bad_datatype_for_cval_leads_to_failure(self):
# bas datatype for cval
got_exception = False
try:
_ = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8, cval=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___mode_is_string(self):
# single string for mode
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
mode="nearest")
assert is_parameter_instance(aug.mode, iap.Deterministic)
assert aug.mode.value == "nearest"
def test___init___mode_is_list(self):
# list for mode
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
mode=["nearest", "edge", "symmetric"])
assert is_parameter_instance(aug.mode, iap.Choice)
assert all([
v in aug.mode.a for v in ["nearest", "edge", "symmetric"]
])
def test___init___mode_is_stochastic_parameter(self):
# StochasticParameter for mode
aug = iaa.PiecewiseAffine(
scale=0.1, nb_rows=8, nb_cols=8,
mode=iap.Choice(["nearest", "edge", "symmetric"]))
assert is_parameter_instance(aug.mode, iap.Choice)
assert all([
v in aug.mode.a for v in ["nearest", "edge", "symmetric"]
])
def test___init___mode_is_all(self):
# ALL for mode
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8, mode=ia.ALL)
assert is_parameter_instance(aug.mode, iap.Choice)
assert all([
v in aug.mode.a
for v
in ["constant", "edge", "symmetric", "reflect", "wrap"]
])
def test___init___bad_datatype_for_mode_leads_to_failure(self):
# bad datatype for mode
got_exception = False
try:
_ = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=8,
mode=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
# -----
# scale
# -----
def test_scale_is_small_image(self):
# basic test
aug = iaa.PiecewiseAffine(scale=0.01, nb_rows=12, nb_cols=4)
observed = aug.augment_image(self.image)
assert (
100.0
< np.average(observed[self.mask])
< np.average(self.image[self.mask])
)
assert (
100.0-75.0
> np.average(observed[~self.mask])
> np.average(self.image[~self.mask])
)
def test_scale_is_small_image_absolute_scale(self):
aug = iaa.PiecewiseAffine(scale=1, nb_rows=12, nb_cols=4,
absolute_scale=True)
observed = aug.augment_image(self.image)
assert (
100.0
< np.average(observed[self.mask])
< np.average(self.image[self.mask])
)
assert (
100.0-75.0
> np.average(observed[~self.mask])
> np.average(self.image[~self.mask])
)
def test_scale_is_small_heatmaps(self):
# basic test, heatmaps
aug = iaa.PiecewiseAffine(scale=0.01, nb_rows=12, nb_cols=4)
observed = aug.augment_heatmaps([self.heatmaps])[0]
observed_arr = observed.get_arr()
assert observed.shape == self.heatmaps.shape
_assert_same_min_max(observed, self.heatmaps)
assert (
100.0/255.0
< np.average(observed_arr[self.mask])
< np.average(self.heatmaps.get_arr()[self.mask]))
assert (
(100.0-75.0)/255.0
> np.average(observed_arr[~self.mask])
> np.average(self.heatmaps.get_arr()[~self.mask]))
def test_scale_is_small_segmaps(self):
# basic test, segmaps
aug = iaa.PiecewiseAffine(scale=0.001, nb_rows=12, nb_cols=4)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
observed_arr = observed.get_arr()
# left column starts at 9-11 and right one at 69-71
# result is 9-11 (curvy, i.e. like 50% filled) and 70-71 (straight,
# i.e. 100% filled). Reason for that is unclear, maybe a scikit-image
# problem.
observed_arr_left_col = observed_arr[:, 9:11+1]
observed_arr_right_col = observed_arr[:, 69:71+1]
assert observed.shape == self.segmaps.shape
assert np.average(observed_arr_left_col == 1) > 0.5
assert np.average(observed_arr_right_col == 1) > 0.5
assert np.average(observed_arr[~self.mask] == 0) > 0.9
def test_scale_is_zero_image(self):
# scale 0
aug = iaa.PiecewiseAffine(scale=0, nb_rows=12, nb_cols=4)
observed = aug.augment_image(self.image)
assert np.array_equal(observed, self.image)
def test_scale_is_zero_image_absolute_scale(self):
aug = iaa.PiecewiseAffine(scale=0, nb_rows=12, nb_cols=4,
absolute_scale=True)
observed = aug.augment_image(self.image)
assert np.array_equal(observed, self.image)
def test_scale_is_zero_heatmaps(self):
# scale 0, heatmaps
aug = iaa.PiecewiseAffine(scale=0, nb_rows=12, nb_cols=4)
observed = aug.augment_heatmaps([self.heatmaps])[0]
observed_arr = observed.get_arr()
assert observed.shape == self.heatmaps.shape
_assert_same_min_max(observed, self.heatmaps)
assert np.array_equal(observed_arr, self.heatmaps.get_arr())
def test_scale_is_zero_segmaps(self):
# scale 0, segmaps
aug = iaa.PiecewiseAffine(scale=0, nb_rows=12, nb_cols=4)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
observed_arr = observed.get_arr()
assert observed.shape == self.segmaps.shape
assert np.array_equal(observed_arr, self.segmaps.get_arr())
def test_scale_is_zero_keypoints(self):
# scale 0, keypoints
aug = iaa.PiecewiseAffine(scale=0, nb_rows=12, nb_cols=4)
kps = [ia.Keypoint(x=5, y=3), ia.Keypoint(x=3, y=8)]
kpsoi = ia.KeypointsOnImage(kps, shape=(14, 14, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert_cbaois_equal(kpsoi_aug, kpsoi)
@classmethod
def _test_scale_is_zero_cbaoi(cls, cbaoi, augf_name):
aug = iaa.PiecewiseAffine(scale=0, nb_rows=10, nb_cols=10)
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi)
def test_scale_is_zero_polygons(self):
exterior = [(10, 10),
(70, 10), (70, 20), (70, 30), (70, 40),
(70, 50), (70, 60), (70, 70), (70, 80),
(70, 90),
(10, 90),
(10, 80), (10, 70), (10, 60), (10, 50),
(10, 40), (10, 30), (10, 20), (10, 10)]
poly = ia.Polygon(exterior)
psoi = ia.PolygonsOnImage([poly, poly.shift(x=1, y=1)],
shape=(100, 80))
self._test_scale_is_zero_cbaoi(psoi, "augment_polygons")
def test_scale_is_zero_line_strings(self):
coords = [(10, 10),
(70, 10), (70, 20), (70, 30), (70, 40),
(70, 50), (70, 60), (70, 70), (70, 80),
(70, 90),
(10, 90),
(10, 80), (10, 70), (10, 60), (10, 50),
(10, 40), (10, 30), (10, 20), (10, 10)]
ls = ia.LineString(coords)
lsoi = ia.LineStringsOnImage([ls, ls.shift(x=1, y=1)],
shape=(100, 80))
self._test_scale_is_zero_cbaoi(lsoi, "augment_line_strings")
def test_scale_is_zero_bounding_boxes(self):
bb = ia.BoundingBox(x1=10, y1=10, x2=70, y2=20)
bbsoi = ia.BoundingBoxesOnImage([bb, bb.shift(x=1, y=1)],
shape=(100, 80))
self._test_scale_is_zero_cbaoi(bbsoi, "augment_bounding_boxes")
def test_scale_stronger_values_should_increase_changes_images(self):
# stronger scale should lead to stronger changes
aug1 = iaa.PiecewiseAffine(scale=0.01, nb_rows=12, nb_cols=4)
aug2 = iaa.PiecewiseAffine(scale=0.10, nb_rows=12, nb_cols=4)
observed1 = aug1.augment_image(self.image)
observed2 = aug2.augment_image(self.image)
assert (
np.average(observed1[~self.mask])
< np.average(observed2[~self.mask])
)
def test_scale_stronger_values_should_increase_changes_images_abs(self):
aug1 = iaa.PiecewiseAffine(scale=1, nb_rows=12, nb_cols=4,
absolute_scale=True)
aug2 = iaa.PiecewiseAffine(scale=10, nb_rows=12, nb_cols=4,
absolute_scale=True)
observed1 = aug1.augment_image(self.image)
observed2 = aug2.augment_image(self.image)
assert (
np.average(observed1[~self.mask])
< np.average(observed2[~self.mask])
)
def test_scale_stronger_values_should_increase_changes_heatmaps(self):
# stronger scale should lead to stronger changes, heatmaps
aug1 = iaa.PiecewiseAffine(scale=0.01, nb_rows=12, nb_cols=4)
aug2 = iaa.PiecewiseAffine(scale=0.10, nb_rows=12, nb_cols=4)
observed1 = aug1.augment_heatmaps([self.heatmaps])[0]
observed2 = aug2.augment_heatmaps([self.heatmaps])[0]
observed1_arr = observed1.get_arr()
observed2_arr = observed2.get_arr()
assert observed1.shape == self.heatmaps.shape
assert observed2.shape == self.heatmaps.shape
_assert_same_min_max(observed1, self.heatmaps)
_assert_same_min_max(observed2, self.heatmaps)
assert (
np.average(observed1_arr[~self.mask])
< np.average(observed2_arr[~self.mask])
)
def test_scale_stronger_values_should_increase_changes_heatmaps_abs(self):
aug1 = iaa.PiecewiseAffine(scale=1, nb_rows=12, nb_cols=4,
absolute_scale=True)
aug2 = iaa.PiecewiseAffine(scale=10, nb_rows=12, nb_cols=4,
absolute_scale=True)
observed1 = aug1.augment_heatmaps([self.heatmaps])[0]
observed2 = aug2.augment_heatmaps([self.heatmaps])[0]
observed1_arr = observed1.get_arr()
observed2_arr = observed2.get_arr()
assert observed1.shape == self.heatmaps.shape
assert observed2.shape == self.heatmaps.shape
_assert_same_min_max(observed1, self.heatmaps)
_assert_same_min_max(observed2, self.heatmaps)
assert (
np.average(observed1_arr[~self.mask])
< np.average(observed2_arr[~self.mask])
)
def test_scale_stronger_values_should_increase_changes_segmaps(self):
# stronger scale should lead to stronger changes, segmaps
aug1 = iaa.PiecewiseAffine(scale=0.01, nb_rows=12, nb_cols=4)
aug2 = iaa.PiecewiseAffine(scale=0.10, nb_rows=12, nb_cols=4)
observed1 = aug1.augment_segmentation_maps([self.segmaps])[0]
observed2 = aug2.augment_segmentation_maps([self.segmaps])[0]
observed1_arr = observed1.get_arr()
observed2_arr = observed2.get_arr()
assert observed1.shape == self.segmaps.shape
assert observed2.shape == self.segmaps.shape
assert (
np.average(observed1_arr[~self.mask] == 0)
> np.average(observed2_arr[~self.mask] == 0)
)
def test_scale_alignment_between_images_and_heatmaps(self):
# strong scale, measure alignment between images and heatmaps
aug = iaa.PiecewiseAffine(scale=0.10, nb_rows=12, nb_cols=4)
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(self.image)
hm_aug = aug_det.augment_heatmaps([self.heatmaps])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = hm_aug.arr_0to1 > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert hm_aug.shape == (60, 80, 3)
_assert_same_min_max(hm_aug, self.heatmaps)
assert (same / img_aug_mask.size) >= 0.98
def test_scale_alignment_between_images_and_segmaps(self):
# strong scale, measure alignment between images and segmaps
aug = iaa.PiecewiseAffine(scale=0.10, nb_rows=12, nb_cols=4)
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(self.image)
segmap_aug = aug_det.augment_segmentation_maps([self.segmaps])[0]
img_aug_mask = (img_aug > 255*0.1)
segmap_aug_mask = (segmap_aug.arr == 1)
same = np.sum(img_aug_mask == segmap_aug_mask[:, :, 0])
assert segmap_aug.shape == (60, 80, 3)
assert (same / img_aug_mask.size) >= 0.9
def test_scale_alignment_between_images_and_smaller_heatmaps(self):
# strong scale, measure alignment between images and heatmaps
# heatmaps here smaller than image
aug = iaa.PiecewiseAffine(scale=0.10, nb_rows=12, nb_cols=4)
aug_det = aug.to_deterministic()
heatmaps_small = ia.HeatmapsOnImage(
(
ia.imresize_single_image(
self.image, (30, 40+10), interpolation="cubic"
) / 255.0
).astype(np.float32),
shape=(60, 80, 3)
)
img_aug = aug_det.augment_image(self.image)
hm_aug = aug_det.augment_heatmaps([heatmaps_small])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = ia.imresize_single_image(
hm_aug.arr_0to1, (60, 80), interpolation="cubic"
) > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert hm_aug.shape == (60, 80, 3)
assert hm_aug.arr_0to1.shape == (30, 40+10, 1)
assert (same / img_aug_mask.size) >= 0.9 # seems to be 0.948 actually
def test_scale_alignment_between_images_and_smaller_heatmaps_abs(self):
# image is 60x80, so a scale of 8 is about 0.1*max(60,80)
aug = iaa.PiecewiseAffine(scale=8, nb_rows=12, nb_cols=4,
absolute_scale=True)
aug_det = aug.to_deterministic()
heatmaps_small = ia.HeatmapsOnImage(
(
ia.imresize_single_image(
self.image, (30, 40+10), interpolation="cubic"
) / 255.0
).astype(np.float32),
shape=(60, 80, 3)
)
img_aug = aug_det.augment_image(self.image)
hm_aug = aug_det.augment_heatmaps([heatmaps_small])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = ia.imresize_single_image(
hm_aug.arr_0to1, (60, 80), interpolation="cubic"
) > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert hm_aug.shape == (60, 80, 3)
assert hm_aug.arr_0to1.shape == (30, 40+10, 1)
assert (same / img_aug_mask.size) >= 0.9 # seems to be 0.930 actually
def test_scale_alignment_between_images_and_smaller_segmaps(self):
# strong scale, measure alignment between images and segmaps
# segmaps here smaller than image
aug = iaa.PiecewiseAffine(scale=0.10, nb_rows=12, nb_cols=4)
aug_det = aug.to_deterministic()
segmaps_small = SegmentationMapsOnImage(
(
ia.imresize_single_image(
self.image, (30, 40+10), interpolation="cubic"
) > 100
).astype(np.int32),
shape=(60, 80, 3)
)
img_aug = aug_det.augment_image(self.image)
segmaps_aug = aug_det.augment_segmentation_maps([segmaps_small])[0]
img_aug_mask = img_aug > 255*0.1
segmaps_aug_mask = (
ia.imresize_single_image(
segmaps_aug.arr, (60, 80),
interpolation="nearest"
) == 1
)
same = np.sum(img_aug_mask == segmaps_aug_mask[:, :, 0])
assert segmaps_aug.shape == (60, 80, 3)
assert segmaps_aug.arr.shape == (30, 40+10, 1)
assert (same / img_aug_mask.size) >= 0.9
def test_scale_alignment_between_images_and_keypoints(self):
# strong scale, measure alignment between images and keypoints
# fairly large scale here, as otherwise keypoints can end up
# outside of the image plane
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=12, nb_cols=4)
aug_det = aug.to_deterministic()
kps = [ia.Keypoint(x=160, y=110), ia.Keypoint(x=140, y=90)]
kpsoi = ia.KeypointsOnImage(kps, shape=(200, 300, 3))
img_kps = np.zeros((200, 300, 3), dtype=np.uint8)
img_kps = kpsoi.draw_on_image(img_kps, color=[255, 255, 255])
img_kps_aug = aug_det.augment_image(img_kps)
kpsoi_aug = aug_det.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (200, 300, 3)
bb1 = ia.BoundingBox(
x1=kpsoi_aug.keypoints[0].x-1, y1=kpsoi_aug.keypoints[0].y-1,
x2=kpsoi_aug.keypoints[0].x+1, y2=kpsoi_aug.keypoints[0].y+1)
bb2 = ia.BoundingBox(
x1=kpsoi_aug.keypoints[1].x-1, y1=kpsoi_aug.keypoints[1].y-1,
x2=kpsoi_aug.keypoints[1].x+1, y2=kpsoi_aug.keypoints[1].y+1)
patch1 = bb1.extract_from_image(img_kps_aug)
patch2 = bb2.extract_from_image(img_kps_aug)
assert np.max(patch1) > 150
assert np.max(patch2) > 150
assert np.average(img_kps_aug) < 40
# this test was apparently added later on (?) without noticing that
# a similar test already existed
def test_scale_alignment_between_images_and_keypoints2(self):
img = np.zeros((100, 80), dtype=np.uint8)
img[:, 9:11+1] = 255
img[:, 69:71+1] = 255
kps = [ia.Keypoint(x=10, y=20), ia.Keypoint(x=10, y=40),
ia.Keypoint(x=70, y=20), ia.Keypoint(x=70, y=40)]
kpsoi = ia.KeypointsOnImage(kps, shape=img.shape)
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=10, nb_cols=10)
aug_det = aug.to_deterministic()
observed_img = aug_det.augment_image(img)
observed_kpsoi = aug_det.augment_keypoints([kpsoi])
assert not keypoints_equal([kpsoi], observed_kpsoi)
for kp in observed_kpsoi[0].keypoints:
assert observed_img[int(kp.y), int(kp.x)] > 0
@classmethod
def _test_scale_alignment_between_images_and_poly_or_line_strings(
cls, cba_class, cbaoi_class, augf_name):
img = np.zeros((100, 80), dtype=np.uint8)
img[:, 10-5:10+5] = 255
img[:, 70-5:70+5] = 255
coords = [(10, 10),
(70, 10), (70, 20), (70, 30), (70, 40),
(70, 50), (70, 60), (70, 70), (70, 80),
(70, 90),
(10, 90),
(10, 80), (10, 70), (10, 60), (10, 50),
(10, 40), (10, 30), (10, 20), (10, 10)]
cba = cba_class(coords)
cbaoi = cbaoi_class([cba, cba.shift(x=1, y=1)],
shape=img.shape)
aug = iaa.PiecewiseAffine(scale=0.03, nb_rows=10, nb_cols=10)
aug_det = aug.to_deterministic()
observed_imgs = aug_det.augment_images([img, img])
observed_cbaois = getattr(aug_det, augf_name)([cbaoi, cbaoi])
for observed_img, observed_cbaoi in zip(observed_imgs, observed_cbaois):
assert observed_cbaoi.shape == img.shape
for cba_aug in observed_cbaoi.items:
if hasattr(cba_aug, "is_valid"):
assert cba_aug.is_valid
for point_aug in cba_aug.coords:
x = int(np.round(point_aug[0]))
y = int(np.round(point_aug[1]))
assert observed_img[y, x] > 0
def test_scale_alignment_between_images_and_polygons(self):
self._test_scale_alignment_between_images_and_poly_or_line_strings(
ia.Polygon, ia.PolygonsOnImage, "augment_polygons")
def test_scale_alignment_between_images_and_line_strings(self):
self._test_scale_alignment_between_images_and_poly_or_line_strings(
ia.LineString, ia.LineStringsOnImage, "augment_line_strings")
def test_scale_alignment_between_images_and_bounding_boxes(self):
img = np.zeros((100, 80), dtype=np.uint8)
s = 0
img[10-s:10+s+1, 20-s:20+s+1] = 255
img[60-s:60+s+1, 70-s:70+s+1] = 255
bb = ia.BoundingBox(y1=10, x1=20, y2=60, x2=70)
bbsoi = ia.BoundingBoxesOnImage([bb], shape=img.shape)
aug = iaa.PiecewiseAffine(scale=0.03, nb_rows=10, nb_cols=10)
observed_imgs, observed_bbsois = aug(
images=[img], bounding_boxes=[bbsoi])
for observed_img, observed_bbsoi in zip(observed_imgs, observed_bbsois):
assert observed_bbsoi.shape == img.shape
observed_img_x = np.max(observed_img, axis=0)
observed_img_y = np.max(observed_img, axis=1)
nonz_x = np.nonzero(observed_img_x)[0]
nonz_y = np.nonzero(observed_img_y)[0]
img_x1 = min(nonz_x)
img_x2 = max(nonz_x)
img_y1 = min(nonz_y)
img_y2 = max(nonz_y)
expected = ia.BoundingBox(x1=img_x1, y1=img_y1,
x2=img_x2, y2=img_y2)
for bb_aug in observed_bbsoi.bounding_boxes:
# we don't expect perfect IoU here, because the actual
# underlying KP aug used distance maps
# most IoUs seem to end up in the range 0.9-0.95
assert bb_aug.iou(expected) > 0.8
def test_scale_is_list(self):
aug1 = iaa.PiecewiseAffine(scale=0.01, nb_rows=12, nb_cols=4)
aug2 = iaa.PiecewiseAffine(scale=0.10, nb_rows=12, nb_cols=4)
aug = iaa.PiecewiseAffine(scale=[0.01, 0.10], nb_rows=12, nb_cols=4)
avg1 = np.average([
np.average(
aug1.augment_image(self.image)
* (~self.mask).astype(np.float32)
)
for _ in sm.xrange(3)
])
avg2 = np.average([
np.average(
aug2.augment_image(self.image)
* (~self.mask).astype(np.float32)
)
for _ in sm.xrange(3)
])
seen = [0, 0]
for _ in sm.xrange(15):
observed = aug.augment_image(self.image)
avg = np.average(observed * (~self.mask).astype(np.float32))
diff1 = abs(avg - avg1)
diff2 = abs(avg - avg2)
if diff1 < diff2:
seen[0] += 1
else:
seen[1] += 1
assert seen[0] > 0
assert seen[1] > 0
# -----
# rows and cols
# -----
@classmethod
def _compute_observed_std_ygrad_in_mask(cls, observed, mask):
grad_vert = (
observed[1:, :].astype(np.float32)
- observed[:-1, :].astype(np.float32)
)
grad_vert = grad_vert * (~mask[1:, :]).astype(np.float32)
return np.std(grad_vert)
def _compute_std_ygrad_in_mask(self, aug, image, mask, nb_iterations):
stds = []
for _ in sm.xrange(nb_iterations):
observed = aug.augment_image(image)
stds.append(
self._compute_observed_std_ygrad_in_mask(observed, mask)
)
return np.average(stds)
def test_nb_rows_affects_images(self):
# verify effects of rows
aug1 = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=4)
aug2 = iaa.PiecewiseAffine(scale=0.05, nb_rows=30, nb_cols=4)
std1 = self._compute_std_ygrad_in_mask(aug1, self.image, self.mask, 3)
std2 = self._compute_std_ygrad_in_mask(aug2, self.image, self.mask, 3)
assert std1 < std2
def test_nb_rows_is_list_affects_images(self):
# rows as list
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=[4, 20], nb_cols=4)
aug1 = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=4)
aug2 = iaa.PiecewiseAffine(scale=0.05, nb_rows=30, nb_cols=4)
std1 = self._compute_std_ygrad_in_mask(aug1, self.image, self.mask, 3)
std2 = self._compute_std_ygrad_in_mask(aug2, self.image, self.mask, 3)
seen = [0, 0]
for _ in sm.xrange(20):
observed = aug.augment_image(self.image)
std = self._compute_observed_std_ygrad_in_mask(observed, self.mask)
diff1 = abs(std - std1)
diff2 = abs(std - std2)
if diff1 < diff2:
seen[0] += 1
else:
seen[1] += 1
assert seen[0] > 0
assert seen[1] > 0
def test_nb_cols_affects_images(self):
# verify effects of cols
image = self.image.T
mask = self.mask.T
aug1 = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=4)
aug2 = iaa.PiecewiseAffine(scale=0.05, nb_rows=20, nb_cols=4)
std1 = self._compute_std_ygrad_in_mask(aug1, image, mask, 3)
std2 = self._compute_std_ygrad_in_mask(aug2, image, mask, 3)
assert std1 < std2
def test_nb_cols_is_list_affects_images(self):
# cols as list
image = self.image.T
mask = self.mask.T
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=[4, 20])
aug1 = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=4)
aug2 = iaa.PiecewiseAffine(scale=0.05, nb_rows=4, nb_cols=30)
std1 = self._compute_std_ygrad_in_mask(aug1, image, mask, 3)
std2 = self._compute_std_ygrad_in_mask(aug2, image, mask, 3)
seen = [0, 0]
for _ in sm.xrange(20):
observed = aug.augment_image(image)
std = self._compute_observed_std_ygrad_in_mask(observed, mask)
diff1 = abs(std - std1)
diff2 = abs(std - std2)
if diff1 < diff2:
seen[0] += 1
else:
seen[1] += 1
assert seen[0] > 0
assert seen[1] > 0
# -----
# order
# -----
# TODO
# -----
# cval
# -----
def test_cval_is_zero(self):
# since scikit-image 0.16.2 and scipy 1.4.0(!), this test requires
# several iterations to find one image that required filling with cval
found = False
for _ in np.arange(50):
img = np.zeros((16, 16, 3), dtype=np.uint8) + 255
aug = iaa.PiecewiseAffine(scale=0.7, nb_rows=10, nb_cols=10,
mode="constant", cval=0)
observed = aug.augment_image(img)
if np.sum([observed[:, :] == [0, 0, 0]]) > 0:
found = True
break
assert found
def test_cval_should_be_ignored_by_heatmaps(self):
# cval as deterministic, heatmaps should always use cval=0
heatmaps = HeatmapsOnImage(
np.zeros((50, 50, 1), dtype=np.float32), shape=(50, 50, 3))
aug = iaa.PiecewiseAffine(scale=0.7, nb_rows=10, nb_cols=10,
mode="constant", cval=255)
observed = aug.augment_heatmaps([heatmaps])[0]
assert np.sum([observed.get_arr()[:, :] >= 0.01]) == 0
def test_cval_should_be_ignored_by_segmaps(self):
# cval as deterministic, segmaps should always use cval=0
segmaps = SegmentationMapsOnImage(
np.zeros((50, 50, 1), dtype=np.int32), shape=(50, 50, 3))
aug = iaa.PiecewiseAffine(scale=0.7, nb_rows=10, nb_cols=10,
mode="constant", cval=255)
observed = aug.augment_segmentation_maps([segmaps])[0]
assert np.sum([observed.get_arr()[:, :] > 0]) == 0
def test_cval_is_list(self):
# cval as list
img = np.zeros((20, 20), dtype=np.uint8) + 255
aug = iaa.PiecewiseAffine(scale=0.7, nb_rows=5, nb_cols=5,
mode="constant", cval=[0, 10])
seen = [0, 0, 0]
for _ in sm.xrange(30):
observed = aug.augment_image(img)
nb_0 = np.sum([observed[:, :] == 0])
nb_10 = np.sum([observed[:, :] == 10])
if nb_0 > 0:
seen[0] += 1
elif nb_10 > 0:
seen[1] += 1
else:
seen[2] += 1
assert seen[0] > 5
assert seen[1] > 5
assert seen[2] <= 4
# -----
# mode
# -----
# TODO
# ---------
# remaining keypoints tests
# ---------
def test_keypoints_outside_of_image(self):
# keypoints outside of image
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=10, nb_cols=10)
kps = [ia.Keypoint(x=-10, y=-20)]
kpsoi = ia.KeypointsOnImage(kps, shape=(10, 10, 3))
observed = aug.augment_keypoints(kpsoi)
assert_cbaois_equal(observed, kpsoi)
def test_keypoints_empty(self):
# empty keypoints
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=10, nb_cols=10)
kpsoi = ia.KeypointsOnImage([], shape=(10, 10, 3))
observed = aug.augment_keypoints(kpsoi)
assert_cbaois_equal(observed, kpsoi)
# ---------
# remaining polygons tests
# ---------
def test_polygons_outside_of_image(self):
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=10, nb_cols=10)
exterior = [(-10, -10), (110, -10), (110, 90), (-10, 90)]
poly = ia.Polygon(exterior)
psoi = ia.PolygonsOnImage([poly], shape=(10, 10, 3))
observed = aug.augment_polygons(psoi)
assert_cbaois_equal(observed, psoi)
def test_empty_polygons(self):
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=10, nb_cols=10)
psoi = ia.PolygonsOnImage([], shape=(10, 10, 3))
observed = aug.augment_polygons(psoi)
assert_cbaois_equal(observed, psoi)
# ---------
# remaining line string tests
# ---------
def test_line_strings_outside_of_image(self):
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=10, nb_cols=10)
coords = [(-10, -10), (110, -10), (110, 90), (-10, 90)]
ls = ia.LineString(coords)
lsoi = ia.LineStringsOnImage([ls], shape=(10, 10, 3))
observed = aug.augment_line_strings(lsoi)
assert_cbaois_equal(observed, lsoi)
def test_empty_line_strings(self):
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=10, nb_cols=10)
lsoi = ia.LineStringsOnImage([], shape=(10, 10, 3))
observed = aug.augment_line_strings(lsoi)
assert_cbaois_equal(observed, lsoi)
# ---------
# remaining bounding box tests
# ---------
def test_bounding_boxes_outside_of_image(self):
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=10, nb_cols=10)
bbs = ia.BoundingBox(x1=-10, y1=-10, x2=15, y2=15)
bbsoi = ia.BoundingBoxesOnImage([bbs], shape=(10, 10, 3))
observed = aug.augment_bounding_boxes(bbsoi)
assert_cbaois_equal(observed, bbsoi)
def test_empty_bounding_boxes(self):
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=10, nb_cols=10)
bbsoi = ia.BoundingBoxesOnImage([], shape=(10, 10, 3))
observed = aug.augment_bounding_boxes(bbsoi)
assert_cbaois_equal(observed, bbsoi)
# ---------
# zero-sized axes
# ---------
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PiecewiseAffine(scale=0.05, nb_rows=2, nb_cols=2)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_zero_sized_axes_absolute_scale(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PiecewiseAffine(scale=5, nb_rows=2, nb_cols=2,
absolute_scale=True)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
# ---------
# other methods
# ---------
def test_get_parameters(self):
aug = iaa.PiecewiseAffine(scale=0.1, nb_rows=8, nb_cols=10, order=1,
cval=2, mode="constant",
absolute_scale=False)
params = aug.get_parameters()
assert params[0] is aug.jitter.scale
assert params[1] is aug.nb_rows
assert params[2] is aug.nb_cols
assert params[3] is aug.order
assert params[4] is aug.cval
assert params[5] is aug.mode
assert params[6] is False
assert 0.1 - 1e-8 < params[0].value < 0.1 + 1e-8
assert params[1].value == 8
assert params[2].value == 10
assert params[3].value == 1
assert params[4].value == 2
assert params[5].value == "constant"
# ---------
# other dtypes
# ---------
@property
def other_dtypes_mask(self):
mask = np.zeros((21, 21), dtype=bool)
mask[:, 7:13] = True
return mask
def test_other_dtypes_bool(self):
aug = iaa.PiecewiseAffine(scale=0.2, nb_rows=8, nb_cols=4, order=0,
mode="constant")
image = np.zeros((21, 21), dtype=bool)
image[self.other_dtypes_mask] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert not np.all(image_aug == 1)
assert np.any(image_aug[~self.other_dtypes_mask] == 1)
def test_other_dtypes_uint_int(self):
aug = iaa.PiecewiseAffine(scale=0.2, nb_rows=8, nb_cols=4, order=0,
mode="constant")
dtypes = ["uint8", "uint16", "uint32", "int8", "int16", "int32"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value-100, max_value]
values = values + [(-1)*value for value in values]
else:
values = [1, 5, 10, 100, int(center_value),
int(0.1 * max_value), int(0.2 * max_value),
int(0.5 * max_value), max_value-100, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((21, 21), dtype=dtype)
image[:, 7:13] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert not np.all(image_aug == value)
assert np.any(image_aug[~self.other_dtypes_mask] == value)
def test_other_dtypes_float(self):
aug = iaa.PiecewiseAffine(scale=0.2, nb_rows=8, nb_cols=4, order=0,
mode="constant")
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [
0.01,
1.0,
10.0,
100.0,
500 ** (isize - 1),
float(np.float64(1000 ** (isize - 1)))
]
values = values + [(-1) * value for value in values]
values = values + [min_value, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((21, 21), dtype=dtype)
image[:, 7:13] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert not np.all(_isclose(image_aug, value))
assert np.any(_isclose(image_aug[~self.other_dtypes_mask],
value))
def test_pickleable(self):
aug = iaa.PiecewiseAffine(scale=0.2, nb_rows=4, nb_cols=4, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(25, 25, 1))
class TestPerspectiveTransform(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
img = np.zeros((30, 30), dtype=np.uint8)
img[10:20, 10:20] = 255
return img
@property
def heatmaps(self):
return HeatmapsOnImage((self.image / 255.0).astype(np.float32),
shape=self.image.shape)
@property
def segmaps(self):
return SegmentationMapsOnImage((self.image > 0).astype(np.int32),
shape=self.image.shape)
# --------
# __init__
# --------
def test___init___scale_is_tuple(self):
# tuple for scale
aug = iaa.PerspectiveTransform(scale=(0.1, 0.2))
assert is_parameter_instance(aug.jitter.scale, iap.Uniform)
assert is_parameter_instance(aug.jitter.scale.a, iap.Deterministic)
assert is_parameter_instance(aug.jitter.scale.b, iap.Deterministic)
assert 0.1 - 1e-8 < aug.jitter.scale.a.value < 0.1 + 1e-8
assert 0.2 - 1e-8 < aug.jitter.scale.b.value < 0.2 + 1e-8
def test___init___scale_is_list(self):
# list for scale
aug = iaa.PerspectiveTransform(scale=[0.1, 0.2, 0.3])
assert is_parameter_instance(aug.jitter.scale, iap.Choice)
assert len(aug.jitter.scale.a) == 3
assert 0.1 - 1e-8 < aug.jitter.scale.a[0] < 0.1 + 1e-8
assert 0.2 - 1e-8 < aug.jitter.scale.a[1] < 0.2 + 1e-8
assert 0.3 - 1e-8 < aug.jitter.scale.a[2] < 0.3 + 1e-8
def test___init___scale_is_stochastic_parameter(self):
# StochasticParameter for scale
aug = iaa.PerspectiveTransform(scale=iap.Choice([0.1, 0.2, 0.3]))
assert is_parameter_instance(aug.jitter.scale, iap.Choice)
assert len(aug.jitter.scale.a) == 3
assert 0.1 - 1e-8 < aug.jitter.scale.a[0] < 0.1 + 1e-8
assert 0.2 - 1e-8 < aug.jitter.scale.a[1] < 0.2 + 1e-8
assert 0.3 - 1e-8 < aug.jitter.scale.a[2] < 0.3 + 1e-8
def test___init___bad_datatype_for_scale_leads_to_failure(self):
# bad datatype for scale
got_exception = False
try:
_ = iaa.PerspectiveTransform(scale=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___mode_is_all(self):
aug = iaa.PerspectiveTransform(cval=0, mode=ia.ALL)
assert is_parameter_instance(aug.mode, iap.Choice)
def test___init___mode_is_string(self):
aug = iaa.PerspectiveTransform(cval=0, mode="replicate")
assert is_parameter_instance(aug.mode, iap.Deterministic)
assert aug.mode.value == "replicate"
def test___init___mode_is_list(self):
aug = iaa.PerspectiveTransform(cval=0, mode=["replicate", "constant"])
assert is_parameter_instance(aug.mode, iap.Choice)
assert (
len(aug.mode.a) == 2
and "replicate" in aug.mode.a
and "constant" in aug.mode.a)
def test___init___mode_is_stochastic_parameter(self):
aug = iaa.PerspectiveTransform(
cval=0, mode=iap.Choice(["replicate", "constant"]))
assert is_parameter_instance(aug.mode, iap.Choice)
assert (
len(aug.mode.a) == 2
and "replicate" in aug.mode.a
and "constant" in aug.mode.a)
# --------
# image, heatmaps, segmaps
# --------
def test_image_without_keep_size(self):
# without keep_size
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
observed = aug.augment_image(self.image)
y1 = int(30*0.2)
y2 = int(30*0.8)
x1 = int(30*0.2)
x2 = int(30*0.8)
expected = self.image[y1:y2, x1:x2]
assert all([
abs(s1-s2) <= 1 for s1, s2 in zip(observed.shape, expected.shape)
])
if observed.shape != expected.shape:
observed = ia.imresize_single_image(
observed, expected.shape[0:2], interpolation="cubic")
# differences seem to mainly appear around the border of the inner
# rectangle, possibly due to interpolation
assert np.average(
np.abs(observed.astype(np.int32) - expected.astype(np.int32))
) < 30.0
def test_image_heatmaps_alignment_without_keep_size(self):
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
hm = HeatmapsOnImage(
self.image.astype(np.float32)/255.0,
shape=(30, 30)
)
observed = aug.augment_image(self.image)
hm_aug = aug.augment_heatmaps([hm])[0]
y1 = int(30*0.2)
y2 = int(30*0.8)
x1 = int(30*0.2)
x2 = int(30*0.8)
expected = (y2 - y1, x2 - x1)
assert all([
abs(s1-s2) <= 1
for s1, s2
in zip(hm_aug.shape, expected)
])
assert all([
abs(s1-s2) <= 1
for s1, s2
in zip(hm_aug.arr_0to1.shape, expected + (1,))
])
img_aug_mask = observed > 255*0.1
hm_aug_mask = hm_aug.arr_0to1 > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert (same / img_aug_mask.size) >= 0.99
def test_image_segmaps_alignment_without_keep_size(self):
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
segmaps = SegmentationMapsOnImage(
(self.image > 100).astype(np.int32),
shape=(30, 30)
)
observed = aug.augment_image(self.image)
segmaps_aug = aug.augment_segmentation_maps([segmaps])[0]
y1 = int(30*0.2)
y2 = int(30*0.8)
x1 = int(30*0.2)
x2 = int(30*0.8)
expected = (y2 - y1, x2 - x1)
assert all([
abs(s1-s2) <= 1
for s1, s2
in zip(segmaps_aug.shape, expected)
])
assert all([
abs(s1-s2) <= 1
for s1, s2
in zip(segmaps_aug.arr.shape, expected + (1,))
])
img_aug_mask = observed > 255*0.5
segmaps_aug_mask = segmaps_aug.arr > 0
same = np.sum(img_aug_mask == segmaps_aug_mask[:, :, 0])
assert (same / img_aug_mask.size) >= 0.99
def test_consecutive_calls_produce_different_results(self):
# PerspectiveTransform works with random_state.copy(), so we
# test explicitly that it doesn't always use the same samples
aug = iaa.PerspectiveTransform((0.0, 0.2))
image = np.mod(np.arange(16*16), 255).astype(np.uint8).reshape((16, 16))
nb_same = 0
last_image = aug(image=image)
for _ in np.arange(100):
image_aug = aug(image=image)
nb_same += int(np.array_equal(image_aug, last_image))
assert nb_same <= 1
def test_heatmaps_smaller_than_image_without_keep_size(self):
# without keep_size, different heatmap size
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
height, width = 300, 200
height_small, width_small = 150, 100
y1 = int(height*0.2)
y2 = int(height*0.8)
x1 = int(width*0.2)
x2 = int(width*0.8)
y1_small = int(height_small*0.2)
y2_small = int(height_small*0.8)
x1_small = int(width_small*0.2)
x2_small = int(width_small*0.8)
img_small = ia.imresize_single_image(
self.image,
(height_small, width_small),
interpolation="cubic")
hm = ia.HeatmapsOnImage(
img_small.astype(np.float32)/255.0,
shape=(height, width))
img_aug = aug.augment_image(self.image)
hm_aug = aug.augment_heatmaps([hm])[0]
expected = (y2 - y1, x2 - x1)
expected_small = (y2_small - y1_small, x2_small - x1_small, 1)
assert all([
abs(s1-s2) <= 1
for s1, s2
in zip(hm_aug.shape, expected)
])
assert all([
abs(s1-s2) <= 1
for s1, s2
in zip(hm_aug.arr_0to1.shape, expected_small)
])
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = ia.imresize_single_image(
hm_aug.arr_0to1, img_aug.shape[0:2], interpolation="linear"
) > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert (same / img_aug_mask.size) >= 0.96
def test_segmaps_smaller_than_image_without_keep_size(self):
# without keep_size, different segmap size
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
y1 = int(30*0.2)
y2 = int(30*0.8)
x1 = int(30*0.2)
x2 = int(30*0.8)
x1_small = int(25*0.2)
x2_small = int(25*0.8)
y1_small = int(20*0.2)
y2_small = int(20*0.8)
img_small = ia.imresize_single_image(
self.image,
(20, 25),
interpolation="cubic")
seg = SegmentationMapsOnImage(
(img_small > 100).astype(np.int32),
shape=(30, 30))
img_aug = aug.augment_image(self.image)
seg_aug = aug.augment_segmentation_maps([seg])[0]
expected = (y2 - y1, x2 - x1)
expected_small = (y2_small - y1_small, x2_small - x1_small, 1)
assert all([
abs(s1-s2) <= 1
for s1, s2
in zip(seg_aug.shape, expected)
])
assert all([
abs(s1-s2) <= 1
for s1, s2
in zip(seg_aug.arr.shape, expected_small)
])
img_aug_mask = img_aug > 255*0.5
seg_aug_mask = ia.imresize_single_image(
seg_aug.arr, img_aug.shape[0:2], interpolation="nearest") > 0
same = np.sum(img_aug_mask == seg_aug_mask[:, :, 0])
assert (same / img_aug_mask.size) >= 0.92
def test_image_with_keep_size(self):
# with keep_size
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
aug.jitter = iap.Deterministic(0.2)
observed = aug.augment_image(self.image)
expected = self.image[int(30*0.2):int(30*0.8),
int(30*0.2):int(30*0.8)]
expected = ia.imresize_single_image(
expected,
self.image.shape[0:2],
interpolation="cubic")
assert observed.shape == self.image.shape
# differences seem to mainly appear around the border of the inner
# rectangle, possibly due to interpolation
assert np.average(
np.abs(observed.astype(np.int32) - expected.astype(np.int32))
) < 30.0
def test_heatmaps_with_keep_size(self):
# with keep_size, heatmaps
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
aug.jitter = iap.Deterministic(0.2)
observed = aug.augment_heatmaps([self.heatmaps])[0]
heatmaps_arr = self.heatmaps.get_arr()
expected = heatmaps_arr[int(30*0.2):int(30*0.8),
int(30*0.2):int(30*0.8)]
expected = ia.imresize_single_image(
(expected*255).astype(np.uint8),
self.image.shape[0:2],
interpolation="cubic")
expected = (expected / 255.0).astype(np.float32)
assert observed.shape == self.heatmaps.shape
_assert_same_min_max(observed, self.heatmaps)
# differences seem to mainly appear around the border of the inner
# rectangle, possibly due to interpolation
assert np.average(np.abs(observed.get_arr() - expected)) < 30.0
def test_segmaps_with_keep_size(self):
# with keep_size, segmaps
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
aug.jitter = iap.Deterministic(0.2)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
segmaps_arr = self.segmaps.get_arr()
expected = segmaps_arr[int(30*0.2):int(30*0.8),
int(30*0.2):int(30*0.8)]
expected = ia.imresize_single_image(
(expected*255).astype(np.uint8),
self.image.shape[0:2],
interpolation="cubic")
expected = (expected > 255*0.5).astype(np.int32)
assert observed.shape == self.segmaps.shape
assert np.average(observed.get_arr() != expected) < 0.05
def test_image_rgb_with_keep_size(self):
# with keep_size, RGB images
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
aug.jitter = iap.Deterministic(0.2)
imgs = np.tile(self.image[np.newaxis, :, :, np.newaxis], (2, 1, 1, 3))
observed = aug.augment_images(imgs)
for img_idx in sm.xrange(2):
for c in sm.xrange(3):
observed_i = observed[img_idx, :, :, c]
expected = imgs[img_idx,
int(30*0.2):int(30*0.8),
int(30*0.2):int(30*0.8),
c]
expected = ia.imresize_single_image(
expected, imgs.shape[1:3], interpolation="cubic")
assert observed_i.shape == imgs.shape[1:3]
# differences seem to mainly appear around the border of the
# inner rectangle, possibly due to interpolation
assert np.average(
np.abs(
observed_i.astype(np.int32) - expected.astype(np.int32)
)
) < 30.0
# --------
# keypoints
# --------
def test_keypoints_without_keep_size(self):
# keypoint augmentation without keep_size
# TODO deviations of around 0.4-0.7 in this and the next test (between
# expected and observed coordinates) -- why?
kps = [ia.Keypoint(x=10, y=10), ia.Keypoint(x=14, y=11)]
kpsoi = ia.KeypointsOnImage(kps, shape=self.image.shape)
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
observed = aug.augment_keypoints([kpsoi])
kps_expected = [
ia.Keypoint(x=10-0.2*30, y=10-0.2*30),
ia.Keypoint(x=14-0.2*30, y=11-0.2*30)
]
gen = zip(observed[0].keypoints, kps_expected)
# TODO deviations of around 0.5 here from expected values, why?
for kp_observed, kp_expected in gen:
assert kp_observed.coords_almost_equals(
kp_expected, max_distance=1.5)
def test_keypoints_with_keep_size(self):
# keypoint augmentation with keep_size
kps = [ia.Keypoint(x=10, y=10), ia.Keypoint(x=14, y=11)]
kpsoi = ia.KeypointsOnImage(kps, shape=self.image.shape)
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
aug.jitter = iap.Deterministic(0.2)
observed = aug.augment_keypoints([kpsoi])
kps_expected = [
ia.Keypoint(x=((10-0.2*30)/(30*0.6))*30,
y=((10-0.2*30)/(30*0.6))*30),
ia.Keypoint(x=((14-0.2*30)/(30*0.6))*30,
y=((11-0.2*30)/(30*0.6))*30)
]
gen = zip(observed[0].keypoints, kps_expected)
# TODO deviations of around 0.5 here from expected values, why?
for kp_observed, kp_expected in gen:
assert kp_observed.coords_almost_equals(
kp_expected, max_distance=1.5)
def test_image_keypoint_alignment(self):
img = np.zeros((100, 100), dtype=np.uint8)
img[25-3:25+3, 25-3:25+3] = 255
img[50-3:50+3, 25-3:25+3] = 255
img[75-3:75+3, 25-3:25+3] = 255
img[25-3:25+3, 75-3:75+3] = 255
img[50-3:50+3, 75-3:75+3] = 255
img[75-3:75+3, 75-3:75+3] = 255
img[50-3:75+3, 50-3:75+3] = 255
kps = [
ia.Keypoint(y=25, x=25), ia.Keypoint(y=50, x=25),
ia.Keypoint(y=75, x=25), ia.Keypoint(y=25, x=75),
ia.Keypoint(y=50, x=75), ia.Keypoint(y=75, x=75),
ia.Keypoint(y=50, x=50)
]
kpsoi = ia.KeypointsOnImage(kps, shape=img.shape)
aug = iaa.PerspectiveTransform(scale=(0.05, 0.15), keep_size=True)
for _ in sm.xrange(10):
aug_det = aug.to_deterministic()
imgs_aug = aug_det.augment_images([img, img])
kpsois_aug = aug_det.augment_keypoints([kpsoi, kpsoi])
for img_aug, kpsoi_aug in zip(imgs_aug, kpsois_aug):
assert kpsoi_aug.shape == img.shape
for kp_aug in kpsoi_aug.keypoints:
x, y = int(np.round(kp_aug.x)), int(np.round(kp_aug.y))
if 0 <= x < img.shape[1] and 0 <= y < img.shape[0]:
assert img_aug[y, x] > 10
def test_empty_keypoints(self):
# test empty keypoints
kpsoi = ia.KeypointsOnImage([], shape=(20, 10, 3))
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
observed = aug.augment_keypoints(kpsoi)
assert_cbaois_equal(observed, kpsoi)
# --------
# abstract test methods for polygons and line strings
# --------
@classmethod
def _test_cbaois_without_keep_size(cls, cba_class, cbaoi_class, augf_name):
points = np.float32([
[10, 10],
[25, 10],
[25, 25],
[10, 25]
])
cbaoi = cbaoi_class([cba_class(points)], shape=(30, 30, 3))
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
observed = getattr(aug, augf_name)(cbaoi)
assert observed.shape == (30 - 12, 30 - 12, 3)
assert len(observed.items) == 1
if hasattr(observed.items[0], "is_valid"):
assert observed.items[0].is_valid
points_expected = np.copy(points)
points_expected[:, 0] -= 0.2 * 30
points_expected[:, 1] -= 0.2 * 30
# TODO deviations of around 0.5 here from expected values, why?
assert observed.items[0].coords_almost_equals(
points_expected, max_distance=1.5)
@classmethod
def _test_cbaois_with_keep_size(cls, cba_class, cbaoi_class, augf_name):
# polygon augmentation with keep_size
points = np.float32([
[10, 10],
[25, 10],
[25, 25],
[10, 25]
])
cbaoi = cbaoi_class([cba_class(points)], shape=(30, 30, 3))
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
aug.jitter = iap.Deterministic(0.2)
observed = getattr(aug, augf_name)(cbaoi)
assert observed.shape == (30, 30, 3)
assert len(observed.items) == 1
if hasattr(observed.items[0], "is_valid"):
assert observed.items[0].is_valid
points_expected = np.copy(points)
points_expected[:, 0] = (
(points_expected[:, 0] - 0.2 * 30) / (30 * 0.6)
) * 30
points_expected[:, 1] = (
(points_expected[:, 1] - 0.2 * 30) / (30 * 0.6)
) * 30
# TODO deviations of around 0.5 here from expected values, why?
assert observed.items[0].coords_almost_equals(
points_expected, max_distance=2.5)
@classmethod
def _test_image_cba_alignment(cls, cba_class, cbaoi_class, augf_name):
img = np.zeros((100, 100), dtype=np.uint8)
img[25-3:25+3, 25-3:25+3] = 255
img[50-3:50+3, 25-3:25+3] = 255
img[75-3:75+3, 25-3:25+3] = 255
img[25-3:25+3, 75-3:75+3] = 255
img[50-3:50+3, 75-3:75+3] = 255
img[75-3:75+3, 75-3:75+3] = 255
points = [
[25, 25],
[75, 25],
[75, 50],
[75, 75],
[25, 75],
[25, 50]
]
cbaoi = cbaoi_class([cba_class(points)], shape=img.shape)
aug = iaa.PerspectiveTransform(scale=0.1, keep_size=True)
for _ in sm.xrange(10):
aug_det = aug.to_deterministic()
imgs_aug = aug_det.augment_images([img] * 4)
cbaois_aug = getattr(aug_det, augf_name)([cbaoi] * 4)
for img_aug, cbaoi_aug in zip(imgs_aug, cbaois_aug):
assert cbaoi_aug.shape == img.shape
for cba_aug in cbaoi_aug.items:
if hasattr(cba_aug, "is_valid"):
assert cba_aug.is_valid
for x, y in cba_aug.coords:
if 0 <= x < img.shape[1] and 0 <= y < img.shape[0]:
bb = ia.BoundingBox(x1=x-2, x2=x+2, y1=y-2, y2=y+2)
img_ex = bb.extract_from_image(img_aug)
assert np.any(img_ex > 10)
@classmethod
def _test_empty_cba(cls, cbaoi, augf_name):
# test empty polygons
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi)
# --------
# polygons
# --------
def test_polygons_without_keep_size(self):
self._test_cbaois_without_keep_size(ia.Polygon, ia.PolygonsOnImage,
"augment_polygons")
def test_polygons_with_keep_size(self):
self._test_cbaois_with_keep_size(ia.Polygon, ia.PolygonsOnImage,
"augment_polygons")
def test_image_polygon_alignment(self):
self._test_image_cba_alignment(ia.Polygon, ia.PolygonsOnImage,
"augment_polygons")
def test_empty_polygons(self):
psoi = ia.PolygonsOnImage([], shape=(20, 10, 3))
self._test_empty_cba(psoi, "augment_polygons")
def test_polygons_under_extreme_scale_values(self):
# test extreme scales
# TODO when setting .min_height and .min_width in PerspectiveTransform
# to 1x1, at least one of the output polygons was invalid and had
# only 3 instead of the expected 4 points - why?
for scale in [0.1, 0.2, 0.3, 0.4]:
with self.subTest(scale=scale):
exterior = np.float32([
[10, 10],
[25, 10],
[25, 25],
[10, 25]
])
psoi = ia.PolygonsOnImage([ia.Polygon(exterior)],
shape=(30, 30, 3))
aug = iaa.PerspectiveTransform(scale=scale, keep_size=True)
aug.jitter = iap.Deterministic(scale)
observed = aug.augment_polygons(psoi)
assert observed.shape == (30, 30, 3)
assert len(observed.polygons) == 1
assert observed.polygons[0].is_valid
# FIXME this part is currently deactivated due to too large
# deviations from expectations. As the alignment check
# works, this is probably some error on the test side
"""
exterior_expected = np.copy(exterior)
exterior_expected[:, 0] = (
(exterior_expected[:, 0] - scale * 30) / (30*(1-2*scale))
) * 30
exterior_expected[:, 1] = (
(exterior_expected[:, 1] - scale * 30) / (30*(1-2*scale))
) * 30
poly0 = observed.polygons[0]
# TODO deviations of around 0.5 here from expected values, why?
assert poly0.exterior_almost_equals(
exterior_expected, max_distance=2.0)
"""
# --------
# line strings
# --------
def test_line_strings_without_keep_size(self):
self._test_cbaois_without_keep_size(ia.LineString, ia.LineStringsOnImage,
"augment_line_strings")
def test_line_strings_with_keep_size(self):
self._test_cbaois_with_keep_size(ia.LineString, ia.LineStringsOnImage,
"augment_line_strings")
def test_image_line_string_alignment(self):
self._test_image_cba_alignment(ia.LineString, ia.LineStringsOnImage,
"augment_line_strings")
def test_empty_line_strings(self):
lsoi = ia.LineStringsOnImage([], shape=(20, 10, 3))
self._test_empty_cba(lsoi, "augment_line_strings")
# --------
# bounding boxes
# --------
def test_bounding_boxes_without_keep_size(self):
# BB augmentation without keep_size
# TODO deviations of around 0.4-0.7 in this and the next test (between
# expected and observed coordinates) -- why?
bbs = [ia.BoundingBox(x1=0, y1=10, x2=20, y2=20)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
observed = aug.augment_bounding_boxes([bbsoi])
bbs_expected = [
ia.BoundingBox(x1=0-0.2*30, y1=10-0.2*30,
x2=20-0.2*30, y2=20-0.2*30)
]
gen = zip(observed[0].bounding_boxes, bbs_expected)
# TODO deviations of around 0.5 here from expected values, why?
for bb_observed, bb_expected in gen:
assert bb_observed.coords_almost_equals(
bb_expected, max_distance=1.5)
def test_bounding_boxes_with_keep_size(self):
# BB augmentation with keep_size
bbs = [ia.BoundingBox(x1=0, y1=10, x2=20, y2=20)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
aug.jitter = iap.Deterministic(0.2)
observed = aug.augment_bounding_boxes([bbsoi])
bbs_expected = [
ia.BoundingBox(
x1=((0-0.2*30)/(30*0.6))*30,
y1=((10-0.2*30)/(30*0.6))*30,
x2=((20-0.2*30)/(30*0.6))*30,
y2=((20-0.2*30)/(30*0.6))*30
)
]
gen = zip(observed[0].bounding_boxes, bbs_expected)
# TODO deviations of around 0.5 here from expected values, why?
for bb_observed, bb_expected in gen:
assert bb_observed.coords_almost_equals(
bb_expected, max_distance=1.5)
def test_image_bounding_box_alignment(self):
img = np.zeros((100, 100), dtype=np.uint8)
img[35:35+1, 35:65+1] = 255
img[65:65+1, 35:65+1] = 255
img[35:65+1, 35:35+1] = 255
img[35:65+1, 65:65+1] = 255
bbs = [
ia.BoundingBox(y1=35.5, x1=35.5, y2=65.5, x2=65.5),
]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=img.shape)
aug = iaa.PerspectiveTransform(scale=(0.05, 0.2), keep_size=True)
for _ in sm.xrange(30):
imgs_aug, bbsois_aug = aug(
images=[img, img, img, img],
bounding_boxes=[bbsoi, bbsoi, bbsoi, bbsoi])
nb_skipped = 0
for img_aug, bbsoi_aug in zip(imgs_aug, bbsois_aug):
assert bbsoi_aug.shape == img_aug.shape
for bb_aug in bbsoi_aug.bounding_boxes:
if bb_aug.is_fully_within_image(img_aug):
# top, bottom, left, right
x1 = bb_aug.x1_int
x2 = bb_aug.x2_int
y1 = bb_aug.y1_int
y2 = bb_aug.y2_int
top_row = img_aug[y1-1:y1+1, x1-1:x2+1]
btm_row = img_aug[y2-1:y2+1, x1-1:x2+1]
lft_row = img_aug[y1-1:y2+1, x1-1:x1+1]
rgt_row = img_aug[y1-1:y2+1, x2-1:x2+1]
assert np.max(top_row) > 10
assert np.max(btm_row) > 10
assert np.max(lft_row) > 10
assert np.max(rgt_row) > 10
else:
nb_skipped += 1
assert nb_skipped <= 3
def test_bounding_boxes_cover_extreme_points(self):
# Test that for BBs, the augmented BB x coord is really the minimum
# of the BB corner x-coords after augmentation and e.g. not just always
# the augmented top-left corner's coordinate.
h = w = 200 # height, width
s = 5 # block size
j_r = 0.1 # relative amount of jitter
j = int(h * j_r) # absolute amount of jitter
# Note that PerspectiveTransform currently places four points on the
# image and back-projects to the image size (roughly).
# That's why e.g. TopWiderThanBottom has coordinates that seem like
# the top is thinner than the bottom (after projecting back to the
# image rectangle, the top becomes wider).
class _JitterTopWiderThanBottom(object):
def draw_samples(self, size, random_state):
return np.float32([
[
[j_r, 0.0], # top-left
[j_r, 0.0], # top-right
[0.0, 0.0], # bottom-right
[0.0, 0.0], # bottom-left
]
])
class _JitterTopThinnerThanBottom(object):
def draw_samples(self, size, random_state):
return np.float32([
[
[0.0, 0.0], # top-left
[0.0, 0.0], # top-right
[j_r, 0.0], # bottom-right
[j_r, 0.0], # bottom-left
]
])
class _JitterLeftWiderThanRight(object):
def draw_samples(self, size, random_state):
return np.float32([
[
[0.0, j_r], # top-left
[0.0, 0.0], # top-right
[0.0, 0.0], # bottom-right
[0.0, j_r], # bottom-left
]
])
class _JitterLeftThinnerThanRight(object):
def draw_samples(self, size, random_state):
return np.float32([
[
[0.0, 0.0], # top-left
[0.0, j_r], # top-right
[0.0, j_r], # bottom-right
[0.0, 0.0], # bottom-left
]
])
jitters = [
_JitterTopWiderThanBottom(),
_JitterTopThinnerThanBottom(),
_JitterLeftWiderThanRight(),
_JitterLeftThinnerThanRight(),
]
# expected coordinates after applying the above jitter
# coordinates here are given as
# (ystart, yend), (xstart, xend)
coords = [
# top wider than bottom
[
[(0+j, s+j+1), (0, s+1)], # top left
[(0+j, s+j+1), (w-s, w+1)], # top right
[(h-s-j, h-j+1), (w-s-j, w-j+1)], # bottom right
[(h-s-j, h-j+1), (0+j, s+j+1)] # bottom left
],
# top thinner than bottom
[
[(0+j, s+j+1), (0+j, s+j+1)],
[(0+j, s+j+1), (w-s-j, w-j+1)],
[(h-s-j, h-j+1), (w-s, w+1)],
[(h-s-j, h-j+1), (0, s+1)]
],
# left wider than right
[
[(0, s+1), (0+j, s+j+1)],
[(0+j, s+j+1), (w-s-j, w-j+1)],
[(h-s-j, h-j+1), (w-s-j, w-j+1)],
[(h-s, h+1), (0+j, s+j+1)]
],
# left thinner than right
[
[(0+j, s+j+1), (0+j, s+j+1)],
[(0, s+1), (w-s-j, w-j+1)],
[(h-s, h+1), (w-s-j, w-j+1)],
[(h-s-j, h-j+1), (0+j, s+j+1)]
],
]
image = np.zeros((h-1, w-1, 4), dtype=np.uint8)
image = iaa.pad(image, top=1, right=1, bottom=1, left=1, cval=50)
image[0+j:s+j+1, 0+j:s+j+1, 0] = 255
image[0+j:s+j+1, w-s-j:w-j+1, 1] = 255
image[h-s-j:h-j+1, w-s-j:w-j+1, 2] = 255
image[h-s-j:h-j+1, 0+j:s+j+1, 3] = 255
bb = ia.BoundingBox(x1=0.0+j,
y1=0.0+j,
x2=w-j,
y2=h-j)
bbsoi = ia.BoundingBoxesOnImage([bb], shape=image.shape)
i = 0
for jitter, coords_i in zip(jitters, coords):
with self.subTest(jitter=jitter.__class__.__name__):
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
aug.jitter = jitter
image_aug, bbsoi_aug = aug(image=image, bounding_boxes=bbsoi)
assert image_aug.shape == image.shape
(tl_y1, tl_y2), (tl_x1, tl_x2) = coords_i[0]
(tr_y1, tr_y2), (tr_x1, tr_x2) = coords_i[1]
(br_y1, br_y2), (br_x1, br_x2) = coords_i[2]
(bl_y1, bl_y2), (bl_x1, bl_x2) = coords_i[3]
# We have to be rather tolerant here (>100 instead of e.g.
# >200), because the transformation seems to be not that
# accurate and the blobs may be a few pixels off the expected
# coorindates.
assert np.max(image_aug[tl_y1:tl_y2, tl_x1:tl_x2, 0]) > 100
assert np.max(image_aug[tr_y1:tr_y2, tr_x1:tr_x2, 1]) > 100
assert np.max(image_aug[br_y1:br_y2, br_x1:br_x2, 2]) > 100
assert np.max(image_aug[bl_y1:bl_y2, bl_x1:bl_x2, 3]) > 100
# We have rather strong tolerances of 7.5 here, partially
# because the blobs are wide and the true coordinates are in
# the center of the blobs; partially, because of above
# mentioned inaccuracy of PerspectiveTransform.
bb_aug = bbsoi_aug.bounding_boxes[0]
exp_x1 = min([tl_x1, tr_x1, br_x1, bl_x1])
exp_x2 = max([tl_x2, tr_x2, br_x2, bl_x2])
exp_y1 = min([tl_y1, tr_y1, br_y1, bl_y1])
exp_y2 = max([tl_y2, tr_y2, br_y2, bl_y2])
assert np.isclose(bb_aug.x1, exp_x1, atol=7.5)
assert np.isclose(bb_aug.y1, exp_y1, atol=7.5)
assert np.isclose(bb_aug.x2, exp_x2, atol=7.5)
assert np.isclose(bb_aug.y2, exp_y2, atol=7.5)
def test_empty_bounding_boxes(self):
# test empty bounding boxes
bbsoi = ia.BoundingBoxesOnImage([], shape=(20, 10, 3))
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=True)
observed = aug.augment_bounding_boxes(bbsoi)
assert_cbaois_equal(observed, bbsoi)
# ------------
# mode
# ------------
def test_draw_samples_with_mode_being_int(self):
aug = iaa.PerspectiveTransform(scale=0.001, mode=cv2.BORDER_REPLICATE)
samples = aug._draw_samples([(10, 10, 3)], iarandom.RNG(0))
assert samples.modes.shape == (1,)
assert samples.modes[0] == cv2.BORDER_REPLICATE
def test_draw_samples_with_mode_being_string(self):
aug = iaa.PerspectiveTransform(scale=0.001, mode="replicate")
samples = aug._draw_samples([(10, 10, 3)], iarandom.RNG(0))
assert samples.modes.shape == (1,)
assert samples.modes[0] == cv2.BORDER_REPLICATE
def test_mode_replicate_copies_values(self):
aug = iaa.PerspectiveTransform(
scale=0.001, mode="replicate", cval=0, seed=31)
img = np.ones((256, 256, 3), dtype=np.uint8) * 255
img_aug = aug.augment_image(img)
assert (img_aug == 255).all()
def test_mode_constant_uses_cval(self):
aug255 = iaa.PerspectiveTransform(
scale=0.001, mode="constant", cval=255, seed=31)
aug0 = iaa.PerspectiveTransform(
scale=0.001, mode="constant", cval=0, seed=31)
img = np.ones((256, 256, 3), dtype=np.uint8) * 255
img_aug255 = aug255.augment_image(img)
img_aug0 = aug0.augment_image(img)
assert (img_aug255 == 255).all()
# TODO This was originally "assert not (...)", but since
# PerspectiveTransform has become more precise, there are no
# filled pixels anymore at the edges. That is because PerspT
# currently only zooms in and not out. Filled pixels at the sides
# were previously due to a bug.
assert (img_aug0 == 255).all()
# ---------
# fit_output
# ---------
def test_fit_output_with_fixed_jitter(self):
aug = iaa.PerspectiveTransform(scale=0.2, fit_output=True,
keep_size=False)
aug.jitter = iap.Deterministic(0.2)
image = np.zeros((40, 40, 3), dtype=np.uint8)
image[0:3, 0:3, 0] = 255
image[0:3, 40-3:, 1] = 255
image[40-3:, 40-3:, 2] = 255
image_aug = aug(image=image)
h, w = image_aug.shape[0:2]
y0 = np.argmax(image_aug[:, 0, 0])
x0 = np.argmax(image_aug[0, :, 0])
y1 = np.argmax(image_aug[:, w-1, 1])
x1 = np.argmax(image_aug[0, :, 1])
y2 = np.argmax(image_aug[:, w-1, 2])
x2 = np.argmax(image_aug[h-1, :, 2])
# different shape
assert image_aug.shape == image.shape
# corners roughly still at top-left, top-right, bottom-right
assert 0 <= y0 <= 3
assert 0 <= x0 <= 3
assert 0 <= y1 <= 3
assert image_aug.shape[1]-3 <= x1 <= image_aug.shape[1]
assert image_aug.shape[1]-3 <= y2 <= image_aug.shape[1]
assert image_aug.shape[1]-3 <= x2 <= image_aug.shape[1]
# no corner pixels now in the center
assert np.max(image_aug[8:h-8, 8:w-8, :]) == 0
def test_fit_output_with_random_jitter(self):
aug = iaa.PerspectiveTransform(scale=0.1, fit_output=True,
keep_size=False)
image = np.zeros((50, 50, 4), dtype=np.uint8)
image[0:5, 0:5, 0] = 255
image[0:5, 50-5:, 1] = 255
image[50-5:, 50-5:, 2] = 255
image[50-5:, 0:5, 3] = 255
for _ in sm.xrange(10):
image_aug = aug(image=image)
h, w = image_aug.shape[0:2]
arr_nochan = np.max(image_aug, axis=2)
y_idx = np.where(np.max(arr_nochan, axis=1))[0]
x_idx = np.where(np.max(arr_nochan, axis=0))[0]
y_min = np.min(y_idx)
y_max = np.max(y_idx)
x_min = np.min(x_idx)
x_max = np.max(x_idx)
tol = 0
assert 0 <= y_min <= 5+tol
assert 0 <= x_min <= 5+tol
assert h-5-tol <= y_max <= h-1
assert w-5-tol <= x_max <= w-1
def test_fit_output_with_random_jitter__segmentation_maps(self):
aug = iaa.PerspectiveTransform(scale=0.1, fit_output=True,
keep_size=False)
arr = np.zeros((50, 50, 4), dtype=np.uint8)
arr[0:5, 0:5, 0] = 1
arr[0:5, 50-5:, 1] = 1
arr[50-5:, 50-5:, 2] = 1
arr[50-5:, 0:5, 3] = 1
segmap = ia.SegmentationMapsOnImage(arr, shape=(50, 50, 3))
image = np.zeros((49, 49, 3), dtype=np.uint8)
image = iaa.pad(image, top=1, right=1, bottom=1, left=1, cval=128)
for _ in sm.xrange(10):
image_aug, segmap_aug = aug(image=image, segmentation_maps=segmap)
h, w = segmap_aug.arr.shape[0:2]
arr_nochan = np.max(segmap_aug.arr, axis=2)
y_idx = np.where(np.max(arr_nochan, axis=1))[0]
x_idx = np.where(np.max(arr_nochan, axis=0))[0]
y_min = np.min(y_idx)
y_max = np.max(y_idx)
x_min = np.min(x_idx)
x_max = np.max(x_idx)
tol = 0
assert 0 <= y_min <= 5+tol
assert 0 <= x_min <= 5+tol
assert h-5-tol <= y_max <= h-1
assert w-5-tol <= x_max <= w-1
def test_fit_output_with_fixed_jitter__keypoints(self):
aug = iaa.PerspectiveTransform(scale=0.1, fit_output=True,
keep_size=False)
kpsoi = ia.KeypointsOnImage.from_xy_array([
(0, 0),
(50, 0),
(50, 50),
(0, 50)
], shape=(50, 50, 3))
for i in sm.xrange(10):
kpsoi_aug = aug(keypoints=kpsoi)
h, w = kpsoi_aug.shape[0:2]
y0, x0 = kpsoi_aug.keypoints[0].y, kpsoi_aug.keypoints[0].x
y1, x1 = kpsoi_aug.keypoints[1].y, kpsoi_aug.keypoints[1].x
y2, x2 = kpsoi_aug.keypoints[2].y, kpsoi_aug.keypoints[2].x
y3, x3 = kpsoi_aug.keypoints[3].y, kpsoi_aug.keypoints[3].x
y_min = min([y0, y1, y2, y3])
y_max = max([y0, y1, y2, y3])
x_min = min([x0, x1, x2, x3])
x_max = max([x0, x1, x2, x3])
tol = 0.5
assert 0-tol <= y_min <= tol, "Got y_min=%.4f at %d" % (y_min, i)
assert 0-tol <= x_min <= tol, "Got x_min=%.4f at %d" % (x_min, i)
assert h-tol <= y_max <= h+tol, (
"Got y_max=%.4f for h=%.2f at %d" % (y_max, h, i))
assert w-tol <= x_max <= w+tol, (
"Got x_max=%.4f for w=%.2f at %d" % (x_max, w, i))
# ---------
# unusual channel numbers
# ---------
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PerspectiveTransform(scale=0.01)
image_aug = aug(image=image)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
# ---------
# zero-sized axes
# ---------
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
for keep_size in [False, True]:
with self.subTest(shape=shape, keep_size=keep_size):
for _ in sm.xrange(3):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PerspectiveTransform(scale=0.01)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
# --------
# get_parameters
# --------
def test_get_parameters(self):
aug = iaa.PerspectiveTransform(scale=0.1, keep_size=False)
params = aug.get_parameters()
assert is_parameter_instance(params[0], iap.Normal)
assert is_parameter_instance(params[0].scale, iap.Deterministic)
assert 0.1 - 1e-8 < params[0].scale.value < 0.1 + 1e-8
assert params[1] is False
assert params[2].value == 0
assert params[3].value == "constant"
assert params[4] is False
# --------
# other dtypes
# --------
def test_other_dtypes_bool(self):
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
y1 = int(30 * 0.2)
y2 = int(30 * 0.8)
x1 = int(30 * 0.2)
x2 = int(30 * 0.8)
image = np.zeros((30, 30), dtype=bool)
image[12:18, :] = True
image[:, 12:18] = True
expected = image[y1:y2, x1:x2]
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert image_aug.shape == expected.shape
assert (np.sum(image_aug == expected) / expected.size) > 0.9
def test_other_dtypes_uint_int(self):
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
y1 = int(30 * 0.2)
y2 = int(30 * 0.8)
x1 = int(30 * 0.2)
x2 = int(30 * 0.8)
dtypes = ["uint8", "uint16", "int8", "int16"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [0, 1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value-100, max_value]
values = values + [(-1)*value for value in values]
else:
values = [0, 1, 5, 10, 100, int(center_value),
int(0.1 * max_value), int(0.2 * max_value),
int(0.5 * max_value), max_value-100, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((30, 30), dtype=dtype)
image[12:18, :] = value
image[:, 12:18] = value
expected = image[y1:y2, x1:x2]
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert image_aug.shape == expected.shape
# rather high tolerance of 0.7 here because of
# interpolation
assert (
np.sum(image_aug == expected) / expected.size
) > 0.7
def test_other_dtypes_float(self):
aug = iaa.PerspectiveTransform(scale=0.2, keep_size=False)
aug.jitter = iap.Deterministic(0.2)
y1 = int(30 * 0.2)
y2 = int(30 * 0.8)
x1 = int(30 * 0.2)
x2 = int(30 * 0.8)
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [0.01, 1.0, 10.0, 100.0, 500 ** (isize - 1),
1000 ** (isize - 1)]
values = values + [(-1) * value for value in values]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((30, 30), dtype=dtype)
image[12:18, :] = value
image[:, 12:18] = value
expected = image[y1:y2, x1:x2]
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert image_aug.shape == expected.shape
# rather high tolerance of 0.7 here because of
# interpolation
assert (
np.sum(_isclose(image_aug, expected)) / expected.size
) > 0.7
def test_pickleable(self):
aug = iaa.PerspectiveTransform(0.2, seed=1)
runtest_pickleable_uint8_img(aug, iterations=4, shape=(25, 25, 1))
class _elastic_trans_temp_thresholds(object):
def __init__(self, alpha, sigma):
self.alpha = alpha
self.sigma = sigma
self.old_alpha = None
self.old_sigma = None
def __enter__(self):
self.old_alpha = iaa.ElasticTransformation.KEYPOINT_AUG_ALPHA_THRESH
self.old_sigma = iaa.ElasticTransformation.KEYPOINT_AUG_SIGMA_THRESH
iaa.ElasticTransformation.KEYPOINT_AUG_ALPHA_THRESH = self.alpha
iaa.ElasticTransformation.KEYPOINT_AUG_SIGMA_THRESH = self.sigma
def __exit__(self, exc_type, exc_val, exc_tb):
iaa.ElasticTransformation.KEYPOINT_AUG_ALPHA_THRESH = self.old_alpha
iaa.ElasticTransformation.KEYPOINT_AUG_SIGMA_THRESH = self.old_sigma
# TODO add tests for order
# TODO improve tests for cval
# TODO add tests for mode
class TestElasticTransformation(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
img = np.zeros((50, 50), dtype=np.uint8) + 255
img = np.pad(img, ((100, 100), (100, 100)), mode="constant",
constant_values=0)
return img
@property
def mask(self):
img = self.image
mask = img > 0
return mask
@property
def heatmaps(self):
img = self.image
return HeatmapsOnImage(img.astype(np.float32) / 255.0,
shape=img.shape)
@property
def segmaps(self):
img = self.image
return SegmentationMapsOnImage((img > 0).astype(np.int32),
shape=img.shape)
# -----------
# __init__
# -----------
def test___init___bad_datatype_for_alpha_leads_to_failure(self):
# test alpha having bad datatype
got_exception = False
try:
_ = iaa.ElasticTransformation(alpha=False, sigma=0.25)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___alpha_is_tuple(self):
# test alpha being tuple
aug = iaa.ElasticTransformation(alpha=(1.0, 2.0), sigma=0.25)
assert is_parameter_instance(aug.alpha, iap.Uniform)
assert is_parameter_instance(aug.alpha.a, iap.Deterministic)
assert is_parameter_instance(aug.alpha.b, iap.Deterministic)
assert 1.0 - 1e-8 < aug.alpha.a.value < 1.0 + 1e-8
assert 2.0 - 1e-8 < aug.alpha.b.value < 2.0 + 1e-8
def test___init___sigma_is_tuple(self):
# test sigma being tuple
aug = iaa.ElasticTransformation(alpha=0.25, sigma=(1.0, 2.0))
assert is_parameter_instance(aug.sigma, iap.Uniform)
assert is_parameter_instance(aug.sigma.a, iap.Deterministic)
assert is_parameter_instance(aug.sigma.b, iap.Deterministic)
assert 1.0 - 1e-8 < aug.sigma.a.value < 1.0 + 1e-8
assert 2.0 - 1e-8 < aug.sigma.b.value < 2.0 + 1e-8
def test___init___bad_datatype_for_sigma_leads_to_failure(self):
# test sigma having bad datatype
got_exception = False
try:
_ = iaa.ElasticTransformation(alpha=0.25, sigma=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___order_is_all(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, order=ia.ALL)
assert is_parameter_instance(aug.order, iap.Choice)
assert all([order in aug.order.a for order in [0, 1, 2, 3, 4, 5]])
def test___init___order_is_int(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, order=1)
assert is_parameter_instance(aug.order, iap.Deterministic)
assert aug.order.value == 1
def test___init___order_is_list(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, order=[0, 1, 2])
assert is_parameter_instance(aug.order, iap.Choice)
assert all([order in aug.order.a for order in [0, 1, 2]])
def test___init___order_is_stochastic_parameter(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0,
order=iap.Choice([0, 1, 2, 3]))
assert is_parameter_instance(aug.order, iap.Choice)
assert all([order in aug.order.a for order in [0, 1, 2, 3]])
def test___init___bad_datatype_for_order_leads_to_failure(self):
got_exception = False
try:
_ = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, order=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___cval_is_all(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, cval=ia.ALL)
assert is_parameter_instance(aug.cval, iap.Uniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 0
assert aug.cval.b.value == 255
def test___init___cval_is_int(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, cval=128)
assert is_parameter_instance(aug.cval, iap.Deterministic)
assert aug.cval.value == 128
def test___init___cval_is_list(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0,
cval=[16, 32, 64])
assert is_parameter_instance(aug.cval, iap.Choice)
assert all([cval in aug.cval.a for cval in [16, 32, 64]])
def test___init___cval_is_stochastic_parameter(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0,
cval=iap.Choice([16, 32, 64]))
assert is_parameter_instance(aug.cval, iap.Choice)
assert all([cval in aug.cval.a for cval in [16, 32, 64]])
def test___init___cval_is_tuple(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, cval=(128, 255))
assert is_parameter_instance(aug.cval, iap.Uniform)
assert is_parameter_instance(aug.cval.a, iap.Deterministic)
assert is_parameter_instance(aug.cval.b, iap.Deterministic)
assert aug.cval.a.value == 128
assert aug.cval.b.value == 255
def test___init___bad_datatype_for_cval_leads_to_failure(self):
got_exception = False
try:
_ = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, cval=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test___init___mode_is_all(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, mode=ia.ALL)
assert is_parameter_instance(aug.mode, iap.Choice)
assert all([
mode in aug.mode.a
for mode
in ["constant", "nearest", "reflect", "wrap"]])
def test___init___mode_is_string(self):
aug = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, mode="nearest")
assert is_parameter_instance(aug.mode, iap.Deterministic)
assert aug.mode.value == "nearest"
def test___init___mode_is_list(self):
aug = iaa.ElasticTransformation(
alpha=0.25, sigma=1.0, mode=["constant", "nearest"])
assert is_parameter_instance(aug.mode, iap.Choice)
assert all([mode in aug.mode.a for mode in ["constant", "nearest"]])
def test___init___mode_is_stochastic_parameter(self):
aug = iaa.ElasticTransformation(
alpha=0.25, sigma=1.0, mode=iap.Choice(["constant", "nearest"]))
assert is_parameter_instance(aug.mode, iap.Choice)
assert all([mode in aug.mode.a for mode in ["constant", "nearest"]])
def test___init___bad_datatype_for_mode_leads_to_failure(self):
got_exception = False
try:
_ = iaa.ElasticTransformation(alpha=0.25, sigma=1.0, mode=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
# -----------
# alpha, sigma
# -----------
def test_images(self):
# test basic funtionality
aug = iaa.ElasticTransformation(alpha=5, sigma=0.25)
observed = aug.augment_image(self.image)
mask = self.mask
# assume that some white/255 pixels have been moved away from the
# center and replaced by black/0 pixels
assert np.sum(observed[mask]) < np.sum(self.image[mask])
# assume that some black/0 pixels have been moved away from the outer
# area and replaced by white/255 pixels
assert np.sum(observed[~mask]) > np.sum(self.image[~mask])
def test_images_nonsquare(self):
# test basic funtionality with non-square images
aug = iaa.ElasticTransformation(alpha=2.0, sigma=0.25, order=3)
img_nonsquare = np.zeros((50, 100), dtype=np.uint8) + 255
img_nonsquare = np.pad(img_nonsquare, ((100, 100), (100, 100)),
mode="constant", constant_values=0)
mask_nonsquare = (img_nonsquare > 0)
observed = aug.augment_image(img_nonsquare)
assert (
np.sum(observed[mask_nonsquare])
< np.sum(img_nonsquare[mask_nonsquare]))
assert (
np.sum(observed[~mask_nonsquare])
> np.sum(img_nonsquare[~mask_nonsquare]))
def test_images_unusual_channel_numbers(self):
# test unusual channels numbers
aug = iaa.ElasticTransformation(alpha=5, sigma=0.5)
for nb_channels in [1, 2, 4, 5, 7, 10, 11]:
img_c = np.tile(self.image[..., np.newaxis], (1, 1, nb_channels))
assert img_c.shape == (250, 250, nb_channels)
observed = aug.augment_image(img_c)
assert observed.shape == (250, 250, nb_channels)
for c in sm.xrange(1, nb_channels):
assert np.array_equal(observed[..., c], observed[..., 0])
def test_heatmaps(self):
# test basic funtionality, heatmaps
aug = iaa.ElasticTransformation(alpha=0.5, sigma=0.25)
observed = aug.augment_heatmaps([self.heatmaps])[0]
mask = self.mask
assert observed.shape == self.heatmaps.shape
_assert_same_min_max(observed, self.heatmaps)
assert (
np.sum(observed.get_arr()[mask])
< np.sum(self.heatmaps.get_arr()[mask]))
assert (
np.sum(observed.get_arr()[~mask])
> np.sum(self.heatmaps.get_arr()[~mask]))
def test_segmaps(self):
# test basic funtionality, segmaps
# alpha=1.5 instead of 0.5 as above here, because otherwise nothing
# is moved
aug = iaa.ElasticTransformation(alpha=1.5, sigma=0.25)
observed = aug.augment_segmentation_maps([self.segmaps])[0]
mask = self.mask
assert observed.shape == self.segmaps.shape
assert (
np.sum(observed.get_arr()[mask])
< np.sum(self.segmaps.get_arr()[mask]))
assert (
np.sum(observed.get_arr()[~mask])
> np.sum(self.segmaps.get_arr()[~mask]))
def test_images_weak_vs_strong_alpha(self):
# test effects of increased alpha strength
aug1 = iaa.ElasticTransformation(alpha=0.1, sigma=0.25)
aug2 = iaa.ElasticTransformation(alpha=5.0, sigma=0.25)
observed1 = aug1.augment_image(self.image)
observed2 = aug2.augment_image(self.image)
mask = self.mask
# assume that the inner area has become more black-ish when using high
# alphas (more white pixels were moved out of the inner area)
assert np.sum(observed1[mask]) > np.sum(observed2[mask])
# assume that the outer area has become more white-ish when using high
# alphas (more black pixels were moved into the inner area)
assert np.sum(observed1[~mask]) < np.sum(observed2[~mask])
def test_heatmaps_weak_vs_strong_alpha(self):
# test effects of increased alpha strength, heatmaps
aug1 = iaa.ElasticTransformation(alpha=0.1, sigma=0.25)
aug2 = iaa.ElasticTransformation(alpha=5.0, sigma=0.25)
observed1 = aug1.augment_heatmaps([self.heatmaps])[0]
observed2 = aug2.augment_heatmaps([self.heatmaps])[0]
mask = self.mask
assert observed1.shape == self.heatmaps.shape
assert observed2.shape == self.heatmaps.shape
_assert_same_min_max(observed1, self.heatmaps)
_assert_same_min_max(observed2, self.heatmaps)
assert (
np.sum(observed1.get_arr()[mask])
> np.sum(observed2.get_arr()[mask]))
assert (
np.sum(observed1.get_arr()[~mask])
< np.sum(observed2.get_arr()[~mask]))
def test_segmaps_weak_vs_strong_alpha(self):
# test effects of increased alpha strength, segmaps
aug1 = iaa.ElasticTransformation(alpha=0.1, sigma=0.25)
aug2 = iaa.ElasticTransformation(alpha=5.0, sigma=0.25)
observed1 = aug1.augment_segmentation_maps([self.segmaps])[0]
observed2 = aug2.augment_segmentation_maps([self.segmaps])[0]
mask = self.mask
assert observed1.shape == self.segmaps.shape
assert observed2.shape == self.segmaps.shape
assert (
np.sum(observed1.get_arr()[mask])
> np.sum(observed2.get_arr()[mask]))
assert (
np.sum(observed1.get_arr()[~mask])
< np.sum(observed2.get_arr()[~mask]))
def test_images_low_vs_high_sigma(self):
# test effects of increased sigmas
aug1 = iaa.ElasticTransformation(alpha=3.0, sigma=0.1)
aug2 = iaa.ElasticTransformation(alpha=3.0, sigma=3.0)
observed1 = aug1.augment_image(self.image)
observed2 = aug2.augment_image(self.image)
observed1_std_hori = np.std(
observed1.astype(np.float32)[:, 1:]
- observed1.astype(np.float32)[:, :-1])
observed2_std_hori = np.std(
observed2.astype(np.float32)[:, 1:]
- observed2.astype(np.float32)[:, :-1])
observed1_std_vert = np.std(
observed1.astype(np.float32)[1:, :]
- observed1.astype(np.float32)[:-1, :])
observed2_std_vert = np.std(
observed2.astype(np.float32)[1:, :]
- observed2.astype(np.float32)[:-1, :])
observed1_std = (observed1_std_hori + observed1_std_vert) / 2
observed2_std = (observed2_std_hori + observed2_std_vert) / 2
assert observed1_std > observed2_std
def test_images_alpha_is_stochastic_parameter(self):
# test alpha being iap.Choice
aug = iaa.ElasticTransformation(alpha=iap.Choice([0.001, 5.0]),
sigma=0.25)
seen = [0, 0]
for _ in sm.xrange(100):
observed = aug.augment_image(self.image)
diff = np.average(
np.abs(
self.image.astype(np.float32)
- observed.astype(np.float32)
)
)
if diff < 1.0:
seen[0] += 1
else:
seen[1] += 1
assert seen[0] > 10
assert seen[1] > 10
def test_sigma_is_stochastic_parameter(self):
# test sigma being iap.Choice
for order in [0, 1, 3]:
with self.subTest(order=order):
aug = iaa.ElasticTransformation(alpha=50.0,
sigma=iap.Choice([0.001, 5.0]),
order=order)
seen = [0, 0]
for _ in sm.xrange(100):
observed = aug.augment_image(self.image)
observed_std_hori = np.std(
observed.astype(np.float32)[:, 1:]
- observed.astype(np.float32)[:, :-1])
observed_std_vert = np.std(
observed.astype(np.float32)[1:, :]
- observed.astype(np.float32)[:-1, :])
observed_std = (observed_std_hori + observed_std_vert) / 2
if observed_std > 25.0:
seen[0] += 1
else:
seen[1] += 1
assert seen[0] > 10
assert seen[1] > 10
# -----------
# cval
# -----------
def test_images_cval_is_int_and_order_is_0(self):
aug = iaa.ElasticTransformation(alpha=30.0, sigma=3.0, mode="constant",
cval=255, order=0)
img = np.zeros((100, 100), dtype=np.uint8)
observed = aug.augment_image(img)
assert np.sum(observed == 255) > 0
assert np.sum(np.logical_and(0 < observed, observed < 255)) == 0
def test_images_cval_is_int_and_order_is_0_weak_alpha(self):
aug = iaa.ElasticTransformation(alpha=3.0, sigma=3.0, mode="constant",
cval=0, order=0)
img = np.zeros((100, 100), dtype=np.uint8)
observed = aug.augment_image(img)
assert np.sum(observed == 255) == 0
def test_images_cval_is_int_and_order_is_2(self):
aug = iaa.ElasticTransformation(alpha=3.0, sigma=3.0, mode="constant",
cval=255, order=2)
img = np.zeros((100, 100), dtype=np.uint8)
observed = aug.augment_image(img)
assert np.sum(np.logical_and(0 < observed, observed < 255)) > 0
def test_images_cval_is_int_image_hw3(self):
aug = iaa.ElasticTransformation(alpha=5.0, sigma=3.0, mode="constant",
cval=255, order=0)
img = np.zeros((100, 100, 3), dtype=np.uint8)
observed = aug.augment_image(img)
count_255 = np.sum(observed == 255, axis=2)
mask_not_all_channels_same_intensity = np.logical_and(
count_255 > 0, count_255 < 3)
mask_all_channels_same_intensity = (count_255 == 3)
assert not np.any(mask_not_all_channels_same_intensity)
assert np.any(mask_all_channels_same_intensity)
def test_heatmaps_ignore_cval(self):
# cval with heatmaps
heatmaps = HeatmapsOnImage(
np.zeros((32, 32, 1), dtype=np.float32), shape=(32, 32, 3))
aug = iaa.ElasticTransformation(alpha=3.0, sigma=3.0,
mode="constant", cval=255)
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == heatmaps.shape
_assert_same_min_max(observed, heatmaps)
assert np.sum(observed.get_arr() > 0.01) == 0
def test_segmaps_ignore_cval(self):
# cval with segmaps
segmaps = SegmentationMapsOnImage(
np.zeros((32, 32, 1), dtype=np.int32), shape=(32, 32, 3))
aug = iaa.ElasticTransformation(alpha=3.0, sigma=3.0, mode="constant",
cval=255)
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == segmaps.shape
assert np.sum(observed.get_arr() > 0) == 0
# -----------
# keypoints
# -----------
def test_keypoints_no_movement_if_alpha_below_threshold(self):
# for small alpha, should not move if below threshold
with _elastic_trans_temp_thresholds(alpha=1.0, sigma=0.0):
kps = [
ia.Keypoint(x=1, y=1), ia.Keypoint(x=15, y=25),
ia.Keypoint(x=5, y=5), ia.Keypoint(x=7, y=4),
ia.Keypoint(x=48, y=5), ia.Keypoint(x=21, y=37),
ia.Keypoint(x=32, y=39), ia.Keypoint(x=6, y=8),
ia.Keypoint(x=12, y=21), ia.Keypoint(x=3, y=45),
ia.Keypoint(x=45, y=3), ia.Keypoint(x=7, y=48)]
kpsoi = ia.KeypointsOnImage(kps, shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=0.001, sigma=1.0)
observed = aug.augment_keypoints([kpsoi])[0]
d = kpsoi.to_xy_array() - observed.to_xy_array()
d[:, 0] = d[:, 0] ** 2
d[:, 1] = d[:, 1] ** 2
d = np.sum(d, axis=1)
d = np.average(d, axis=0)
assert d < 1e-8
def test_keypoints_no_movement_if_sigma_below_threshold(self):
# for small sigma, should not move if below threshold
with _elastic_trans_temp_thresholds(alpha=0.0, sigma=1.0):
kps = [
ia.Keypoint(x=1, y=1), ia.Keypoint(x=15, y=25),
ia.Keypoint(x=5, y=5), ia.Keypoint(x=7, y=4),
ia.Keypoint(x=48, y=5), ia.Keypoint(x=21, y=37),
ia.Keypoint(x=32, y=39), ia.Keypoint(x=6, y=8),
ia.Keypoint(x=12, y=21), ia.Keypoint(x=3, y=45),
ia.Keypoint(x=45, y=3), ia.Keypoint(x=7, y=48)]
kpsoi = ia.KeypointsOnImage(kps, shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=1.0, sigma=0.001)
observed = aug.augment_keypoints([kpsoi])[0]
d = kpsoi.to_xy_array() - observed.to_xy_array()
d[:, 0] = d[:, 0] ** 2
d[:, 1] = d[:, 1] ** 2
d = np.sum(d, axis=1)
d = np.average(d, axis=0)
assert d < 1e-8
def test_keypoints_small_movement_for_weak_alpha_if_threshold_zero(self):
# for small alpha (at sigma 1.0), should barely move
# if thresholds set to zero
with _elastic_trans_temp_thresholds(alpha=0.0, sigma=0.0):
kps = [
ia.Keypoint(x=1, y=1), ia.Keypoint(x=15, y=25),
ia.Keypoint(x=5, y=5), ia.Keypoint(x=7, y=4),
ia.Keypoint(x=48, y=5), ia.Keypoint(x=21, y=37),
ia.Keypoint(x=32, y=39), ia.Keypoint(x=6, y=8),
ia.Keypoint(x=12, y=21), ia.Keypoint(x=3, y=45),
ia.Keypoint(x=45, y=3), ia.Keypoint(x=7, y=48)]
kpsoi = ia.KeypointsOnImage(kps, shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=0.001, sigma=1.0)
observed = aug.augment_keypoints([kpsoi])[0]
d = kpsoi.to_xy_array() - observed.to_xy_array()
d[:, 0] = d[:, 0] ** 2
d[:, 1] = d[:, 1] ** 2
d = np.sum(d, axis=1)
d = np.average(d, axis=0)
assert d < 0.5
def test_image_keypoint_alignment(self):
# test alignment between between images and keypoints
image = np.zeros((120, 70), dtype=np.uint8)
s = 3
image[:, 35-s:35+s+1] = 255
kps = [ia.Keypoint(x=35, y=20),
ia.Keypoint(x=35, y=40),
ia.Keypoint(x=35, y=60),
ia.Keypoint(x=35, y=80),
ia.Keypoint(x=35, y=100)]
kpsoi = ia.KeypointsOnImage(kps, shape=image.shape)
aug = iaa.ElasticTransformation(alpha=70, sigma=5)
aug_det = aug.to_deterministic()
images_aug = aug_det.augment_images([image, image])
kpsois_aug = aug_det.augment_keypoints([kpsoi, kpsoi])
count_bad = 0
for image_aug, kpsoi_aug in zip(images_aug, kpsois_aug):
assert kpsoi_aug.shape == (120, 70)
assert len(kpsoi_aug.keypoints) == 5
for kp_aug in kpsoi_aug.keypoints:
x, y = int(np.round(kp_aug.x)), int(np.round(kp_aug.y))
bb = ia.BoundingBox(x1=x-2, x2=x+2+1, y1=y-2, y2=y+2+1)
img_ex = bb.extract_from_image(image_aug)
if np.any(img_ex > 10):
pass # close to expected location
else:
count_bad += 1
assert count_bad <= 1
def test_empty_keypoints(self):
aug = iaa.ElasticTransformation(alpha=10, sigma=10)
kpsoi = ia.KeypointsOnImage([], shape=(10, 10, 3))
kpsoi_aug = aug.augment_keypoints(kpsoi)
assert len(kpsoi_aug.keypoints) == 0
assert kpsoi_aug.shape == (10, 10, 3)
# -----------
# abstract methods for polygons and line strings
# -----------
@classmethod
def _test_cbaois_no_movement_if_alpha_below_threshold(
cls, cba_class, cbaoi_class, augf_name):
# for small alpha, should not move if below threshold
with _elastic_trans_temp_thresholds(alpha=1.0, sigma=0.0):
cba = cba_class([(10, 15), (40, 15), (40, 35), (10, 35)])
cbaoi = cbaoi_class([cba], shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=0.001, sigma=1.0)
observed = getattr(aug, augf_name)(cbaoi)
assert observed.shape == (50, 50)
assert len(observed.items) == 1
assert observed.items[0].coords_almost_equals(cba)
if hasattr(observed.items[0], "is_valid"):
assert observed.items[0].is_valid
@classmethod
def _test_cbaois_no_movement_if_sigma_below_threshold(
cls, cba_class, cbaoi_class, augf_name):
# for small sigma, should not move if below threshold
with _elastic_trans_temp_thresholds(alpha=0.0, sigma=1.0):
cba = cba_class([(10, 15), (40, 15), (40, 35), (10, 35)])
cbaoi = cbaoi_class([cba], shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=1.0, sigma=0.001)
observed = getattr(aug, augf_name)(cbaoi)
assert observed.shape == (50, 50)
assert len(observed.items) == 1
assert observed.items[0].coords_almost_equals(cba)
if hasattr(observed.items[0], "is_valid"):
assert observed.items[0].is_valid
@classmethod
def _test_cbaois_small_movement_for_weak_alpha_if_threshold_zero(
cls, cba_class, cbaoi_class, augf_name):
# for small alpha (at sigma 1.0), should barely move
# if thresholds set to zero
with _elastic_trans_temp_thresholds(alpha=0.0, sigma=0.0):
cba = cba_class([(10, 15), (40, 15), (40, 35), (10, 35)])
cbaoi = cbaoi_class([cba], shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=0.001, sigma=1.0)
observed = getattr(aug, augf_name)(cbaoi)
assert observed.shape == (50, 50)
assert len(observed.items) == 1
assert observed.items[0].coords_almost_equals(
cba, max_distance=0.5)
if hasattr(observed.items[0], "is_valid"):
assert observed.items[0].is_valid
@classmethod
def _test_image_cbaoi_alignment(cls, cba_class, cbaoi_class, augf_name):
# test alignment between between images and polygons
height_step_size = 50
width_step_size = 30
height_steps = 2 # don't set >2, otherwise polygon will be broken
width_steps = 10
height = (2+height_steps) * height_step_size
width = (2+width_steps) * width_step_size
s = 3
image = np.zeros((height, width), dtype=np.uint8)
points = []
for w in sm.xrange(0, 2+width_steps):
if w not in [0, width_steps+2-1]:
x = width_step_size * w
y = height_step_size
points.append((x, y))
image[y-s:y+s+1, x-s:x+s+1] = 255
for w in sm.xrange(2+width_steps-1, 0, -1):
if w not in [0, width_steps+2-1]:
x = width_step_size * w
y = height_step_size*2
points.append((x, y))
image[y-s:y+s+1, x-s:x+s+1] = 255
cba = cba_class(points)
cbaoi = cbaoi_class([cba], shape=image.shape)
aug = iaa.ElasticTransformation(alpha=100, sigma=7)
aug_det = aug.to_deterministic()
images_aug = aug_det.augment_images([image, image])
cbaois_aug = getattr(aug_det, augf_name)([cbaoi, cbaoi])
count_bad = 0
for image_aug, cbaoi_aug in zip(images_aug, cbaois_aug):
assert cbaoi_aug.shape == image.shape
assert len(cbaoi_aug.items) == 1
for cba_aug in cbaoi_aug.items:
if hasattr(cba_aug, "is_valid"):
assert cba_aug.is_valid
for point_aug in cba_aug.coords:
x, y = point_aug[0], point_aug[1]
bb = ia.BoundingBox(x1=x-2, x2=x+2, y1=y-2, y2=y+2)
img_ex = bb.extract_from_image(image_aug)
if np.any(img_ex > 10):
pass # close to expected location
else:
count_bad += 1
assert count_bad <= 3
@classmethod
def _test_empty_cbaois(cls, cbaoi, augf_name):
aug = iaa.ElasticTransformation(alpha=10, sigma=10)
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(cbaoi_aug, cbaoi)
# -----------
# polygons
# -----------
def test_polygons_no_movement_if_alpha_below_threshold(self):
self._test_cbaois_no_movement_if_alpha_below_threshold(
ia.Polygon, ia.PolygonsOnImage, "augment_polygons")
def test_polygons_no_movement_if_sigma_below_threshold(self):
self._test_cbaois_no_movement_if_sigma_below_threshold(
ia.Polygon, ia.PolygonsOnImage, "augment_polygons")
def test_polygons_small_movement_for_weak_alpha_if_threshold_zero(self):
self._test_cbaois_small_movement_for_weak_alpha_if_threshold_zero(
ia.Polygon, ia.PolygonsOnImage, "augment_polygons")
def test_image_polygon_alignment(self):
self._test_image_cbaoi_alignment(
ia.Polygon, ia.PolygonsOnImage, "augment_polygons")
def test_empty_polygons(self):
cbaoi = ia.PolygonsOnImage([], shape=(10, 10, 3))
self._test_empty_cbaois(cbaoi, "augment_polygons")
# -----------
# line strings
# -----------
def test_line_strings_no_movement_if_alpha_below_threshold(self):
self._test_cbaois_no_movement_if_alpha_below_threshold(
ia.LineString, ia.LineStringsOnImage, "augment_line_strings")
def test_line_strings_no_movement_if_sigma_below_threshold(self):
self._test_cbaois_no_movement_if_sigma_below_threshold(
ia.LineString, ia.LineStringsOnImage, "augment_line_strings")
def test_line_strings_small_movement_for_weak_alpha_if_threshold_zero(self):
self._test_cbaois_small_movement_for_weak_alpha_if_threshold_zero(
ia.LineString, ia.LineStringsOnImage, "augment_line_strings")
def test_image_line_string_alignment(self):
self._test_image_cbaoi_alignment(
ia.LineString, ia.LineStringsOnImage, "augment_line_strings")
def test_empty_line_strings(self):
cbaoi = ia.LineStringsOnImage([], shape=(10, 10, 3))
self._test_empty_cbaois(cbaoi, "augment_line_strings")
# -----------
# bounding boxes
# -----------
def test_bounding_boxes_no_movement_if_alpha_below_threshold(self):
# for small alpha, should not move if below threshold
with _elastic_trans_temp_thresholds(alpha=1.0, sigma=0.0):
bbs = [
ia.BoundingBox(x1=10, y1=12, x2=20, y2=22),
ia.BoundingBox(x1=20, y1=32, x2=40, y2=42)
]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=0.001, sigma=1.0)
observed = aug.augment_bounding_boxes([bbsoi])[0]
d = bbsoi.to_xyxy_array() - observed.to_xyxy_array()
d = d.reshape((2*2, 2))
d[:, 0] = d[:, 0] ** 2
d[:, 1] = d[:, 1] ** 2
d = np.sum(d, axis=1)
d = np.average(d, axis=0)
assert d < 1e-8
def test_bounding_boxes_no_movement_if_sigma_below_threshold(self):
# for small sigma, should not move if below threshold
with _elastic_trans_temp_thresholds(alpha=0.0, sigma=1.0):
bbs = [
ia.BoundingBox(x1=10, y1=12, x2=20, y2=22),
ia.BoundingBox(x1=20, y1=32, x2=40, y2=42)
]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=1.0, sigma=0.001)
observed = aug.augment_bounding_boxes([bbsoi])[0]
d = bbsoi.to_xyxy_array() - observed.to_xyxy_array()
d = d.reshape((2*2, 2))
d[:, 0] = d[:, 0] ** 2
d[:, 1] = d[:, 1] ** 2
d = np.sum(d, axis=1)
d = np.average(d, axis=0)
assert d < 1e-8
def test_bounding_boxes_small_movement_for_weak_alpha_if_threshold_zero(
self):
# for small alpha (at sigma 1.0), should barely move
# if thresholds set to zero
with _elastic_trans_temp_thresholds(alpha=0.0, sigma=0.0):
bbs = [
ia.BoundingBox(x1=10, y1=12, x2=20, y2=22),
ia.BoundingBox(x1=20, y1=32, x2=40, y2=42)
]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(50, 50))
aug = iaa.ElasticTransformation(alpha=0.001, sigma=1.0)
observed = aug.augment_bounding_boxes([bbsoi])[0]
d = bbsoi.to_xyxy_array() - observed.to_xyxy_array()
d = d.reshape((2*2, 2))
d[:, 0] = d[:, 0] ** 2
d[:, 1] = d[:, 1] ** 2
d = np.sum(d, axis=1)
d = np.average(d, axis=0)
assert d < 0.5
def test_image_bounding_box_alignment(self):
# test alignment between between images and bounding boxes
image = np.zeros((100, 100), dtype=np.uint8)
image[35:35+1, 35:65+1] = 255
image[65:65+1, 35:65+1] = 255
image[35:65+1, 35:35+1] = 255
image[35:65+1, 65:65+1] = 255
bbs = [
ia.BoundingBox(x1=35.5, y1=35.5, x2=65.5, y2=65.5)
]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=image.shape)
aug = iaa.ElasticTransformation(alpha=70, sigma=5)
images_aug, bbsois_aug = aug(images=[image, image],
bounding_boxes=[bbsoi, bbsoi])
count_bad = 0
for image_aug, bbsoi_aug in zip(images_aug, bbsois_aug):
assert bbsoi_aug.shape == (100, 100)
assert len(bbsoi_aug.bounding_boxes) == 1
for bb_aug in bbsoi_aug.bounding_boxes:
if bb_aug.is_fully_within_image(image_aug):
# top, bottom, left, right
x1 = bb_aug.x1_int
x2 = bb_aug.x2_int
y1 = bb_aug.y1_int
y2 = bb_aug.y2_int
top_row = image_aug[y1-2:y1+2, x1-2:x2+2]
btm_row = image_aug[y2-2:y2+2, x1-2:x2+2]
lft_row = image_aug[y1-2:y2+2, x1-2:x1+2]
rgt_row = image_aug[y1-2:y2+2, x2-2:x2+2]
assert np.max(top_row) > 10
assert np.max(btm_row) > 10
assert np.max(lft_row) > 10
assert np.max(rgt_row) > 10
else:
count_bad += 1
assert count_bad <= 1
def test_empty_bounding_boxes(self):
aug = iaa.ElasticTransformation(alpha=10, sigma=10)
bbsoi = ia.BoundingBoxesOnImage([], shape=(10, 10, 3))
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
assert len(bbsoi_aug.bounding_boxes) == 0
assert bbsoi_aug.shape == (10, 10, 3)
# -----------
# heatmaps alignment
# -----------
def test_image_heatmaps_alignment(self):
# test alignment between images and heatmaps
for order in [0, 1, 3]:
with self.subTest(order=order):
img = np.zeros((80, 80), dtype=np.uint8)
img[:, 30:50] = 255
img[30:50, :] = 255
hm = HeatmapsOnImage(img.astype(np.float32)/255.0, shape=(80, 80))
aug = iaa.ElasticTransformation(
alpha=60.0,
sigma=4.0,
mode="constant",
cval=0,
order=order
)
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(img)
hm_aug = aug_det.augment_heatmaps([hm])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = hm_aug.arr_0to1 > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert hm_aug.shape == (80, 80)
assert hm_aug.arr_0to1.shape == (80, 80, 1)
assert (same / img_aug_mask.size) >= 0.97
def test_image_heatmaps_alignment_if_heatmaps_smaller_than_image(self):
# test alignment between images and heatmaps
# here with heatmaps that are smaller than the image
for order in [0, 1, 3]:
with self.subTest(order=order):
img = np.zeros((80, 80), dtype=np.uint8)
img[:, 30:50] = 255
img[30:50, :] = 255
img_small = ia.imresize_single_image(
img, (40, 40), interpolation="nearest")
hm = HeatmapsOnImage(
img_small.astype(np.float32)/255.0,
shape=(80, 80))
aug = iaa.ElasticTransformation(
alpha=60.0, sigma=4.0, mode="constant", cval=0)
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(img)
hm_aug = aug_det.augment_heatmaps([hm])[0]
img_aug_mask = img_aug > 255*0.1
hm_aug_mask = ia.imresize_single_image(
hm_aug.arr_0to1, (80, 80), interpolation="nearest"
) > 0.1
same = np.sum(img_aug_mask == hm_aug_mask[:, :, 0])
assert hm_aug.shape == (80, 80)
assert hm_aug.arr_0to1.shape == (40, 40, 1)
# TODO this is a fairly low threshold, why is that the case?
assert (same / img_aug_mask.size) >= 0.9
# -----------
# segmaps alignment
# -----------
def test_image_segmaps_alignment(self):
# test alignment between images and segmaps
img = np.zeros((80, 80), dtype=np.uint8)
img[:, 30:50] = 255
img[30:50, :] = 255
segmaps = SegmentationMapsOnImage(
(img > 0).astype(np.int32),
shape=(80, 80))
aug = iaa.ElasticTransformation(
alpha=60.0, sigma=4.0, mode="constant", cval=0, order=0)
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(img)
segmaps_aug = aug_det.augment_segmentation_maps([segmaps])[0]
img_aug_mask = img_aug > 255*0.1
segmaps_aug_mask = segmaps_aug.arr > 0
same = np.sum(img_aug_mask == segmaps_aug_mask[:, :, 0])
assert segmaps_aug.shape == (80, 80)
assert segmaps_aug.arr.shape == (80, 80, 1)
assert (same / img_aug_mask.size) >= 0.99
def test_image_segmaps_alignment_if_heatmaps_smaller_than_image(self):
# test alignment between images and segmaps
# here with segmaps that are smaller than the image
img = np.zeros((80, 80), dtype=np.uint8)
img[:, 30:50] = 255
img[30:50, :] = 255
img_small = ia.imresize_single_image(
img, (40, 40), interpolation="nearest")
segmaps = SegmentationMapsOnImage(
(img_small > 0).astype(np.int32), shape=(80, 80))
aug = iaa.ElasticTransformation(
alpha=60.0, sigma=4.0, mode="constant", cval=0, order=0)
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(img)
segmaps_aug = aug_det.augment_segmentation_maps([segmaps])[0]
img_aug_mask = img_aug > 255*0.1
segmaps_aug_mask = ia.imresize_single_image(
segmaps_aug.arr, (80, 80), interpolation="nearest") > 0
same = np.sum(img_aug_mask == segmaps_aug_mask[:, :, 0])
assert segmaps_aug.shape == (80, 80)
assert segmaps_aug.arr.shape == (40, 40, 1)
assert (same / img_aug_mask.size) >= 0.93
# ---------
# unusual channel numbers
# ---------
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.ElasticTransformation(alpha=2.0, sigma=2.0)
image_aug = aug(image=image)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
# ---------
# zero-sized axes
# ---------
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
for keep_size in [False, True]:
with self.subTest(shape=shape, keep_size=keep_size):
for _ in sm.xrange(3):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.ElasticTransformation(alpha=2.0, sigma=2.0)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
# -----------
# get_parameters
# -----------
def test_get_parameters(self):
aug = iaa.ElasticTransformation(
alpha=0.25, sigma=1.0, order=2, cval=10, mode="constant")
params = aug.get_parameters()
assert params[0] is aug.alpha
assert params[1] is aug.sigma
assert params[2] is aug.order
assert params[3] is aug.cval
assert params[4] is aug.mode
assert 0.25 - 1e-8 < params[0].value < 0.25 + 1e-8
assert 1.0 - 1e-8 < params[1].value < 1.0 + 1e-8
assert params[2].value == 2
assert params[3].value == 10
assert params[4].value == "constant"
# -----------
# other dtypes
# -----------
def test_other_dtypes_bool(self):
aug = iaa.ElasticTransformation(sigma=0.5, alpha=5, order=0)
mask = np.zeros((21, 21), dtype=bool)
mask[7:13, 7:13] = True
image = np.zeros((21, 21), dtype=bool)
image[mask] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert not np.all(image_aug == 1)
assert np.any(image_aug[~mask] == 1)
def test_other_dtypes_uint_int(self):
aug = iaa.ElasticTransformation(sigma=0.5, alpha=5, order=0)
mask = np.zeros((21, 21), dtype=bool)
mask[7:13, 7:13] = True
dtypes = ["uint8", "uint16", "uint32", "int8", "int16", "int32"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
image = np.zeros((21, 21), dtype=dtype)
image[7:13, 7:13] = max_value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert not np.all(image_aug == max_value)
assert np.any(image_aug[~mask] == max_value)
def test_other_dtypes_float(self):
aug = iaa.ElasticTransformation(sigma=0.5, alpha=5, order=0)
mask = np.zeros((21, 21), dtype=bool)
mask[7:13, 7:13] = True
for dtype in ["float16", "float32", "float64"]:
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [
0.01,
1.0,
10.0,
100.0,
500 ** (isize - 1),
float(np.float64(1000 ** (isize - 1)))
]
values = values + [(-1) * value for value in values]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((21, 21), dtype=dtype)
image[7:13, 7:13] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert not np.all(_isclose(image_aug, value))
assert np.any(_isclose(image_aug[~mask], value))
def test_other_dtypes_bool_all_orders(self):
mask = np.zeros((50, 50), dtype=bool)
mask[10:40, 20:30] = True
mask[20:30, 10:40] = True
for order in [0, 1, 2, 3, 4, 5]:
aug = iaa.ElasticTransformation(sigma=1.0, alpha=50, order=order)
image = np.zeros((50, 50), dtype=bool)
image[mask] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert not np.all(image_aug == 1)
assert np.any(image_aug[~mask] == 1)
def test_other_dtypes_uint_int_all_orders(self):
mask = np.zeros((50, 50), dtype=bool)
mask[10:40, 20:30] = True
mask[20:30, 10:40] = True
for order in [0, 1, 2, 3, 4, 5]:
aug = iaa.ElasticTransformation(sigma=1.0, alpha=50, order=order)
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
if order == 0:
dtypes = ["uint8", "uint16", "uint32",
"int8", "int16", "int32"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
dynamic_range = max_value - min_value
image = np.zeros((50, 50), dtype=dtype)
image[mask] = max_value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
if order == 0:
assert not np.all(image_aug == max_value)
assert np.any(image_aug[~mask] == max_value)
else:
atol = 0.1 * dynamic_range
assert not np.all(
np.isclose(image_aug,
max_value,
rtol=0, atol=atol)
)
assert np.any(
np.isclose(image_aug[~mask],
max_value,
rtol=0, atol=atol))
def test_other_dtypes_float_all_orders(self):
mask = np.zeros((50, 50), dtype=bool)
mask[10:40, 20:30] = True
mask[20:30, 10:40] = True
for order in [0, 1, 2, 3, 4, 5]:
aug = iaa.ElasticTransformation(sigma=1.0, alpha=50, order=order)
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
value = (
0.1 * max_value
if dtype != "float64"
else 0.0001 * max_value)
image = np.zeros((50, 50), dtype=dtype)
image[mask] = value
image_aug = aug.augment_image(image)
if order == 0:
assert image_aug.dtype.name == dtype
assert not np.all(
_isclose(image_aug, value)
)
assert np.any(
_isclose(image_aug[~mask], value)
)
else:
atol = (
10
if dtype == "float16"
else 0.00001 * max_value)
assert not np.all(
np.isclose(
image_aug,
value,
rtol=0, atol=atol
))
assert np.any(
np.isclose(
image_aug[~mask],
value,
rtol=0, atol=atol
))
def test_pickleable(self):
aug = iaa.ElasticTransformation(alpha=(0.2, 1.5), sigma=(1.0, 10.0),
seed=1)
runtest_pickleable_uint8_img(aug, iterations=4, shape=(25, 25, 1))
class _TwoValueParam(iap.StochasticParameter):
def __init__(self, v1, v2):
super(_TwoValueParam, self).__init__()
self.v1 = v1
self.v2 = v2
def _draw_samples(self, size, random_state):
arr = np.full(size, self.v1, dtype=np.int32)
arr[1::2] = self.v2
return arr
class TestRot90(unittest.TestCase):
@property
def kp_offset(self):
# set this to -1 when using integer-based KP rotation instead of
# subpixel/float-based rotation
return 0
@property
def image(self):
return np.arange(4*4*3).reshape((4, 4, 3)).astype(np.uint8)
@property
def heatmaps(self):
return HeatmapsOnImage(self.image[..., 0:1].astype(np.float32) / 255,
shape=(4, 4, 3))
@property
def heatmaps_smaller(self):
return HeatmapsOnImage(
np.float32([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6]]), shape=(4, 8, 3))
@property
def segmaps(self):
return SegmentationMapsOnImage(
self.image[..., 0:1].astype(np.int32), shape=(4, 4, 3))
@property
def segmaps_smaller(self):
return SegmentationMapsOnImage(
np.int32([[0, 1, 2], [3, 4, 5]]), shape=(4, 8, 3))
@property
def kpsoi(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=2, y=3)]
return ia.KeypointsOnImage(kps, shape=(4, 8, 3))
@property
def psoi(self):
return ia.PolygonsOnImage(
[ia.Polygon([(1, 1), (3, 1), (3, 3), (1, 3)])],
shape=(4, 8, 3)
)
@property
def lsoi(self):
return ia.LineStringsOnImage(
[ia.LineString([(1, 1), (3, 1), (3, 3), (1, 3)])],
shape=(4, 8, 3)
)
@property
def bbsoi(self):
return ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=1, y1=1, x2=3, y2=3)],
shape=(4, 8, 3)
)
@property
def kpsoi_k1(self):
# without keep size
kp_offset = self.kp_offset
expected_k1_kps = [(4-2+kp_offset, 1),
(4-3+kp_offset, 2)]
kps = [ia.Keypoint(x, y) for x, y in expected_k1_kps]
return ia.KeypointsOnImage(kps, shape=(8, 4, 3))
@property
def kpsoi_k2(self):
# without keep size
kp_offset = self.kp_offset
expected_k1_kps = self.kpsoi_k1.to_xy_array()
expected_k2_kps = [
(8-expected_k1_kps[0][1]+kp_offset, expected_k1_kps[0][0]),
(8-expected_k1_kps[1][1]+kp_offset, expected_k1_kps[1][0])]
kps = [ia.Keypoint(x, y) for x, y in expected_k2_kps]
return ia.KeypointsOnImage(kps, shape=(4, 8, 3))
@property
def kpsoi_k3(self):
# without keep size
kp_offset = self.kp_offset
expected_k2_kps = self.kpsoi_k2.to_xy_array()
expected_k3_kps = [
(4-expected_k2_kps[0][1]+kp_offset, expected_k2_kps[0][0]),
(4-expected_k2_kps[1][1]+kp_offset, expected_k2_kps[1][0])]
kps = [ia.Keypoint(x, y) for x, y in expected_k3_kps]
return ia.KeypointsOnImage(kps, shape=(8, 4, 3))
@property
def psoi_k1(self):
# without keep size
kp_offset = self.kp_offset
expected_k1_polys = [(4-1+kp_offset, 1),
(4-1+kp_offset, 3),
(4-3+kp_offset, 3),
(4-3+kp_offset, 1)]
return ia.PolygonsOnImage([ia.Polygon(expected_k1_polys)],
shape=(8, 4, 3))
@property
def psoi_k2(self):
# without keep size
kp_offset = self.kp_offset
expected_k1_polys = self.psoi_k1.polygons[0].exterior
expected_k2_polys = [
(8-expected_k1_polys[0][1]+kp_offset, expected_k1_polys[0][0]),
(8-expected_k1_polys[1][1]+kp_offset, expected_k1_polys[1][0]),
(8-expected_k1_polys[2][1]+kp_offset, expected_k1_polys[2][0]),
(8-expected_k1_polys[3][1]+kp_offset, expected_k1_polys[3][0])]
return ia.PolygonsOnImage([ia.Polygon(expected_k2_polys)],
shape=(4, 8, 3))
@property
def psoi_k3(self):
# without keep size
kp_offset = self.kp_offset
expected_k2_polys = self.psoi_k2.polygons[0].exterior
expected_k3_polys = [
(4-expected_k2_polys[0][1]+kp_offset, expected_k2_polys[0][0]),
(4-expected_k2_polys[1][1]+kp_offset, expected_k2_polys[1][0]),
(4-expected_k2_polys[2][1]+kp_offset, expected_k2_polys[2][0]),
(4-expected_k2_polys[3][1]+kp_offset, expected_k2_polys[3][0])]
return ia.PolygonsOnImage([ia.Polygon(expected_k3_polys)],
shape=(8, 4, 3))
@property
def lsoi_k1(self):
# without keep size
kp_offset = self.kp_offset
expected_k1_ls = [(4-1+kp_offset, 1),
(4-1+kp_offset, 3),
(4-3+kp_offset, 3),
(4-3+kp_offset, 1)]
return ia.LineStringsOnImage([ia.LineString(expected_k1_ls)],
shape=(8, 4, 3))
@property
def lsoi_k2(self):
# without keep size
kp_offset = self.kp_offset
expected_k1_ls = self.psoi_k1.items[0].coords
expected_k2_ls = [
(8-expected_k1_ls[0][1]+kp_offset, expected_k1_ls[0][0]),
(8-expected_k1_ls[1][1]+kp_offset, expected_k1_ls[1][0]),
(8-expected_k1_ls[2][1]+kp_offset, expected_k1_ls[2][0]),
(8-expected_k1_ls[3][1]+kp_offset, expected_k1_ls[3][0])]
return ia.LineStringsOnImage([ia.LineString(expected_k2_ls)],
shape=(4, 8, 3))
@property
def lsoi_k3(self):
# without keep size
kp_offset = self.kp_offset
expected_k2_ls = self.lsoi_k2.items[0].coords
expected_k3_ls = [
(4-expected_k2_ls[0][1]+kp_offset, expected_k2_ls[0][0]),
(4-expected_k2_ls[1][1]+kp_offset, expected_k2_ls[1][0]),
(4-expected_k2_ls[2][1]+kp_offset, expected_k2_ls[2][0]),
(4-expected_k2_ls[3][1]+kp_offset, expected_k2_ls[3][0])]
return ia.LineStringsOnImage([ia.LineString(expected_k3_ls)],
shape=(8, 4, 3))
@property
def bbsoi_k1(self):
# without keep size
kp_offset = self.kp_offset
expected_k1_coords = [
(4-1+kp_offset, 1),
(4-3+kp_offset, 3)]
return ia.BoundingBoxesOnImage([
ia.BoundingBox(
x1=min(expected_k1_coords[0][0], expected_k1_coords[1][0]),
y1=min(expected_k1_coords[0][1], expected_k1_coords[1][1]),
x2=max(expected_k1_coords[1][0], expected_k1_coords[0][0]),
y2=max(expected_k1_coords[1][1], expected_k1_coords[0][1])
)], shape=(8, 4, 3))
@property
def bbsoi_k2(self):
# without keep size
kp_offset = self.kp_offset
coords = self.bbsoi_k1.bounding_boxes[0].coords
expected_k2_coords = [
(8-coords[0][1]+kp_offset, coords[0][0]),
(8-coords[1][1]+kp_offset, coords[1][0])]
return ia.BoundingBoxesOnImage([
ia.BoundingBox(
x1=min(expected_k2_coords[0][0], expected_k2_coords[1][0]),
y1=min(expected_k2_coords[0][1], expected_k2_coords[1][1]),
x2=max(expected_k2_coords[1][0], expected_k2_coords[0][0]),
y2=max(expected_k2_coords[1][1], expected_k2_coords[0][1])
)],
shape=(4, 8, 3))
@property
def bbsoi_k3(self):
# without keep size
kp_offset = self.kp_offset
coords = self.bbsoi_k2.bounding_boxes[0].coords
expected_k3_coords = [
(4-coords[0][1]+kp_offset, coords[0][0]),
(4-coords[1][1]+kp_offset, coords[1][0])]
return ia.BoundingBoxesOnImage([
ia.BoundingBox(
x1=min(expected_k3_coords[0][0], expected_k3_coords[1][0]),
y1=min(expected_k3_coords[0][1], expected_k3_coords[1][1]),
x2=max(expected_k3_coords[1][0], expected_k3_coords[0][0]),
y2=max(expected_k3_coords[1][1], expected_k3_coords[0][1])
)],
shape=(8, 4, 3))
def test___init___k_is_list(self):
aug = iaa.Rot90([1, 3])
assert is_parameter_instance(aug.k, iap.Choice)
assert len(aug.k.a) == 2
assert aug.k.a[0] == 1
assert aug.k.a[1] == 3
def test___init___k_is_all(self):
aug = iaa.Rot90(ia.ALL)
assert is_parameter_instance(aug.k, iap.Choice)
assert len(aug.k.a) == 4
assert aug.k.a == [0, 1, 2, 3]
def test_images_k_is_0_and_4(self):
for k in [0, 4]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
img_aug = aug.augment_image(self.image)
assert img_aug.dtype.name == "uint8"
assert np.array_equal(img_aug, self.image)
def test_heatmaps_k_is_0_and_4(self):
for k in [0, 4]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
hms_aug = aug.augment_heatmaps([self.heatmaps])[0]
assert (hms_aug.arr_0to1.dtype.name
== self.heatmaps.arr_0to1.dtype.name)
assert np.allclose(hms_aug.arr_0to1, self.heatmaps.arr_0to1)
assert hms_aug.shape == self.heatmaps.shape
def test_segmaps_k_is_0_and_4(self):
for k in [0, 4]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
segmaps_aug = aug.augment_segmentation_maps(
[self.segmaps]
)[0]
assert (
segmaps_aug.arr.dtype.name
== self.segmaps.arr.dtype.name)
assert np.allclose(segmaps_aug.arr, self.segmaps.arr)
assert segmaps_aug.shape == self.segmaps.shape
def test_keypoints_k_is_0_and_4(self):
for k in [0, 4]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
kpsoi_aug = aug.augment_keypoints([self.kpsoi])[0]
assert_cbaois_equal(kpsoi_aug, self.kpsoi)
def test_polygons_k_is_0_and_4(self):
for k in [0, 4]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
psoi_aug = aug.augment_polygons(self.psoi)
assert_cbaois_equal(psoi_aug, self.psoi)
def test_line_strings_k_is_0_and_4(self):
for k in [0, 4]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
lsoi_aug = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(lsoi_aug, self.lsoi)
def test_bounding_boxes_k_is_0_and_4(self):
for k in [0, 4]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
bbsoi_aug = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(bbsoi_aug, self.bbsoi)
def test_images_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
img_aug = aug.augment_image(self.image)
assert img_aug.dtype.name == "uint8"
assert np.array_equal(img_aug,
np.rot90(self.image, 1, axes=(1, 0)))
def test_heatmaps_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
hms_aug = aug.augment_heatmaps([self.heatmaps])[0]
assert (hms_aug.arr_0to1.dtype.name
== self.heatmaps.arr_0to1.dtype.name)
assert np.allclose(
hms_aug.arr_0to1,
np.rot90(self.heatmaps.arr_0to1, 1, axes=(1, 0)))
assert hms_aug.shape == (4, 4, 3)
def test_heatmaps_smaller_than_image_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
hms_smaller_aug = aug.augment_heatmaps(
[self.heatmaps_smaller]
)[0]
assert (
hms_smaller_aug.arr_0to1.dtype.name
== self.heatmaps_smaller.arr_0to1.dtype.name)
assert np.allclose(
hms_smaller_aug.arr_0to1,
np.rot90(self.heatmaps_smaller.arr_0to1, 1, axes=(1, 0)))
assert hms_smaller_aug.shape == (8, 4, 3)
def test_segmaps_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
segmaps_aug = aug.augment_segmentation_maps(
[self.segmaps]
)[0]
assert (
segmaps_aug.arr.dtype.name
== self.segmaps.arr.dtype.name)
assert np.allclose(
segmaps_aug.arr,
np.rot90(self.segmaps.arr, 1, axes=(1, 0)))
assert segmaps_aug.shape == (4, 4, 3)
def test_segmaps_smaller_than_image_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
segmaps_smaller_aug = aug.augment_segmentation_maps(
self.segmaps_smaller)
assert (
segmaps_smaller_aug.arr.dtype.name
== self.segmaps_smaller.arr.dtype.name)
assert np.allclose(
segmaps_smaller_aug.arr,
np.rot90(self.segmaps_smaller.arr, 1, axes=(1, 0)))
assert segmaps_smaller_aug.shape == (8, 4, 3)
def test_keypoints_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
kpsoi_aug = aug.augment_keypoints([self.kpsoi])[0]
assert_cbaois_equal(kpsoi_aug, self.kpsoi_k1)
def test_polygons_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
psoi_aug = aug.augment_polygons(self.psoi)
assert_cbaois_equal(psoi_aug, self.psoi_k1)
def test_line_strings_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
lsoi_aug = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(lsoi_aug, self.lsoi_k1)
def test_bounding_boxes_k_is_1_and_5(self):
for k in [1, 5]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
bbsoi_aug = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(bbsoi_aug, self.bbsoi_k1)
def test_images_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
img = self.image
img_aug = aug.augment_image(img)
assert img_aug.dtype.name == "uint8"
assert np.array_equal(img_aug, np.rot90(img, 2, axes=(1, 0)))
def test_heatmaps_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
hms = self.heatmaps
hms_aug = aug.augment_heatmaps([hms])[0]
assert hms_aug.arr_0to1.dtype.name == hms.arr_0to1.dtype.name
assert np.allclose(
hms_aug.arr_0to1,
np.rot90(hms.arr_0to1, 2, axes=(1, 0)))
assert hms_aug.shape == (4, 4, 3)
def test_heatmaps_smaller_than_image_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
hms_smaller = self.heatmaps_smaller
hms_smaller_aug = aug.augment_heatmaps([hms_smaller])[0]
assert (hms_smaller_aug.arr_0to1.dtype.name
== hms_smaller.arr_0to1.dtype.name)
assert np.allclose(
hms_smaller_aug.arr_0to1,
np.rot90(hms_smaller.arr_0to1, 2, axes=(1, 0)))
assert hms_smaller_aug.shape == (4, 8, 3)
def test_segmaps_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
segmaps = self.segmaps
segmaps_aug = aug.augment_segmentation_maps([segmaps])[0]
assert segmaps_aug.arr.dtype.name == segmaps.arr.dtype.name
assert np.allclose(
segmaps_aug.arr,
np.rot90(segmaps.arr, 2, axes=(1, 0)))
assert segmaps_aug.shape == (4, 4, 3)
def test_segmaps_smaller_than_image_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
segmaps_smaller = self.segmaps_smaller
segmaps_smaller_aug = aug.augment_segmentation_maps(segmaps_smaller)
assert (segmaps_smaller_aug.arr.dtype.name
== segmaps_smaller.arr.dtype.name)
assert np.allclose(
segmaps_smaller_aug.arr,
np.rot90(segmaps_smaller.arr, 2, axes=(1, 0)))
assert segmaps_smaller_aug.shape == (4, 8, 3)
def test_keypoints_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
kpsoi_aug = aug.augment_keypoints([self.kpsoi])[0]
assert_cbaois_equal(kpsoi_aug, self.kpsoi_k2)
def test_polygons_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
psoi_aug = aug.augment_polygons(self.psoi)
assert_cbaois_equal(psoi_aug, self.psoi_k2)
def test_line_strings_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
lsoi_aug = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(lsoi_aug, self.lsoi_k2)
def test_bounding_boxes_k_is_2(self):
aug = iaa.Rot90(2, keep_size=False)
bbsoi_aug = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(bbsoi_aug, self.bbsoi_k2)
def test_images_k_is_3_and_minus1(self):
img = self.image
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
img_aug = aug.augment_image(img)
assert img_aug.dtype.name == "uint8"
assert np.array_equal(img_aug, np.rot90(img, 3, axes=(1, 0)))
def test_heatmaps_k_is_3_and_minus1(self):
hms = self.heatmaps
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
hms_aug = aug.augment_heatmaps([hms])[0]
assert (hms_aug.arr_0to1.dtype.name
== hms.arr_0to1.dtype.name)
assert np.allclose(
hms_aug.arr_0to1,
np.rot90(hms.arr_0to1, 3, axes=(1, 0)))
assert hms_aug.shape == (4, 4, 3)
def test_heatmaps_smaller_than_image_k_is_3_and_minus1(self):
hms_smaller = self.heatmaps_smaller
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
hms_smaller_aug = aug.augment_heatmaps([hms_smaller])[0]
assert (hms_smaller_aug.arr_0to1.dtype.name
== hms_smaller.arr_0to1.dtype.name)
assert np.allclose(
hms_smaller_aug.arr_0to1,
np.rot90(hms_smaller.arr_0to1, 3, axes=(1, 0)))
assert hms_smaller_aug.shape == (8, 4, 3)
def test_segmaps_k_is_3_and_minus1(self):
segmaps = self.segmaps
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
segmaps_aug = aug.augment_segmentation_maps([segmaps])[0]
assert (segmaps_aug.arr.dtype.name
== segmaps.arr.dtype.name)
assert np.allclose(
segmaps_aug.arr,
np.rot90(segmaps.arr, 3, axes=(1, 0)))
assert segmaps_aug.shape == (4, 4, 3)
def test_segmaps_smaller_than_image_k_is_3_and_minus1(self):
segmaps_smaller = self.segmaps_smaller
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
segmaps_smaller_aug = aug.augment_segmentation_maps(
segmaps_smaller)
assert (segmaps_smaller_aug.arr.dtype.name
== segmaps_smaller.arr.dtype.name)
assert np.allclose(
segmaps_smaller_aug.arr,
np.rot90(segmaps_smaller.arr, 3, axes=(1, 0)))
assert segmaps_smaller_aug.shape == (8, 4, 3)
def test_keypoints_k_is_3_and_minus1(self):
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
kpsoi_aug = aug.augment_keypoints([self.kpsoi])[0]
assert_cbaois_equal(kpsoi_aug, self.kpsoi_k3)
def test_polygons_k_is_3_and_minus1(self):
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
psoi_aug = aug.augment_polygons(self.psoi)
assert_cbaois_equal(psoi_aug, self.psoi_k3)
def test_line_strings_k_is_3_and_minus1(self):
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
lsoi_aug = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(lsoi_aug, self.lsoi_k3)
def test_bounding_boxes_k_is_3_and_minus1(self):
for k in [3, -1]:
with self.subTest(k=k):
aug = iaa.Rot90(k, keep_size=False)
bbsoi_aug = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(bbsoi_aug, self.bbsoi_k3)
def test_images_k_is_1_verify_without_using_numpy_rot90(self):
# verify once without np.rot90
aug = iaa.Rot90(k=1, keep_size=False)
image = np.uint8([[1, 0, 0],
[0, 2, 0]])
img_aug = aug.augment_image(image)
expected = np.uint8([[0, 1], [2, 0], [0, 0]])
assert np.array_equal(img_aug, expected)
def test_images_k_is_1_keep_size_is_true(self):
# keep_size=True, k=1
aug = iaa.Rot90(1, keep_size=True)
img_nonsquare = np.arange(5*4*3).reshape((5, 4, 3)).astype(np.uint8)
img_aug = aug.augment_image(img_nonsquare)
assert img_aug.dtype.name == "uint8"
assert np.array_equal(
img_aug,
ia.imresize_single_image(
np.rot90(img_nonsquare, 1, axes=(1, 0)),
(5, 4)
)
)
def test_heatmaps_k_is_1_keep_size_is_true(self):
aug = iaa.Rot90(1, keep_size=True)
hms = self.heatmaps
hms_aug = aug.augment_heatmaps([hms])[0]
assert hms_aug.arr_0to1.dtype.name == hms.arr_0to1.dtype.name
assert np.allclose(
hms_aug.arr_0to1,
np.rot90(hms.arr_0to1, 1, axes=(1, 0)))
assert hms_aug.shape == (4, 4, 3)
def test_heatmaps_smaller_than_image_k_is_1_keep_size_is_true(self):
aug = iaa.Rot90(1, keep_size=True)
hms_smaller = self.heatmaps_smaller
hms_smaller_aug = aug.augment_heatmaps([hms_smaller])[0]
hms_smaller_rot = np.rot90(hms_smaller.arr_0to1, 1, axes=(1, 0))
hms_smaller_rot = np.clip(
ia.imresize_single_image(
hms_smaller_rot, (2, 3), interpolation="cubic"
),
0.0, 1.0)
assert (hms_smaller_aug.arr_0to1.dtype.name
== hms_smaller.arr_0to1.dtype.name)
assert np.allclose(hms_smaller_aug.arr_0to1, hms_smaller_rot)
assert hms_smaller_aug.shape == (4, 8, 3)
def test_segmaps_k_is_1_keep_size_is_true(self):
aug = iaa.Rot90(1, keep_size=True)
segmaps = self.segmaps
segmaps_aug = aug.augment_segmentation_maps([segmaps])[0]
assert (segmaps_aug.arr.dtype.name
== segmaps.arr.dtype.name)
assert np.allclose(segmaps_aug.arr,
np.rot90(segmaps.arr, 1, axes=(1, 0)))
assert segmaps_aug.shape == (4, 4, 3)
def test_segmaps_smaller_than_image_k_is_1_keep_size_is_true(self):
aug = iaa.Rot90(1, keep_size=True)
segmaps_smaller = self.segmaps_smaller
segmaps_smaller_aug = aug.augment_segmentation_maps(segmaps_smaller)
segmaps_smaller_rot = np.rot90(segmaps_smaller.arr, 1, axes=(1, 0))
segmaps_smaller_rot = ia.imresize_single_image(
segmaps_smaller_rot, (2, 3), interpolation="nearest")
assert (segmaps_smaller_aug.arr.dtype.name
== segmaps_smaller.arr.dtype.name)
assert np.allclose(segmaps_smaller_aug.arr, segmaps_smaller_rot)
assert segmaps_smaller_aug.shape == (4, 8, 3)
def test_keypoints_k_is_1_keep_size_is_true(self):
aug = iaa.Rot90(1, keep_size=True)
kp_offset = self.kp_offset
kpsoi = self.kpsoi
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
expected = [(4-2+kp_offset, 1), (4-3+kp_offset, 2)]
expected = [(8*x/4, 4*y/8) for x, y in expected]
assert kpsoi_aug.shape == (4, 8, 3)
for kp_aug, kp in zip(kpsoi_aug.keypoints, expected):
assert np.allclose([kp_aug.x, kp_aug.y], [kp[0], kp[1]])
def test_polygons_k_is_1_keep_size_is_true(self):
aug = iaa.Rot90(1, keep_size=True)
psoi = self.psoi
kp_offset = self.kp_offset
psoi_aug = aug.augment_polygons(psoi)
expected = [(4-1+kp_offset, 1), (4-1+kp_offset, 3),
(4-3+kp_offset, 3), (4-3+kp_offset, 1)]
expected = [(8*x/4, 4*y/8) for x, y in expected]
assert psoi_aug.shape == (4, 8, 3)
assert len(psoi_aug.polygons) == 1
assert psoi_aug.polygons[0].is_valid
assert psoi_aug.polygons[0].exterior_almost_equals(expected)
def test_line_strings_k_is_1_keep_size_is_true(self):
aug = iaa.Rot90(1, keep_size=True)
lsoi = self.lsoi
kp_offset = self.kp_offset
lsoi_aug = aug.augment_line_strings(lsoi)
expected = [(4-1+kp_offset, 1), (4-1+kp_offset, 3),
(4-3+kp_offset, 3), (4-3+kp_offset, 1)]
expected = [(8*x/4, 4*y/8) for x, y in expected]
assert lsoi_aug.shape == (4, 8, 3)
assert len(lsoi_aug.items) == 1
assert lsoi_aug.items[0].coords_almost_equals(expected)
def test_bounding_boxes_k_is_1_keep_size_is_true(self):
aug = iaa.Rot90(1, keep_size=True)
bbsoi = self.bbsoi
kp_offset = self.kp_offset
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
expected = [(4-1+kp_offset, 1),
(4-3+kp_offset, 3)]
expected = [(8*x/4, 4*y/8) for x, y in expected]
expected = np.float32([
[min(expected[0][0], expected[1][0]),
min(expected[0][1], expected[1][1])],
[max(expected[0][0], expected[1][0]),
max(expected[0][1], expected[1][1])]
])
assert bbsoi_aug.shape == (4, 8, 3)
assert len(bbsoi_aug.bounding_boxes) == 1
assert bbsoi_aug.bounding_boxes[0].coords_almost_equals(expected)
def test_images_k_is_list(self):
aug = iaa.Rot90(_TwoValueParam(1, 2), keep_size=False)
img = self.image
imgs_aug = aug.augment_images([img] * 4)
assert np.array_equal(imgs_aug[0], np.rot90(img, 1, axes=(1, 0)))
assert np.array_equal(imgs_aug[1], np.rot90(img, 2, axes=(1, 0)))
assert np.array_equal(imgs_aug[2], np.rot90(img, 1, axes=(1, 0)))
assert np.array_equal(imgs_aug[3], np.rot90(img, 2, axes=(1, 0)))
def test_heatmaps_smaller_than_image_k_is_list(self):
def _rot_hm(hm, k):
return np.rot90(hm.arr_0to1, k, axes=(1, 0))
aug = iaa.Rot90(_TwoValueParam(1, 2), keep_size=False)
hms_smaller = self.heatmaps_smaller
hms_aug = aug.augment_heatmaps([hms_smaller] * 4)
assert hms_aug[0].shape == (8, 4, 3)
assert hms_aug[1].shape == (4, 8, 3)
assert hms_aug[2].shape == (8, 4, 3)
assert hms_aug[3].shape == (4, 8, 3)
assert np.allclose(hms_aug[0].arr_0to1, _rot_hm(hms_smaller, 1))
assert np.allclose(hms_aug[1].arr_0to1, _rot_hm(hms_smaller, 2))
assert np.allclose(hms_aug[2].arr_0to1, _rot_hm(hms_smaller, 1))
assert np.allclose(hms_aug[3].arr_0to1, _rot_hm(hms_smaller, 2))
def test_segmaps_smaller_than_image_k_is_list(self):
def _rot_sm(segmap, k):
return np.rot90(segmap.arr, k, axes=(1, 0))
aug = iaa.Rot90(_TwoValueParam(1, 2), keep_size=False)
segmaps_smaller = self.segmaps_smaller
segmaps_aug = aug.augment_segmentation_maps([segmaps_smaller] * 4)
assert segmaps_aug[0].shape == (8, 4, 3)
assert segmaps_aug[1].shape == (4, 8, 3)
assert segmaps_aug[2].shape == (8, 4, 3)
assert segmaps_aug[3].shape == (4, 8, 3)
assert np.allclose(segmaps_aug[0].arr, _rot_sm(segmaps_smaller, 1))
assert np.allclose(segmaps_aug[1].arr, _rot_sm(segmaps_smaller, 2))
assert np.allclose(segmaps_aug[2].arr, _rot_sm(segmaps_smaller, 1))
assert np.allclose(segmaps_aug[3].arr, _rot_sm(segmaps_smaller, 2))
def test_keypoints_k_is_list(self):
aug = iaa.Rot90(_TwoValueParam(1, 2), keep_size=False)
kpsoi = self.kpsoi
kpsoi_aug = aug.augment_keypoints([kpsoi] * 4)
assert_cbaois_equal(kpsoi_aug[0], self.kpsoi_k1)
assert_cbaois_equal(kpsoi_aug[1], self.kpsoi_k2)
assert_cbaois_equal(kpsoi_aug[2], self.kpsoi_k1)
assert_cbaois_equal(kpsoi_aug[3], self.kpsoi_k2)
def test_polygons_k_is_list(self):
aug = iaa.Rot90(_TwoValueParam(1, 2), keep_size=False)
psoi = self.psoi
psoi_aug = aug.augment_polygons([psoi] * 4)
assert_cbaois_equal(psoi_aug[0], self.psoi_k1)
assert_cbaois_equal(psoi_aug[1], self.psoi_k2)
assert_cbaois_equal(psoi_aug[2], self.psoi_k1)
assert_cbaois_equal(psoi_aug[3], self.psoi_k2)
def test_line_strings_k_is_list(self):
aug = iaa.Rot90(_TwoValueParam(1, 2), keep_size=False)
lsoi = self.lsoi
lsoi_aug = aug.augment_line_strings([lsoi] * 4)
assert_cbaois_equal(lsoi_aug[0], self.lsoi_k1)
assert_cbaois_equal(lsoi_aug[1], self.lsoi_k2)
assert_cbaois_equal(lsoi_aug[2], self.lsoi_k1)
assert_cbaois_equal(lsoi_aug[3], self.lsoi_k2)
def test_bounding_boxes_k_is_list(self):
aug = iaa.Rot90(_TwoValueParam(1, 2), keep_size=False)
bbsoi = self.bbsoi
bbsoi_aug = aug.augment_bounding_boxes([bbsoi] * 4)
assert_cbaois_equal(bbsoi_aug[0], self.bbsoi_k1)
assert_cbaois_equal(bbsoi_aug[1], self.bbsoi_k2)
assert_cbaois_equal(bbsoi_aug[2], self.bbsoi_k1)
assert_cbaois_equal(bbsoi_aug[3], self.bbsoi_k2)
def test_empty_keypoints(self):
aug = iaa.Rot90(k=1, keep_size=False)
kpsoi = ia.KeypointsOnImage([], shape=(4, 8, 3))
kpsoi_aug = aug.augment_keypoints(kpsoi)
expected = self.kpsoi_k1
expected.keypoints = []
assert_cbaois_equal(kpsoi_aug, expected)
def test_empty_polygons(self):
aug = iaa.Rot90(k=1, keep_size=False)
psoi = ia.PolygonsOnImage([], shape=(4, 8, 3))
psoi_aug = aug.augment_polygons(psoi)
expected = self.psoi_k1
expected.polygons = []
assert_cbaois_equal(psoi_aug, expected)
def test_empty_line_strings(self):
aug = iaa.Rot90(k=1, keep_size=False)
lsoi = ia.LineStringsOnImage([], shape=(4, 8, 3))
lsoi_aug = aug.augment_line_strings(lsoi)
expected = self.lsoi_k1
expected.line_strings = []
assert_cbaois_equal(lsoi_aug, expected)
def test_empty_bounding_boxes(self):
aug = iaa.Rot90(k=1, keep_size=False)
bbsoi = ia.BoundingBoxesOnImage([], shape=(4, 8, 3))
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
expected = self.bbsoi_k1
expected.bounding_boxes = []
assert_cbaois_equal(bbsoi_aug, expected)
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Rot90(k=1)
image_aug = aug(image=image)
shape_expected = tuple([shape[1], shape[0]] + list(shape[2:]))
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape_expected
def test_zero_sized_axes_k_0_or_2(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
for keep_size in [False, True]:
with self.subTest(shape=shape, keep_size=keep_size):
for _ in sm.xrange(10):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Rot90([0, 2], keep_size=keep_size)
image_aug = aug(image=image)
assert image_aug.shape == shape
def test_zero_sized_axes_k_1_or_3_no_keep_size(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
for _ in sm.xrange(10):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Rot90([1, 3], keep_size=False)
image_aug = aug(image=image)
shape_expected = tuple([shape[1], shape[0]]
+ list(shape[2:]))
assert image_aug.shape == shape_expected
def test_zero_sized_axes_k_1_or_3_keep_size(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
for _ in sm.xrange(10):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Rot90([1, 3], keep_size=True)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_get_parameters(self):
aug = iaa.Rot90([1, 3], keep_size=False)
assert aug.get_parameters()[0] == aug.k
assert aug.get_parameters()[1] is False
def test_other_dtypes_bool(self):
aug = iaa.Rot90(2)
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert np.all(image_aug[0, 0] == 0)
assert np.all(image_aug[2, 2] == 1)
def test_other_dtypes_uint_int(self):
aug = iaa.Rot90(2)
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = max_value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug[0, 0] == 0)
assert np.all(image_aug[2, 2] == max_value)
def test_other_dtypes_float(self):
aug = iaa.Rot90(2)
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
def _allclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.allclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [
0,
1.0,
10.0,
100.0,
high_res_dt(500 ** (isize-1)),
high_res_dt(1000 ** (isize-1))
]
values = values + [(-1) * value for value in values]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert _allclose(image_aug[0, 0], 0)
assert _allclose(image_aug[2, 2], high_res_dt(value))
def test_pickleable(self):
aug = iaa.Rot90([0, 1, 2, 3], seed=1)
runtest_pickleable_uint8_img(aug, iterations=5)
class TestWithPolarWarping(unittest.TestCase):
def setUp(self):
reseed()
def test___init___single_augmenter_as_child(self):
aug = iaa.WithPolarWarping(iaa.Noop())
assert isinstance(aug.children, iaa.Sequential)
assert isinstance(aug.children[0], iaa.Noop)
def test___init___list_of_augmenters_as_child(self):
aug = iaa.WithPolarWarping([iaa.Noop(), iaa.Noop()])
assert isinstance(aug.children, iaa.Sequential)
assert isinstance(aug.children[0], iaa.Noop)
assert isinstance(aug.children[1], iaa.Noop)
def test_images_no_change(self):
image = np.mod(np.arange(10*20*3), 255).astype(np.uint8)
image = image.reshape((10, 20, 3))
aug = iaa.WithPolarWarping(iaa.Noop())
image_aug = aug(image=image)
avg_dist = np.average(
np.abs(
image_aug.astype(np.int32)[2:-2, 2:-2]
- image.astype(np.int32)[2:-2, 2:-2]
)
)
assert image_aug.shape == (10, 20, 3)
assert avg_dist < 7.0
def test_heatmaps_no_change(self):
hm = np.linspace(0, 1.0, 10*20, dtype=np.float32).reshape((10, 20, 1))
hm = ia.HeatmapsOnImage(hm, shape=(10, 20, 3))
aug = iaa.WithPolarWarping(iaa.Noop())
hm_aug = aug(heatmaps=hm)
avg_dist = np.average(
np.abs(
hm_aug.get_arr()[2:-2, 2:-2]
- hm.get_arr()[2:-2, 2:-2]
)
)
assert hm_aug.shape == (10, 20, 3)
assert avg_dist < 0.0125
def test_segmentation_maps_no_change(self):
sm = np.zeros((10, 20, 1), dtype=np.int32)
sm[1, 0:5] = 1
sm[3:3, 3:3] = 2
sm[7:9, :] = 3
sm = ia.SegmentationMapsOnImage(sm, shape=(10, 20, 3))
aug = iaa.WithPolarWarping(iaa.Noop())
sm_aug = aug(segmentation_maps=sm)
p_same = np.average(
sm_aug.get_arr()[2:-2, 2:-2]
== sm.get_arr()[2:-2, 2:-2]
)
assert sm_aug.shape == (10, 20, 3)
assert p_same > 0.95
def test_keypoints_no_change(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=5, y=5),
ia.Keypoint(x=5, y=9)]
kpsoi = ia.KeypointsOnImage(kps, shape=(10, 20, 3))
aug = iaa.WithPolarWarping(iaa.Noop())
kpsoi_aug = aug(keypoints=kpsoi)
assert kpsoi_aug.shape == (10, 20, 3)
assert np.allclose(kpsoi_aug.to_xy_array(), kpsoi.to_xy_array(),
atol=0.01)
def test_bounding_boxes_no_change(self):
bbs = [
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4, label="foo"),
ia.BoundingBox(x1=3, y1=5, x2=7, y2=10),
]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 20, 3))
aug = iaa.WithPolarWarping(iaa.Noop())
bbsoi_aug = aug(bounding_boxes=bbsoi)
assert bbsoi_aug.items[0].label == "foo"
assert bbsoi_aug.items[1].label is None
assert bbsoi_aug.shape == (10, 20, 3)
assert np.allclose(bbsoi_aug.to_xy_array(), bbsoi.to_xy_array(),
atol=0.01)
def test_polygons_no_change(self):
ps = [
ia.Polygon([(0, 2), (4, 2), (4, 4)], label="foo"),
ia.Polygon([(0, 0), (5, 0), (5, 5), (0, 5)])
]
psoi = ia.PolygonsOnImage(ps, shape=(10, 20, 3))
aug = iaa.WithPolarWarping(iaa.Noop())
psoi_aug = aug(polygons=psoi)
assert psoi_aug.items[0].label == "foo"
assert psoi_aug.items[1].label is None
assert psoi_aug.shape == (10, 20, 3)
assert np.allclose(psoi_aug.to_xy_array(), psoi.to_xy_array(),
atol=0.01)
def test_line_strings_no_change(self):
ls = [
ia.LineString([(0, 2), (4, 2), (4, 4)]),
ia.LineString([(0, 0), (5, 0), (5, 5), (0, 5)])
]
lsoi = ia.LineStringsOnImage(ls, shape=(10, 20, 3))
aug = iaa.WithPolarWarping(iaa.Noop())
lsoi_aug = aug(line_strings=lsoi)
assert lsoi_aug.shape == (10, 20, 3)
assert np.allclose(lsoi_aug.to_xy_array(), lsoi.to_xy_array(),
atol=0.01)
def test_bounding_boxes_and_polygons_provided_no_change(self):
bbs = [
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4, label="foo"),
ia.BoundingBox(x1=3, y1=5, x2=7, y2=10),
]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 20, 3))
ps = [
ia.Polygon([(0, 2), (4, 2), (4, 4)], label="foo"),
ia.Polygon([(0, 0), (5, 0), (5, 5), (0, 5)])
]
psoi = ia.PolygonsOnImage(ps, shape=(10, 20, 3))
aug = iaa.WithPolarWarping(iaa.Noop())
aug = aug.to_deterministic()
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
psoi_aug = aug.augment_polygons(psoi)
assert bbsoi_aug.items[0].label == "foo"
assert bbsoi_aug.items[1].label is None
assert bbsoi_aug.shape == (10, 20, 3)
assert np.allclose(bbsoi_aug.to_xy_array(), bbsoi.to_xy_array(),
atol=0.01)
assert psoi_aug.items[0].label == "foo"
assert psoi_aug.items[1].label is None
assert psoi_aug.shape == (10, 20, 3)
assert np.allclose(psoi_aug.to_xy_array(), psoi.to_xy_array(),
atol=0.01)
def test_images_translation_x(self):
image = np.zeros((50, 70, 3), dtype=np.uint8)
image[20-1:20+1, 30-1:30+1, 0] = 255
image[30-1:30+1, 40-1:40+1, 1] = 255
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 15}))
image_aug = aug(image=image)
x1 = np.argmax(np.max(image_aug[..., 0], axis=0))
y1 = np.argmax(np.max(image_aug[..., 0], axis=1))
x2 = np.argmax(np.max(image_aug[..., 1], axis=0))
y2 = np.argmax(np.max(image_aug[..., 1], axis=1))
# translation on x axis in polar representation should move all points
# a bit away from the center
min_diff = 4
assert image_aug.shape == (50, 70, 3)
assert x1 < 30 - min_diff
assert y1 < 20 - min_diff
assert x2 > 40 + min_diff
assert y2 > 30 + min_diff
def test_heatmaps_translation_x(self):
hm = np.zeros((50, 70, 2), dtype=np.float32)
hm[20-1:20+1, 30-1:30+1, 0] = 1.0
hm[30-1:30+1, 40-1:40+1, 1] = 1.0
hm = ia.HeatmapsOnImage(hm, shape=(50, 70, 3))
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 15}))
hm_aug = aug(heatmaps=hm)
hm_aug_arr = hm_aug.get_arr()
x1 = np.argmax(np.max(hm_aug_arr[..., 0], axis=0))
y1 = np.argmax(np.max(hm_aug_arr[..., 0], axis=1))
x2 = np.argmax(np.max(hm_aug_arr[..., 1], axis=0))
y2 = np.argmax(np.max(hm_aug_arr[..., 1], axis=1))
# translation on x axis in polar representation should move all points
# a bit away from the center
min_diff = 4
assert hm_aug_arr.shape == (50, 70, 2)
assert hm_aug.shape == (50, 70, 3)
assert x1 < 30 - min_diff
assert y1 < 20 - min_diff
assert x2 > 40 + min_diff
assert y2 > 30 + min_diff
def test_segmentation_maps_translation_x(self):
sm = np.zeros((50, 70, 2), dtype=np.int32)
sm[20-1:20+1, 30-1:30+1, 0] = 1
sm[30-1:30+1, 40-1:40+1, 1] = 2
sm = ia.SegmentationMapsOnImage(sm, shape=(50, 70, 3))
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 15}))
sm_aug = aug(segmentation_maps=sm)
sm_aug_arr = sm_aug.get_arr()
x1 = np.argmax(np.max(sm_aug_arr[..., 0], axis=0))
y1 = np.argmax(np.max(sm_aug_arr[..., 0], axis=1))
x2 = np.argmax(np.max(sm_aug_arr[..., 1], axis=0))
y2 = np.argmax(np.max(sm_aug_arr[..., 1], axis=1))
# translation on x axis in polar representation should move all points
# a bit away from the center
min_diff = 4
assert sm_aug_arr.shape == (50, 70, 2)
assert sm_aug.shape == (50, 70, 3)
assert x1 < 30 - min_diff
assert y1 < 20 - min_diff
assert x2 > 40 + min_diff
assert y2 > 30 + min_diff
def test_keypoints_translation_x(self):
cbas = [ia.Keypoint(y=20, x=30), ia.Keypoint(y=30, x=40)]
cbaoi = ia.KeypointsOnImage(cbas, shape=(50, 70, 3))
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 15}))
cbaoi_aug = aug(keypoints=cbaoi)
x1 = cbaoi_aug.items[0].x
y1 = cbaoi_aug.items[0].y
x2 = cbaoi_aug.items[1].x
y2 = cbaoi_aug.items[1].y
# translation on x axis in polar representation should move all points
# a bit away from the center
min_diff = 4
assert cbaoi_aug.shape == (50, 70, 3)
assert x1 < 30 - min_diff
assert y1 < 20 - min_diff
assert x2 > 40 + min_diff
assert y2 > 30 + min_diff
def test_bounding_boxes_translation_x(self):
cbas = [ia.BoundingBox(y1=20, x1=30, y2=20+2, x2=30+2),
ia.BoundingBox(y1=30, x1=40, y2=30+2, x2=40+2)]
cbaoi = ia.BoundingBoxesOnImage(cbas, shape=(50, 70, 3))
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 15}))
cbaoi_aug = aug(bounding_boxes=cbaoi)
x1 = cbaoi_aug.items[0].x1
y1 = cbaoi_aug.items[0].y1
x2 = cbaoi_aug.items[1].x2
y2 = cbaoi_aug.items[1].y2
# translation on x axis in polar representation should move all points
# a bit away from the center
min_diff = 4
assert cbaoi_aug.shape == (50, 70, 3)
assert x1 < 30 - min_diff
assert y1 < 20 - min_diff
assert x2 > 40 + min_diff
assert y2 > 30 + min_diff
def test_polygons_translation_x(self):
cbas = [ia.Polygon([(30, 20), (30+2, 20), (30+2, 20+2)]),
ia.Polygon([(40, 30), (40+2, 30), (40+2, 30+2)])]
cbaoi = ia.PolygonsOnImage(cbas, shape=(50, 70, 3))
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 15}))
cbaoi_aug = aug(polygons=cbaoi)
x1 = cbaoi_aug.items[0].coords[0][0]
y1 = cbaoi_aug.items[0].coords[0][1]
x2 = cbaoi_aug.items[1].coords[2][0]
y2 = cbaoi_aug.items[1].coords[2][1]
# translation on x axis in polar representation should move all points
# a bit away from the center
min_diff = 4
assert cbaoi_aug.shape == (50, 70, 3)
assert x1 < 30 - min_diff
assert y1 < 20 - min_diff
assert x2 > 40 + min_diff
assert y2 > 30 + min_diff
def test_line_strings_translation_x(self):
cbas = [ia.LineString([(30, 20), (30+2, 20), (30+2, 20+2)]),
ia.LineString([(40, 30), (40+2, 30), (40+2, 30+2)])]
cbaoi = ia.LineStringsOnImage(cbas, shape=(50, 70, 3))
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 15}))
cbaoi_aug = aug(line_strings=cbaoi)
x1 = cbaoi_aug.items[0].coords[0][0]
y1 = cbaoi_aug.items[0].coords[0][1]
x2 = cbaoi_aug.items[1].coords[2][0]
y2 = cbaoi_aug.items[1].coords[2][1]
# translation on x axis in polar representation should move all points
# a bit away from the center
min_diff = 4
assert cbaoi_aug.shape == (50, 70, 3)
assert x1 < 30 - min_diff
assert y1 < 20 - min_diff
assert x2 > 40 + min_diff
assert y2 > 30 + min_diff
def test_image_heatmap_alignment(self):
image = np.zeros((80, 100, 3), dtype=np.uint8)
image[40-10:40+10, 50-10:50+10, :] = 255
hm = np.zeros((40, 50, 1), dtype=np.float32)
hm[20-5:20+5, 25-5:25+5, :] = 1.0
hm = ia.HeatmapsOnImage(hm, shape=image.shape)
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 10}))
image_aug, hm_aug = aug(image=image, heatmaps=hm)
hm_aug_arr = hm_aug.get_arr()
hm_aug_arr_rs = ia.imresize_single_image(hm_aug_arr, (80, 100),
interpolation="nearest")
overlap = np.average(
(image_aug[..., 0] > 200)
== (hm_aug_arr_rs[..., 0] > 0.9)
)
assert image_aug.shape == (80, 100, 3)
assert hm_aug.shape == (80, 100, 3)
assert hm_aug_arr.shape == (40, 50, 1)
assert overlap > 0.96
def test_image_segmentation_map_alignment(self):
image = np.zeros((80, 100, 3), dtype=np.uint8)
image[40-10:40+10, 50-10:50+10, :] = 255
sm = np.zeros((40, 50, 1), dtype=np.int32)
sm[20-5:20+5, 25-5:25+5, :] = 1
sm = ia.SegmentationMapsOnImage(sm, shape=image.shape)
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 10}))
image_aug, sm_aug = aug(image=image, segmentation_maps=sm)
sm_aug_arr = sm_aug.get_arr()
sm_aug_arr_rs = ia.imresize_single_image(sm_aug_arr, (80, 100),
interpolation="nearest")
overlap = np.average(
(image_aug[..., 0] > 200)
== (sm_aug_arr_rs[..., 0] == 1)
)
assert image_aug.shape == (80, 100, 3)
assert sm_aug.shape == (80, 100, 3)
assert sm_aug_arr.shape == (40, 50, 1)
assert overlap > 0.96
def test_image_keypoint_alignment(self):
image = np.zeros((80, 100, 3), dtype=np.uint8)
image[40-10:40-10+3, 50-10:50-10+3, :] = 255
image[40+10:40+10+3, 50+10:50+10+3, :] = 255
kps = [ia.Keypoint(y=40-10+1.5, x=50-10+1.5),
ia.Keypoint(y=40+10+1.5, x=50+10+1.5)]
kpsoi = ia.KeypointsOnImage(kps, shape=image.shape)
aug = iaa.WithPolarWarping(iaa.Affine(translate_px={"x": 10}))
image_aug, kpsoi_aug = aug(image=image, keypoints=kpsoi)
kp1 = kpsoi_aug.items[0]
kp2 = kpsoi_aug.items[1]
kp1_intensity = image_aug[int(kp1.y), int(kp1.x), 0]
kp2_intensity = image_aug[int(kp2.y), int(kp2.x), 0]
assert image_aug.shape == (80, 100, 3)
assert kpsoi_aug.shape == (80, 100, 3)
assert kp1_intensity > 200
assert kp2_intensity > 200
def test_image_is_noncontiguous(self):
image = np.mod(np.arange(10*20*3), 255).astype(np.uint8)
image = image.reshape((10, 20, 3))
image_cp = np.fliplr(np.copy(image))
image = np.fliplr(image)
assert image.flags["C_CONTIGUOUS"] is False
aug = iaa.WithPolarWarping(iaa.Noop())
image_aug = aug(image=image)
avg_dist = np.average(
np.abs(
image_aug.astype(np.int32)[2:-2, 2:-2]
- image_cp.astype(np.int32)[2:-2, 2:-2]
)
)
assert image_aug.shape == (10, 20, 3)
assert avg_dist < 7.0
def test_image_is_view(self):
image = np.mod(np.arange(10*20*3), 255).astype(np.uint8)
image = image.reshape((10, 20, 3))
image_cp = np.copy(image)[2:, 2:, :]
image = image[2:, 2:, :]
assert image.flags["OWNDATA"] is False
aug = iaa.WithPolarWarping(iaa.Noop())
image_aug = aug(image=image)
avg_dist = np.average(
np.abs(
image_aug.astype(np.int32)[2:-2, 2:-2]
- image_cp.astype(np.int32)[2:-2, 2:-2]
)
)
assert image_aug.shape == (8, 18, 3)
assert avg_dist < 7.0
def test_propagation_hooks(self):
image = np.mod(np.arange(30*30), 255).astype(np.uint8)
image = image.reshape((30, 30))
aug = iaa.WithPolarWarping(iaa.Add(50))
def _propagator(images, augmenter, parents, default):
return False if augmenter is aug else default
hooks = ia.HooksImages(propagator=_propagator)
observed1 = aug.augment_image(image)
observed2 = aug.augment_image(image, hooks=hooks)
image_plus50 = np.clip(image.astype(np.int32)+50, 0, 255)
diff1 = np.abs(observed1[2:-2].astype(np.int32)
- image_plus50[2:-2].astype(np.int32))
diff2 = np.abs(observed2[2:-2].astype(np.int32)
- image_plus50[2:-2].astype(np.int32))
overlap_1_add = np.average(diff1 <= 1)
overlap_2_add = np.average(diff2 <= 2)
assert overlap_1_add >= 0.9
assert overlap_2_add < 0.01
def test_unusual_channel_numbers(self):
with assertWarns(self, iaa.SuspiciousSingleImageShapeWarning):
shapes = [
(5, 5, 4),
(5, 5, 5),
(5, 5, 512),
(5, 5, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.WithPolarWarping(iaa.Noop())
image_aug = aug(image=image)
shape_expected = tuple([shape[1], shape[0]] + list(shape[2:]))
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape_expected
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)],
shape=image.shape)
sm_arr = np.zeros((3, 3), dtype=np.int32)
sm_arr[1, 1] = 1
sm = ia.SegmentationMapsOnImage(sm_arr, shape=image.shape)
aug = iaa.WithPolarWarping(iaa.Noop())
aug_det = aug.to_deterministic()
image_aug = aug_det(image=image)
kpsoi_aug = aug_det(keypoints=kpsoi)
sm_aug = aug_det(segmentation_maps=sm)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
assert np.allclose(kpsoi_aug.to_xy_array(),
kpsoi.to_xy_array())
assert kpsoi_aug.shape == shape
assert np.array_equal(sm_aug.get_arr(), sm_arr)
assert sm_aug.shape == shape
def test_other_dtypes_bool(self):
aug = iaa.WithPolarWarping(iaa.Noop())
arr = np.zeros((20, 20), dtype=bool)
arr[10-3:10+3, 10-3:10+3] = True
arr_aug = aug(image=arr)
overlap = np.average(arr_aug == arr)
assert arr_aug.shape == (20, 20)
assert arr_aug.dtype.name == "bool"
assert overlap > 0.95
def test_other_dtypes_uint_int(self):
aug = iaa.WithPolarWarping(iaa.Noop())
dtypes = ["uint8", "uint16",
"int8", "int16", "int32",]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
center_value = int(center_value)
image = np.zeros((30, 10), dtype=dtype)
image[0:10, :] = min_value
image[10:20, :] = center_value
image[20:30, :] = max_value
image = iaa.pad(image, top=2, right=2, bottom=2, left=2,
cval=0)
image_aug = aug.augment_image(image)
image_aug = image_aug[2:-2, 2:-2]
overlap_min = np.average(image_aug[0:10] == min_value)
overlap_cv = np.average(image_aug[10:20] == center_value)
overlap_max = np.average(image_aug[20:30] == max_value)
assert image_aug.dtype.name == dtype
assert overlap_min > 0.9
assert overlap_cv > 0.9
assert overlap_max > 0.9
def test_other_dtypes_float(self):
def _avg_close(arr_aug, expected_val):
atol = 1e-8
return np.average(np.isclose(arr_aug, expected_val,
rtol=0, atol=atol))
aug = iaa.WithPolarWarping(iaa.Noop())
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
center_value = center_value
image = np.zeros((70, 10), dtype=dtype)
image[0:10, :] = min_value
image[10:20, :] = center_value
image[20:30, :] = max_value
image[30:40, :] = -1.0
image[40:50, :] = 1.0
image[50:60, :] = -100.0
image[60:70, :] = 100.0
image = iaa.pad(image, top=2, right=2, bottom=2, left=2,
cval=0)
image_aug = aug.augment_image(image)
image_aug = image_aug[2:-2, 2:-2]
overlap1 = _avg_close(image_aug[0:10], min_value)
overlap2 = _avg_close(image_aug[10:20], center_value)
overlap3 = _avg_close(image_aug[20:30], max_value)
overlap4 = _avg_close(image_aug[30:40], -1.0)
overlap5 = _avg_close(image_aug[40:50], 1.0)
overlap6 = _avg_close(image_aug[50:60], -100.0)
overlap7 = _avg_close(image_aug[60:70], 100.0)
assert image_aug.dtype.name == dtype
assert overlap1 > 0.9
assert overlap2 > 0.9
assert overlap3 > 0.9
assert overlap4 > 0.9
assert overlap5 > 0.9
assert overlap6 > 0.9
assert overlap7 > 0.9
def test_get_parameters(self):
aug = iaa.WithPolarWarping(iaa.Noop())
params = aug.get_parameters()
assert len(params) == 0
def test_get_children_lists(self):
children = iaa.Sequential([iaa.Noop()])
aug = iaa.WithPolarWarping(children)
assert aug.get_children_lists() == [children]
def test_to_deterministic(self):
child = iaa.Identity()
aug = iaa.WithPolarWarping([child])
aug_det = aug.to_deterministic()
assert aug_det.deterministic
assert aug_det.random_state is not aug.random_state
assert aug_det.children.deterministic
assert aug_det.children[0].deterministic
def test___repr___and___str__(self):
children = iaa.Sequential([iaa.Noop()])
aug = iaa.WithPolarWarping(children, name="WithPolarWarpingTest")
expected = (
"WithPolarWarping("
"name=WithPolarWarpingTest, "
"children=%s, "
"deterministic=False"
")" % (str(children),))
assert aug.__repr__() == expected
assert aug.__str__() == expected
def test_pickleable(self):
aug = iaa.WithPolarWarping(
iaa.Affine(translate_px=(0, 10), seed=1),
seed=2)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(25, 25, 1))
class Test_apply_jigsaw(unittest.TestCase):
def test_no_movement(self):
dtypes = [
"bool",
"uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64",
"float16", "float32", "float64"
]
try:
dtypes.append(np.dtype("float128"))
except TypeError:
pass # float128 not known on system
for dtype in dtypes:
with self.subTest(dtype=dtype):
arr = np.arange(20*20*1).reshape((20, 20, 1))
if dtype == "bool":
mask = np.logical_or(
arr % 4 == 0,
arr % 7 == 0)
arr[mask] = 1
arr[~mask] = 0
arr = arr.astype(dtype)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
arr[0, 0] = min_value
arr[0, 1] = max_value
destinations = np.arange(5*5).reshape((5, 5))
observed = iaa.apply_jigsaw(arr, destinations)
if arr.dtype.kind != "f":
assert np.array_equal(observed, arr)
else:
atol = 1e-4 if dtype == "float16" else 1e-8
assert np.allclose(observed, arr, rtol=0, atol=atol)
def test_no_movement_zero_sized_axes(self):
sizes = [
(0, 1),
(1, 0),
(0, 0)
]
dtype = "uint8"
for size in sizes:
with self.subTest(size=size):
arr = np.zeros(size, dtype=dtype)
destinations = np.arange(1*1).reshape((1, 1))
observed = iaa.apply_jigsaw(arr, destinations)
assert np.array_equal(observed, arr)
def _test_two_cells_moved__n_channels(self, nb_channels):
dtypes = [
"bool",
"uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64",
"float16", "float32", "float64"
]
try:
dtypes.append(np.dtype("float128").name)
except TypeError:
pass # float128 not known by user system
for dtype in dtypes:
with self.subTest(dtype=dtype):
c = 1 if nb_channels is None else nb_channels
arr = np.arange(20*20*c)
if dtype == "bool":
mask = np.logical_or(
arr % 4 == 0,
arr % 7 == 0)
arr[mask] = 1
arr[~mask] = 0
if nb_channels is not None:
arr = arr.reshape((20, 20, c))
else:
arr = arr.reshape((20, 20))
arr = arr.astype(dtype)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
arr[0, 0] = min_value
arr[0, 1] = max_value
destinations = np.arange(5*5).reshape((5, 5))
destinations[0, 0] = 4 # cell 0 will be filled with 4
destinations[0, 4] = 0 # cell 4 will be filled with 0
destinations[0, 1] = 6 # cell 1 will be filled with 6
destinations[1, 1] = 1 # cell 6 will be filled with 1
observed = iaa.apply_jigsaw(arr, destinations)
cell_0_obs = observed[0:4, 0:4]
cell_0_exp = arr[0:4, 16:20]
cell_4_obs = observed[0:4, 16:20]
cell_4_exp = arr[0:4, 0:4]
cell_1_obs = observed[0:4, 4:8]
cell_1_exp = arr[4:8, 4:8]
cell_6_obs = observed[4:8, 4:8]
cell_6_exp = arr[0:4, 4:8]
cell_2_obs = observed[0:4, 8:12]
cell_2_exp = arr[0:4, 8:12]
if arr.dtype.kind != "f":
assert np.array_equal(cell_0_obs, cell_0_exp)
assert np.array_equal(cell_4_obs, cell_4_exp)
assert np.array_equal(cell_1_obs, cell_1_exp)
assert np.array_equal(cell_6_obs, cell_6_exp)
assert np.array_equal(cell_2_obs, cell_2_exp)
else:
atol = 1e-4 if dtype == "float16" else 1e-8
kwargs = {"rtol": 0, "atol": atol}
assert np.allclose(cell_0_obs, cell_0_exp, **kwargs)
assert np.allclose(cell_4_obs, cell_4_exp, **kwargs)
assert np.allclose(cell_1_obs, cell_1_exp, **kwargs)
assert np.allclose(cell_6_obs, cell_6_exp, **kwargs)
assert np.allclose(cell_2_obs, cell_2_exp, **kwargs)
assert observed.shape == arr.shape
assert observed.dtype.name == dtype
def test_two_cells_moved__no_channels(self):
self._test_two_cells_moved__n_channels(None)
def test_two_cells_moved__1_channel(self):
self._test_two_cells_moved__n_channels(1)
def test_two_cells_moved__3_channels(self):
self._test_two_cells_moved__n_channels(3)
class Test_apply_jigsaw_to_coords(unittest.TestCase):
def test_no_movement(self):
arr = np.float32([
(0.0, 0.0),
(5.0, 5.0),
(25.0, 50.5),
(10.01, 21.0)
])
destinations = np.arange(10*10).reshape((10, 10))
observed = iaa.apply_jigsaw_to_coords(arr, destinations, (50, 100))
assert np.allclose(observed, arr)
def test_with_movement(self):
arr = np.float32([
(0.0, 0.0), # in cell (0,0) = idx 0
(5.0, 5.0), # in cell (0,0) = idx 0
(25.0, 50.5), # in cell (5,2) = idx 52
(10.01, 21.0) # in cell (2,1) = idx 12
])
destinations = np.arange(10*10).reshape((10, 10))
destinations[0, 0] = 1
destinations[0, 1] = 0
destinations[5, 2] = 7
destinations[0, 7] = 52
observed = iaa.apply_jigsaw_to_coords(arr, destinations, (100, 100))
expected = np.float32([
(10.0, 0.0),
(15.0, 5.0),
(75.0, 0.5),
(10.01, 21.0)
])
assert np.allclose(observed, expected)
def test_with_movement_non_square_image(self):
arr = np.float32([
(0.5, 0.6), # in cell (0,0) = idx 0
(180.7, 90.8), # in cell (9,9) = idx 99
])
destinations = np.arange(10*10).reshape((10, 10))
destinations[0, 0] = 99
destinations[9, 9] = 0
observed = iaa.apply_jigsaw_to_coords(arr, destinations, (100, 200))
expected = np.float32([
(180+0.5, 90+0.6),
(0+0.7, 0+0.8)
])
assert np.allclose(observed, expected)
def test_empty_coords(self):
arr = np.zeros((0, 2), dtype=np.float32)
destinations = np.arange(10*10).reshape((10, 10))
observed = iaa.apply_jigsaw_to_coords(arr, destinations, (100, 100))
assert np.allclose(observed, arr)
class Test_generate_jigsaw_destinations(unittest.TestCase):
def test_max_steps_0(self):
rng = iarandom.RNG(0)
max_steps = 0
rows = 10
cols = 20
observed = iaa.generate_jigsaw_destinations(rows, cols, max_steps, rng,
connectivity=8)
assert np.array_equal(
observed,
np.arange(rows*cols).reshape((rows, cols)))
def test_max_steps_1(self):
rng = iarandom.RNG(0)
max_steps = 1
rows = 10
cols = 20
observed = iaa.generate_jigsaw_destinations(rows, cols, max_steps, rng,
connectivity=8)
yy = (observed // cols).reshape((rows, cols))
xx = np.mod(observed, cols).reshape((rows, cols))
yy_expected = np.tile(np.arange(rows).reshape((rows, 1)), (1, cols))
xx_expected = np.tile(np.arange(cols).reshape((1, cols)), (rows, 1))
yy_diff = yy_expected - yy
xx_diff = xx_expected - xx
dist = np.sqrt(yy_diff ** 2 + xx_diff ** 2)
assert np.min(dist) <= 0.01
assert np.any(dist >= np.sqrt(2) - 1e-4)
assert np.max(dist) <= np.sqrt(2) + 1e-4
def test_max_steps_1_connectivity_4(self):
rng = iarandom.RNG(0)
max_steps = 1
rows = 10
cols = 20
observed = iaa.generate_jigsaw_destinations(rows, cols, max_steps, rng,
connectivity=4)
yy = (observed // cols).reshape((rows, cols))
xx = np.mod(observed, cols).reshape((rows, cols))
yy_expected = np.tile(np.arange(rows).reshape((rows, 1)), (1, cols))
xx_expected = np.tile(np.arange(cols).reshape((1, cols)), (rows, 1))
yy_diff = yy_expected - yy
xx_diff = xx_expected - xx
dist = np.sqrt(yy_diff ** 2 + xx_diff ** 2)
assert np.min(dist) <= 0.01
assert np.any(dist >= 0.99)
assert np.max(dist) <= 1.01
class TestJigsaw(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
aug = iaa.Jigsaw(nb_rows=1, nb_cols=2)
assert aug.nb_rows.value == 1
assert aug.nb_cols.value == 2
assert aug.max_steps.value == 1
assert aug.allow_pad is True
def test___init___custom(self):
aug = iaa.Jigsaw(nb_rows=1, nb_cols=2, max_steps=3, allow_pad=False)
assert aug.nb_rows.value == 1
assert aug.nb_cols.value == 2
assert aug.max_steps.value == 3
assert aug.allow_pad is False
def test__draw_samples(self):
aug = iaa.Jigsaw(nb_rows=(1, 5), nb_cols=(1, 6), max_steps=(1, 3))
batch = mock.Mock()
batch.nb_rows = 100
samples = aug._draw_samples(batch, iarandom.RNG(0))
assert len(np.unique(samples.nb_rows)) > 1
assert len(np.unique(samples.nb_cols)) > 1
assert len(np.unique(samples.max_steps)) > 1
assert np.all(samples.nb_rows >= 1)
assert np.all(samples.nb_rows <= 5)
assert np.all(samples.nb_cols >= 1)
assert np.all(samples.nb_cols <= 6)
assert np.all(samples.max_steps >= 1)
assert np.all(samples.max_steps <= 3)
all_same = True
first = samples.destinations[0]
for dest in samples.destinations:
this_same = (dest.shape == first.shape
and np.array_equal(dest, first))
all_same = all_same and this_same
assert not all_same
def test_images_without_shifts(self):
aug = iaa.Jigsaw(nb_rows=2, nb_cols=2, max_steps=0)
image = np.mod(np.arange(20*20*3), 255).astype(np.uint8)
image = image.reshape((20, 20, 3))
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (20, 20, 3)
assert np.array_equal(image_aug, image)
def test_heatmaps_without_shifts(self):
aug = iaa.Jigsaw(nb_rows=2, nb_cols=2, max_steps=0)
arr = np.linspace(0, 1.0, 20*20*1).astype(np.float32)
arr = arr.reshape((20, 20, 1))
heatmap = ia.HeatmapsOnImage(arr, shape=(20, 20, 3))
heatmap_aug = aug(heatmaps=heatmap)
assert heatmap_aug.shape == (20, 20, 3)
assert np.allclose(heatmap_aug.arr_0to1, heatmap.arr_0to1)
def test_segmaps_without_shifts(self):
aug = iaa.Jigsaw(nb_rows=2, nb_cols=2, max_steps=0)
arr = np.zeros((20, 20, 1), dtype=np.int32)
arr[0:10, :] = 1
arr[10:20, 10:20] = 2
arr = arr.reshape((20, 20, 1))
segmap = ia.SegmentationMapsOnImage(arr, shape=(20, 20, 3))
segmap_aug = aug(segmentation_maps=segmap)
assert segmap_aug.shape == (20, 20, 3)
assert np.array_equal(segmap_aug.arr, segmap.arr)
def test_keypoints_without_shifts(self):
aug = iaa.Jigsaw(nb_rows=2, nb_cols=2, max_steps=0)
kpsoi = ia.KeypointsOnImage.from_xy_array([
(0, 0),
(5.5, 3.5),
(12.1, 23.5)
], shape=(20, 20, 3))
kpsoi_aug = aug(keypoints=kpsoi)
assert kpsoi_aug.shape == (20, 20, 3)
assert np.allclose(kpsoi_aug.to_xy_array(), kpsoi.to_xy_array())
def test_images_with_shifts(self):
# these rows/cols/max_steps parameters are mostly ignored due to the
# mocked _draw_samples method below
aug = iaa.Jigsaw(nb_rows=2, nb_cols=2, max_steps=1)
image = np.mod(np.arange(19*19*3), 255).astype(np.uint8)
image = image.reshape((19, 19, 3))
destinations = np.array([
[3, 1],
[2, 0]
], dtype=np.int32)
old_func = aug._draw_samples
def _mocked_draw_samples(batch, random_state):
samples = old_func(batch, random_state)
return geometriclib._JigsawSamples(
nb_rows=samples.nb_rows,
nb_cols=samples.nb_cols,
max_steps=samples.max_steps,
destinations=[destinations])
aug._draw_samples = _mocked_draw_samples
image_aug = aug(image=image)
expected = iaa.pad(image, bottom=1, right=1, cval=0)
expected = iaa.apply_jigsaw(expected, destinations)
assert np.array_equal(image_aug, expected)
def test_heatmaps_with_shifts(self):
# these rows/cols/max_steps parameters are mostly ignored due to the
# mocked _draw_samples method below
aug = iaa.Jigsaw(nb_rows=2, nb_cols=2, max_steps=1)
arr = np.linspace(0, 1.0, 18*18*1).astype(np.float32)
arr = arr.reshape((18, 18, 1))
heatmap = ia.HeatmapsOnImage(arr, shape=(19, 19, 3))
destinations = np.array([
[3, 1],
[2, 0]
], dtype=np.int32)
old_func = aug._draw_samples
def _mocked_draw_samples(batch, random_state):
samples = old_func(batch, random_state)
return geometriclib._JigsawSamples(
nb_rows=samples.nb_rows,
nb_cols=samples.nb_cols,
max_steps=samples.max_steps,
destinations=[destinations])
aug._draw_samples = _mocked_draw_samples
heatmap_aug = aug(heatmaps=heatmap)
expected = ia.imresize_single_image(arr, (19, 19),
interpolation="cubic")
expected = np.clip(expected, 0, 1.0)
expected = iaa.pad(expected, bottom=1, right=1, cval=0.0)
expected = iaa.apply_jigsaw(expected, destinations)
expected = ia.imresize_single_image(expected, (18, 18),
interpolation="cubic")
expected = np.clip(expected, 0, 1.0)
assert np.allclose(heatmap_aug.arr_0to1, expected)
def test_segmaps_with_shifts(self):
# these rows/cols/max_steps parameters are mostly ignored due to the
# mocked _draw_samples method below
aug = iaa.Jigsaw(nb_rows=2, nb_cols=2, max_steps=1)
arr = np.zeros((18, 18, 1), dtype=np.int32)
arr[0:10, :] = 1
arr[10:18, 10:18] = 2
arr = arr.reshape((18, 18, 1))
segmap = ia.SegmentationMapsOnImage(arr, shape=(19, 19, 3))
destinations = np.array([
[3, 1],
[2, 0]
], dtype=np.int32)
old_func = aug._draw_samples
def _mocked_draw_samples(batch, random_state):
samples = old_func(batch, random_state)
return geometriclib._JigsawSamples(
nb_rows=samples.nb_rows,
nb_cols=samples.nb_cols,
max_steps=samples.max_steps,
destinations=[destinations])
aug._draw_samples = _mocked_draw_samples
segmap_aug = aug(segmentation_maps=segmap)
expected = ia.imresize_single_image(arr, (19, 19),
interpolation="nearest")
expected = iaa.pad(expected, bottom=1, right=1, cval=0)
expected = iaa.apply_jigsaw(expected, destinations)
expected = ia.imresize_single_image(expected, (18, 18),
interpolation="nearest")
assert np.array_equal(segmap_aug.arr, expected)
def test_keypoints_with_shifts(self):
# these rows/cols/max_steps parameters are mostly ignored due to the
# mocked _draw_samples method below
aug = iaa.Jigsaw(nb_rows=5, nb_cols=5, max_steps=1)
kpsoi = ia.KeypointsOnImage.from_xy_array([
(0, 0),
(5.5, 3.5),
(4.0, 12.5),
(11.1, 11.2),
(12.1, 23.5)
], shape=(18, 18, 3))
destinations = np.array([
[3, 1],
[2, 0]
], dtype=np.int32)
old_func = aug._draw_samples
def _mocked_draw_samples(batch, random_state):
samples = old_func(batch, random_state)
return geometriclib._JigsawSamples(
nb_rows=samples.nb_rows,
nb_cols=samples.nb_cols,
max_steps=samples.max_steps,
destinations=[destinations])
aug._draw_samples = _mocked_draw_samples
kpsoi_aug = aug(keypoints=kpsoi)
expected = kpsoi.deepcopy()
expected.shape = (20, 20, 3)
# (0.0, 0.0) to cell at bottom-right, 1px pad at top and left
expected.keypoints[0].x = 10.0 + (0.0 - 0.0) + 1.0
expected.keypoints[0].y = 10.0 + (0.0 - 0.0) + 1.0
# (5.5, 3.5) to cell at bottom-right, 1px pad at top and left
expected.keypoints[1].x = 10.0 + (5.5 - 0.0) + 1.0
expected.keypoints[1].y = 10.0 + (3.5 - 0.0) + 1.0
# (4.0, 12.5) not moved to other cell, but 1px pad at top and left
expected.keypoints[2].x = 4.0 + 1.0
expected.keypoints[2].y = 12.5 + 1.0
# (11.0, 11.0) to cell at top-left, 1px pad at top and left
expected.keypoints[3].x = 0.0 + (11.1 - 10.0) + 1.0
expected.keypoints[3].y = 0.0 + (11.2 - 10.0) + 1.0
# (12.1, 23.5) not moved to other cell, but 1px pad at top and left
expected.keypoints[4].x = 12.1 + 1.0
expected.keypoints[4].y = 23.5 + 1.0
expected.shape = (20, 20, 3)
assert kpsoi_aug.shape == (20, 20, 3)
assert np.allclose(kpsoi_aug.to_xy_array(), expected.to_xy_array())
def test_images_and_heatmaps_aligned(self):
nb_changed = 0
rs = iarandom.RNG(0)
for _ in np.arange(10):
aug = iaa.Jigsaw(nb_rows=(2, 5), nb_cols=(2, 5), max_steps=(0, 3))
image_small = rs.integers(0, 10, size=(10, 15)).astype(np.float32)
image_small = image_small / 10.0
image = ia.imresize_single_image(image_small, (20, 30),
interpolation="cubic")
image = np.clip(image, 0, 1.0)
hm = ia.HeatmapsOnImage(image_small, shape=(20, 30))
images_aug, hms_aug = aug(images=[image, image, image],
heatmaps=[hm, hm, hm])
for image_aug, hm_aug in zip(images_aug, hms_aug):
# TODO added squeeze here because get_arr() falsely returns
# (H,W,1) for 2D inputs
arr = np.squeeze(hm_aug.get_arr())
image_aug_rs = ia.imresize_single_image(
image_aug.astype(np.float32),
arr.shape[0:2],
interpolation="cubic")
image_aug_rs = np.clip(image_aug_rs, 0, 1.0)
overlap = np.average(np.isclose(image_aug_rs, arr))
assert overlap > 0.99
if not np.array_equal(arr, hm.get_arr()):
nb_changed += 1
assert nb_changed > 5
def test_images_and_segmaps_aligned(self):
nb_changed = 0
rs = iarandom.RNG(0)
for _ in np.arange(10):
aug = iaa.Jigsaw(nb_rows=(2, 5), nb_cols=(2, 5), max_steps=(0, 3))
image_small = rs.integers(0, 10, size=(10, 15))
image = ia.imresize_single_image(image_small, (20, 30),
interpolation="nearest")
image = image.astype(np.uint8)
segm = ia.SegmentationMapsOnImage(image_small, shape=(20, 30))
images_aug, sms_aug = aug(images=[image, image, image],
segmentation_maps=[segm, segm, segm])
for image_aug, sm_aug in zip(images_aug, sms_aug):
arr = sm_aug.get_arr()
image_aug_rs = ia.imresize_single_image(
image_aug, arr.shape[0:2], interpolation="nearest")
overlap = np.average(image_aug_rs == arr)
assert overlap > 0.99
if not np.array_equal(arr, segm.arr):
nb_changed += 1
assert nb_changed > 5
def test_images_and_keypoints_aligned(self):
for i in np.arange(20):
aug = iaa.Jigsaw(nb_rows=(1, 3), nb_cols=(1, 3), max_steps=(2, 5),
seed=i)
# make sure that these coords are not exactly at a grid cell
# border with any possibly sampled height/width in grid cells
y = 17.5
x = 25.5
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=x, y=y)],
shape=(20, 30))
image = np.zeros((20, 30), dtype=np.uint8)
image[int(y), int(x)] = 255
images_aug, kpsois_aug = aug(images=[image, image, image],
keypoints=[kpsoi, kpsoi, kpsoi])
for image_aug, kpsoi_aug in zip(images_aug, kpsois_aug):
x_aug = kpsoi_aug.keypoints[0].x
y_aug = kpsoi_aug.keypoints[0].y
idx = np.argmax(image_aug)
y_aug_img, x_aug_img = np.unravel_index(idx,
image_aug.shape)
dist = np.sqrt((x_aug - x_aug_img)**2 + (y_aug - y_aug_img)**2)
# best possible distance is about 0.7 as KP coords are in cell
# center and sampled coords are at cell top left
assert dist < 0.8
def test_no_error_for_1x1_grids(self):
aug = iaa.Jigsaw(nb_rows=1, nb_cols=1, max_steps=2)
image = np.mod(np.arange(19*19*3), 255).astype(np.uint8)
image = image.reshape((19, 19, 3))
kpsoi = ia.KeypointsOnImage.from_xy_array([
(0, 0),
(5.5, 3.5),
(4.0, 12.5),
(11.1, 11.2),
(12.1, 23.5)
], shape=(19, 19, 3))
image_aug, kpsoi_aug = aug(image=image, keypoints=kpsoi)
assert np.array_equal(image_aug, image)
assert np.allclose(kpsoi_aug.to_xy_array(), kpsoi.to_xy_array())
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
for _ in sm.xrange(3):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Jigsaw(nb_rows=2, nb_cols=2, max_steps=2)
image_aug = aug(image=image)
# (2, 2, [C]) here, because rows/cols are padded to be
# multiple of nb_rows and nb_cols
shape_exp = tuple([2, 2] + list(shape[2:]))
assert image_aug.dtype.name == "uint8"
assert np.array_equal(image_aug,
np.zeros(shape_exp, dtype=np.uint8))
def test_get_parameters(self):
aug = iaa.Jigsaw(nb_rows=1, nb_cols=2)
params = aug.get_parameters()
assert params[0] is aug.nb_rows
assert params[1] is aug.nb_cols
assert params[2] is aug.max_steps
assert params[3] is True
def test_pickleable(self):
aug = iaa.Jigsaw(nb_rows=(1, 4), nb_cols=(1, 4), max_steps=(1, 3))
runtest_pickleable_uint8_img(aug, iterations=20, shape=(32, 32, 3))
================================================
FILE: test/augmenters/test_imgcorruptlike.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import functools
import numpy as np
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import random as iarandom
from imgaug import parameters as iap
from imgaug.testutils import runtest_pickleable_uint8_img
# imagecorruptions cannot be installed in <=3.4 due to their
# scikit-image requirement
SUPPORTS_LIBRARY = (sys.version_info[0] == 3 and sys.version_info[1] >= 5)
if SUPPORTS_LIBRARY:
import imagecorruptions
from imagecorruptions import corrupt
class Test_get_imgcorrupt_subset(unittest.TestCase):
@unittest.skipUnless(SUPPORTS_LIBRARY,
"imagecorruptions can only be tested for python 3.5+")
def test_by_comparison_with_imagecorruptions(self):
subset_names = ["common", "validation", "all"]
for subset in subset_names:
with self.subTest(subset=subset):
func_names, funcs = iaa.imgcorruptlike.get_corruption_names(
subset)
func_names_exp = imagecorruptions.get_corruption_names(subset)
assert func_names == func_names_exp
for func_name, func in zip(func_names, funcs):
assert getattr(
iaa.imgcorruptlike, "apply_%s" % (func_name,)
) is func
@unittest.skipUnless(SUPPORTS_LIBRARY,
"imagecorruptions can only be tested for python 3.5+")
def test_subset_functions(self):
subset_names = ["common", "validation", "all"]
for subset in subset_names:
func_names, funcs = iaa.imgcorruptlike.get_corruption_names(subset)
image = np.mod(
np.arange(32*32*3), 256
).reshape((32, 32, 3)).astype(np.uint8)
for func_name, func in zip(func_names, funcs):
with self.subTest(subset=subset, name=func_name):
# don't verify here whether e.g. only seed 2 produces
# different results from seed 1, because some methods
# are only dependent on the severity
image_aug1 = func(image, severity=5, seed=1)
image_aug2 = func(image, severity=5, seed=1)
image_aug3 = func(image, severity=1, seed=2)
assert not np.array_equal(image, image_aug1)
assert not np.array_equal(image, image_aug2)
assert not np.array_equal(image_aug2, image_aug3)
assert np.array_equal(image_aug1, image_aug2)
class _CompareFuncWithImageCorruptions(unittest.TestCase):
def _test_by_comparison_with_imagecorruptions(
self,
fname,
shapes=((64, 64), (64, 64, 1), (64, 64, 3)),
dtypes=("uint8",),
severities=(1, 2, 3, 4, 5),
seeds=(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)):
for shape in shapes:
for dtype in dtypes:
for severity in severities:
for seed in seeds:
with self.subTest(shape=shape, severity=severity,
seed=seed):
image_imgaug = self.create_image_imgaug(
shape, dtype, 1000 + seed)
image_imgcor = np.copy(image_imgaug)
self._run_single_comparison_test(
fname, image_imgaug, image_imgcor, severity,
seed)
@classmethod
def create_image_imgaug(cls, shape, dtype, seed, tile=None):
rng = iarandom.RNG(1000 + seed)
if dtype.startswith("uint"):
image = rng.integers(0, 256, size=shape, dtype=dtype)
else:
assert dtype.startswith("float")
image = rng.uniform(0.0, 1.0, size=shape)
image = image.astype(dtype)
if tile is not None:
image = np.tile(image, tile)
return image
@classmethod
def _run_single_comparison_test(cls, fname, image_imgaug, image_imgcor,
severity, seed):
image_imgaug_sum = np.sum(image_imgaug)
image_imgcor_sum = np.sum(image_imgcor)
image_aug, image_aug_exp = cls._generate_augmented_images(
fname, image_imgaug, image_imgcor, severity, seed)
# assert that the original image is unchanged,
# i.e. it was not augmented in-place
assert np.isclose(np.sum(image_imgcor), image_imgcor_sum, rtol=0,
atol=1e-4)
assert np.isclose(np.sum(image_imgaug), image_imgaug_sum, rtol=0,
atol=1e-4)
# assert that the functions returned numpy arrays and not PIL images
assert ia.is_np_array(image_aug_exp)
assert ia.is_np_array(image_aug)
assert image_aug.shape == image_imgaug.shape
assert image_aug.dtype.name == image_aug_exp.dtype.name
atol = 1e-4 # set this to 0.5+1e-4 if output is converted to uint8
assert np.allclose(image_aug, image_aug_exp, rtol=0, atol=atol)
@classmethod
def _generate_augmented_images(cls, fname, image_imgaug, image_imgcor,
severity, seed):
func_imgaug = getattr(
iaa.imgcorruptlike,
"apply_%s" % (fname,))
func_imagecor = functools.partial(corrupt, corruption_name=fname)
with iarandom.temporary_numpy_seed(seed):
image_aug_exp = func_imagecor(image_imgcor, severity=severity)
if not ia.is_np_array(image_aug_exp):
image_aug_exp = np.asarray(image_aug_exp)
if image_imgcor.ndim == 2:
image_aug_exp = image_aug_exp[:, :, 0]
elif image_imgcor.shape[-1] == 1:
image_aug_exp = image_aug_exp[:, :, 0:1]
image_aug = func_imgaug(image_imgaug, severity=severity,
seed=seed)
return image_aug, image_aug_exp
@unittest.skipUnless(SUPPORTS_LIBRARY,
"imagecorruptions can only be tested for python 3.5+")
class Test_apply_functions(_CompareFuncWithImageCorruptions):
def test_apply_gaussian_noise(self):
self._test_by_comparison_with_imagecorruptions("gaussian_noise")
def test_apply_shot_noise(self):
self._test_by_comparison_with_imagecorruptions("shot_noise")
def test_apply_impulse_noise(self):
self._test_by_comparison_with_imagecorruptions("impulse_noise")
def test_apply_speckle_noise(self):
self._test_by_comparison_with_imagecorruptions("speckle_noise")
def test_apply_gaussian_blur(self):
self._test_by_comparison_with_imagecorruptions("gaussian_blur")
def test_apply_glass_blur(self):
# glass_blur() is extremely slow, so we run only a reduced set
# of tests here
self._test_by_comparison_with_imagecorruptions(
"glass_blur",
shapes=[(32, 32), (32, 32, 1), (32, 32, 3)],
severities=[1, 4],
seeds=[1, 2, 3])
def test_apply_defocus_blur(self):
self._test_by_comparison_with_imagecorruptions(
"defocus_blur")
def test_apply_motion_blur(self):
self._test_by_comparison_with_imagecorruptions(
"motion_blur")
def test_apply_zoom_blur(self):
self._test_by_comparison_with_imagecorruptions(
"zoom_blur")
def test_apply_fog(self):
self._test_by_comparison_with_imagecorruptions(
"fog")
def test_apply_frost(self):
self._test_by_comparison_with_imagecorruptions(
"frost",
severities=[1, 5],
seeds=[1, 5, 10])
def test_apply_snow(self):
self._test_by_comparison_with_imagecorruptions(
"snow")
def test_apply_spatter(self):
self._test_by_comparison_with_imagecorruptions(
"spatter")
def test_apply_contrast(self):
self._test_by_comparison_with_imagecorruptions("contrast")
def test_apply_brightness(self):
self._test_by_comparison_with_imagecorruptions("brightness")
def test_apply_saturate(self):
self._test_by_comparison_with_imagecorruptions(
"saturate")
def test_apply_jpeg_compression(self):
self._test_by_comparison_with_imagecorruptions(
"jpeg_compression")
def test_apply_pixelate(self):
self._test_by_comparison_with_imagecorruptions(
"pixelate")
def test_apply_elastic_transform(self):
self._test_by_comparison_with_imagecorruptions(
"elastic_transform")
@unittest.skipUnless(SUPPORTS_LIBRARY,
"imagecorruptions can only be tested for python 3.5+")
class TestAugmenters(unittest.TestCase):
@classmethod
def _test_augmenter(cls, augmenter_name, func_expected,
dependent_on_seed):
# this test verifies:
# - called function seems to be the expected function
# - images produced by augmenter match images produced by function
# - a different seed (and sometimes severity) will lead to a
# different image
# - augmenter can be pickled
severity = 5
aug_cls = getattr(iaa.imgcorruptlike, augmenter_name)
image = np.mod(
np.arange(32*32*3), 256
).reshape((32, 32, 3)).astype(np.uint8)
with iap.no_prefetching():
rng = iarandom.RNG(1)
# Replay sampling of severities.
# Even for deterministic values this is required as currently
# there is an advance() at the end of each draw_samples().
_ = iap.Deterministic(1).draw_samples((1,), rng)
# As for the functions above, we can't just change the seed value
# to get different augmentations as many functions are dependend
# only on the severity. So we change only for some functions only
# the seed and for the others severity+seed.
image_aug1 = aug_cls(severity=severity, seed=1)(image=image)
image_aug2 = aug_cls(severity=severity, seed=1)(image=image)
if dependent_on_seed:
image_aug3 = aug_cls(severity=severity, seed=2)(
image=image)
else:
image_aug3 = aug_cls(severity=severity-1, seed=2)(
image=image)
image_aug_exp = func_expected(
image,
severity=severity,
seed=rng.generate_seed_())
assert aug_cls(severity=severity).func is func_expected
assert np.array_equal(image_aug1, image_aug_exp)
assert np.array_equal(image_aug2, image_aug_exp)
assert not np.array_equal(image_aug3, image_aug2)
# pickling test
aug = aug_cls(severity=(1, 5))
runtest_pickleable_uint8_img(aug, shape=(32, 32, 3))
def test_gaussian_noise(self):
self._test_augmenter("GaussianNoise",
iaa.imgcorruptlike.apply_gaussian_noise,
True)
def test_shot_noise(self):
self._test_augmenter("ShotNoise",
iaa.imgcorruptlike.apply_shot_noise,
True)
def test_impulse_noise(self):
self._test_augmenter("ImpulseNoise",
iaa.imgcorruptlike.apply_impulse_noise,
True)
def test_speckle_noise(self):
self._test_augmenter("SpeckleNoise",
iaa.imgcorruptlike.apply_speckle_noise,
True)
def test_gaussian_blur(self):
self._test_augmenter("GaussianBlur",
iaa.imgcorruptlike.apply_gaussian_blur,
False)
def test_glass_blur(self):
self._test_augmenter("GlassBlur",
iaa.imgcorruptlike.apply_glass_blur,
False)
def test_defocus_blur(self):
self._test_augmenter("DefocusBlur",
iaa.imgcorruptlike.apply_defocus_blur,
False)
def test_motion_blur(self):
self._test_augmenter("MotionBlur",
iaa.imgcorruptlike.apply_motion_blur,
False)
def test_zoom_blur(self):
self._test_augmenter("ZoomBlur",
iaa.imgcorruptlike.apply_zoom_blur,
False)
def test_fog(self):
self._test_augmenter("Fog",
iaa.imgcorruptlike.apply_fog,
True)
def test_frost(self):
self._test_augmenter("Frost",
iaa.imgcorruptlike.apply_frost,
False)
def test_snow(self):
self._test_augmenter("Snow",
iaa.imgcorruptlike.apply_snow,
True)
def test_spatter(self):
self._test_augmenter("Spatter",
iaa.imgcorruptlike.apply_spatter,
True)
def test_contrast(self):
self._test_augmenter("Contrast",
iaa.imgcorruptlike.apply_contrast,
False)
def test_brightness(self):
self._test_augmenter("Brightness",
iaa.imgcorruptlike.apply_brightness,
False)
def test_saturate(self):
self._test_augmenter("Saturate",
iaa.imgcorruptlike.apply_saturate,
False)
def test_jpeg_compression(self):
self._test_augmenter("JpegCompression",
iaa.imgcorruptlike.apply_jpeg_compression,
False)
def test_pixelate(self):
self._test_augmenter("Pixelate",
iaa.imgcorruptlike.apply_pixelate,
False)
def test_elastic_transform(self):
self._test_augmenter("ElasticTransform",
iaa.imgcorruptlike.apply_elastic_transform,
True)
================================================
FILE: test/augmenters/test_meta.py
================================================
from __future__ import print_function, division, absolute_import
import os
import warnings
import sys
import itertools
import copy
from abc import ABCMeta, abstractmethod
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
try:
import cPickle as pickle
except ImportError:
import pickle
import numpy as np
import six
import six.moves as sm
import cv2
import PIL.Image
import imageio
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug import random as iarandom
from imgaug.testutils import (create_random_images, create_random_keypoints,
array_equal_lists, keypoints_equal, reseed,
assert_cbaois_equal,
runtest_pickleable_uint8_img,
TemporaryDirectory, is_parameter_instance)
from imgaug.augmentables.heatmaps import HeatmapsOnImage
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
from imgaug.augmentables.lines import LineString, LineStringsOnImage
from imgaug.augmentables.polys import _ConcavePolygonRecoverer
from imgaug.augmentables.batches import _BatchInAugmentation
IS_PY36_OR_HIGHER = (sys.version_info[0] == 3 and sys.version_info[1] >= 6)
class _InplaceDummyAugmenterImgsArray(iaa.meta.Augmenter):
def __init__(self, addval):
super(_InplaceDummyAugmenterImgsArray, self).__init__()
self.addval = addval
def _augment_batch_(self, batch, random_state, parents, hooks):
batch.images += self.addval
return batch
def get_parameters(self):
return []
class _InplaceDummyAugmenterImgsList(iaa.meta.Augmenter):
def __init__(self, addval):
super(_InplaceDummyAugmenterImgsList, self).__init__()
self.addval = addval
def _augment_batch_(self, batch, random_state, parents, hooks):
assert len(batch.images) > 0
for i in range(len(batch.images)):
batch.images[i] += self.addval
return batch
def get_parameters(self):
return []
class _InplaceDummyAugmenterSegMaps(iaa.meta.Augmenter):
def __init__(self, addval):
super(_InplaceDummyAugmenterSegMaps, self).__init__()
self.addval = addval
def _augment_batch_(self, batch, random_state, parents, hooks):
assert len(batch.segmentation_maps) > 0
for i in range(len(batch.segmentation_maps)):
batch.segmentation_maps[i].arr += self.addval
return batch
def get_parameters(self):
return []
class _InplaceDummyAugmenterKeypoints(iaa.meta.Augmenter):
def __init__(self, x, y):
super(_InplaceDummyAugmenterKeypoints, self).__init__()
self.x = x
self.y = y
def _augment_batch_(self, batch, random_state, parents, hooks):
assert len(batch.keypoints) > 0
for i in range(len(batch.keypoints)):
kpsoi = batch.keypoints[i]
for j in range(len(kpsoi)):
batch.keypoints[i].keypoints[j].x += self.x
batch.keypoints[i].keypoints[j].y += self.y
return batch
def get_parameters(self):
return []
class TestIdentity(unittest.TestCase):
def setUp(self):
reseed()
def test_images(self):
aug = iaa.Identity()
images = create_random_images((16, 70, 50, 3))
observed = aug.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
def test_images_deterministic(self):
aug_det = iaa.Identity().to_deterministic()
images = create_random_images((16, 70, 50, 3))
observed = aug_det.augment_images(images)
expected = images
assert np.array_equal(observed, expected)
def test_heatmaps(self):
aug = iaa.Identity()
heatmaps_arr = np.linspace(0.0, 1.0, 2*2, dtype="float32")\
.reshape((2, 2, 1))
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(2, 2, 3))
observed = aug.augment_heatmaps(heatmaps)
assert np.allclose(observed.arr_0to1, heatmaps.arr_0to1)
def test_heatmaps_deterministic(self):
aug_det = iaa.Identity().to_deterministic()
heatmaps_arr = np.linspace(0.0, 1.0, 2*2, dtype="float32")\
.reshape((2, 2, 1))
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(2, 2, 3))
observed = aug_det.augment_heatmaps(heatmaps)
assert np.allclose(observed.arr_0to1, heatmaps.arr_0to1)
def test_segmentation_maps(self):
aug = iaa.Identity()
segmaps_arr = np.arange(2*2).reshape((2, 2, 1)).astype(np.int32)
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(2, 2, 3))
observed = aug.augment_segmentation_maps(segmaps)
assert np.array_equal(observed.arr, segmaps.arr)
def test_segmentation_maps_deterministic(self):
aug_det = iaa.Identity().to_deterministic()
segmaps_arr = np.arange(2*2).reshape((2, 2, 1)).astype(np.int32)
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(2, 2, 3))
observed = aug_det.augment_segmentation_maps(segmaps)
assert np.array_equal(observed.arr, segmaps.arr)
def test_keypoints(self):
aug = iaa.Identity()
keypoints = create_random_keypoints((16, 70, 50, 3), 4)
observed = aug.augment_keypoints(keypoints)
assert_cbaois_equal(observed, keypoints)
def test_keypoints_deterministic(self):
aug_det = iaa.Identity().to_deterministic()
keypoints = create_random_keypoints((16, 70, 50, 3), 4)
observed = aug_det.augment_keypoints(keypoints)
assert_cbaois_equal(observed, keypoints)
def test_polygons(self):
aug = iaa.Identity()
polygon = ia.Polygon([(10, 10), (30, 10), (30, 50), (10, 50)])
psoi = ia.PolygonsOnImage([polygon], shape=(100, 75, 3))
observed = aug.augment_polygons(psoi)
assert_cbaois_equal(observed, psoi)
def test_polygons_deterministic(self):
aug_det = iaa.Identity().to_deterministic()
polygon = ia.Polygon([(10, 10), (30, 10), (30, 50), (10, 50)])
psoi = ia.PolygonsOnImage([polygon], shape=(100, 75, 3))
observed = aug_det.augment_polygons(psoi)
assert_cbaois_equal(observed, psoi)
def test_line_strings(self):
aug = iaa.Identity()
ls = LineString([(10, 10), (30, 10), (30, 50), (10, 50)])
lsoi = LineStringsOnImage([ls], shape=(100, 75, 3))
observed = aug.augment_line_strings(lsoi)
assert_cbaois_equal(observed, lsoi)
def test_line_strings_deterministic(self):
aug_det = iaa.Identity().to_deterministic()
ls = LineString([(10, 10), (30, 10), (30, 50), (10, 50)])
lsoi = LineStringsOnImage([ls], shape=(100, 75, 3))
observed = aug_det.augment_line_strings(lsoi)
assert_cbaois_equal(observed, lsoi)
def test_bounding_boxes(self):
aug = iaa.Identity()
bbs = ia.BoundingBox(x1=10, y1=10, x2=30, y2=50)
bbsoi = ia.BoundingBoxesOnImage([bbs], shape=(100, 75, 3))
observed = aug.augment_bounding_boxes(bbsoi)
assert_cbaois_equal(observed, bbsoi)
def test_bounding_boxes_deterministic(self):
aug_det = iaa.Identity().to_deterministic()
bbs = ia.BoundingBox(x1=10, y1=10, x2=30, y2=50)
bbsoi = ia.BoundingBoxesOnImage([bbs], shape=(100, 75, 3))
observed = aug_det.augment_bounding_boxes(bbsoi)
assert_cbaois_equal(observed, bbsoi)
def test_keypoints_empty(self):
aug = iaa.Identity()
kpsoi = ia.KeypointsOnImage([], shape=(4, 5, 3))
observed = aug.augment_keypoints(kpsoi)
assert_cbaois_equal(observed, kpsoi)
def test_polygons_empty(self):
aug = iaa.Identity()
psoi = ia.PolygonsOnImage([], shape=(4, 5, 3))
observed = aug.augment_polygons(psoi)
assert_cbaois_equal(observed, psoi)
def test_line_strings_empty(self):
aug = iaa.Identity()
lsoi = ia.LineStringsOnImage([], shape=(4, 5, 3))
observed = aug.augment_line_strings(lsoi)
assert_cbaois_equal(observed, lsoi)
def test_bounding_boxes_empty(self):
aug = iaa.Identity()
bbsoi = ia.BoundingBoxesOnImage([], shape=(4, 5, 3))
observed = aug.augment_bounding_boxes(bbsoi)
assert_cbaois_equal(observed, bbsoi)
def test_get_parameters(self):
assert iaa.Identity().get_parameters() == []
def test_other_dtypes_bool(self):
aug = iaa.Identity()
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == image.dtype.type
assert np.all(image_aug == image)
def test_other_dtypes_uint_int(self):
aug = iaa.Identity()
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, image)
def test_other_dtypes_float(self):
aug = iaa.Identity()
try:
f128 = [np.dtype("float128").name]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == image)
def test_pickleable(self):
aug = iaa.Noop()
runtest_pickleable_uint8_img(aug, iterations=2)
class TestNoop(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.Noop()
assert isinstance(aug, iaa.Identity)
def test_images(self):
image = np.mod(np.arange(10*10*3), 255)
image = image.astype(np.uint8).reshape((10, 10, 3))
image_aug = iaa.Noop()(image=image)
assert np.array_equal(image, image_aug)
# TODO add tests for line strings
class TestLambda(unittest.TestCase):
def setUp(self):
reseed()
@property
def base_img(self):
base_img = np.array([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]], dtype=np.uint8)
base_img = base_img[:, :, np.newaxis]
return base_img
@property
def heatmaps(self):
heatmaps_arr = np.float32([[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0]])
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
return heatmaps
@property
def heatmaps_aug(self):
heatmaps_arr_aug = np.float32([[0.5, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0]])
heatmaps = ia.HeatmapsOnImage(heatmaps_arr_aug, shape=(3, 3, 3))
return heatmaps
@property
def segmentation_maps(self):
segmaps_arr = np.int32([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]])
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
return segmaps
@property
def segmentation_maps_aug(self):
segmaps_arr_aug = np.int32([[1, 1, 2],
[1, 1, 2],
[1, 2, 2]])
segmaps = SegmentationMapsOnImage(segmaps_arr_aug, shape=(3, 3, 3))
return segmaps
@property
def keypoints(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
kpsoi = [ia.KeypointsOnImage(kps, shape=(3, 3, 3))]
return kpsoi
@property
def keypoints_aug(self):
expected_kps = [ia.Keypoint(x=1, y=0), ia.Keypoint(x=2, y=1),
ia.Keypoint(x=0, y=2)]
expected = [ia.KeypointsOnImage(expected_kps, shape=(3, 3, 3))]
return expected
@property
def polygons(self):
poly = ia.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
psois = [ia.PolygonsOnImage([poly], shape=(3, 3, 3))]
return psois
@property
def polygons_aug(self):
expected_poly = ia.Polygon([(1, 2), (3, 2), (3, 4), (1, 4)])
expected_psoi = [ia.PolygonsOnImage([expected_poly], shape=(3, 3, 3))]
return expected_psoi
@property
def lsoi(self):
ls = ia.LineString([(0, 0), (2, 0), (2, 2), (0, 2)])
lsois = [ia.LineStringsOnImage([ls], shape=(3, 3, 3))]
return lsois
@property
def lsoi_aug(self):
ls = ia.LineString([(1, 2), (3, 2), (3, 4), (1, 4)])
lsois = [ia.LineStringsOnImage([ls], shape=(3, 3, 3))]
return lsois
@property
def bbsoi(self):
bb = ia.BoundingBox(x1=0, y1=1, x2=3, y2=4)
bbsois = [ia.BoundingBoxesOnImage([bb], shape=(3, 3, 3))]
return bbsois
@property
def bbsoi_aug(self):
bb = ia.BoundingBox(x1=0+1, y1=1+2, x2=3+1, y2=4+2)
bbsois = [ia.BoundingBoxesOnImage([bb], shape=(3, 3, 3))]
return bbsois
@classmethod
def func_images(cls, images, random_state, parents, hooks):
if isinstance(images, list):
images = [image + 1 for image in images]
else:
images = images + 1
return images
@classmethod
def func_heatmaps(cls, heatmaps, random_state, parents, hooks):
heatmaps[0].arr_0to1[0, 0] += 0.5
return heatmaps
@classmethod
def func_segmaps(cls, segmaps, random_state, parents, hooks):
segmaps[0].arr += 1
return segmaps
@classmethod
def func_keypoints(cls, keypoints_on_images, random_state, parents, hooks):
for keypoints_on_image in keypoints_on_images:
for kp in keypoints_on_image.keypoints:
kp.x = (kp.x + 1) % 3
return keypoints_on_images
@classmethod
def func_polygons(cls, polygons_on_images, random_state, parents, hooks):
if len(polygons_on_images[0].polygons) == 0:
return [ia.PolygonsOnImage([], shape=polygons_on_images[0].shape)]
new_exterior = np.copy(polygons_on_images[0].polygons[0].exterior)
new_exterior[:, 0] += 1
new_exterior[:, 1] += 2
return [
ia.PolygonsOnImage([ia.Polygon(new_exterior)],
shape=polygons_on_images[0].shape)
]
@classmethod
def func_line_strings(cls, line_strings_on_images, random_state, parents,
hooks):
if line_strings_on_images[0].empty:
return [ia.LineStringsOnImage(
[], shape=line_strings_on_images[0].shape)]
new_coords = np.copy(line_strings_on_images[0].items[0].coords)
new_coords[:, 0] += 1
new_coords[:, 1] += 2
return [
ia.LineStringsOnImage(
[ia.LineString(new_coords)],
shape=line_strings_on_images[0].shape)
]
@classmethod
def func_bbs(cls, bounding_boxes_on_images, random_state, parents, hooks):
if bounding_boxes_on_images[0].empty:
return [
ia.BoundingBoxesOnImage(
[], shape=bounding_boxes_on_images[0].shape)
]
new_coords = np.copy(bounding_boxes_on_images[0].items[0].coords)
new_coords[:, 0] += 1
new_coords[:, 1] += 2
return [
ia.BoundingBoxesOnImage(
[ia.BoundingBox(x1=new_coords[0][0], y1=new_coords[0][1],
x2=new_coords[1][0], y2=new_coords[1][1])],
shape=bounding_boxes_on_images[0].shape)
]
def test_images(self):
image = self.base_img
expected = image + 1
aug = iaa.Lambda(func_images=self.func_images)
for _ in sm.xrange(3):
observed = aug.augment_image(image)
assert np.array_equal(observed, expected)
def test_images_deterministic(self):
image = self.base_img
expected = image + 1
aug_det = iaa.Lambda(func_images=self.func_images).to_deterministic()
for _ in sm.xrange(3):
observed = aug_det.augment_image(image)
assert np.array_equal(observed, expected)
def test_images_list(self):
image = self.base_img
expected = [image + 1]
aug = iaa.Lambda(func_images=self.func_images)
observed = aug.augment_images([image])
assert array_equal_lists(observed, expected)
def test_images_list_deterministic(self):
image = self.base_img
expected = [image + 1]
aug_det = iaa.Lambda(func_images=self.func_images).to_deterministic()
observed = aug_det.augment_images([image])
assert array_equal_lists(observed, expected)
def test_heatmaps(self):
heatmaps = self.heatmaps
heatmaps_arr_aug = self.heatmaps_aug.get_arr()
aug = iaa.Lambda(func_heatmaps=self.func_heatmaps)
for _ in sm.xrange(3):
observed = aug.augment_heatmaps(heatmaps)
assert observed.shape == (3, 3, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), heatmaps_arr_aug)
def test_heatmaps_deterministic(self):
heatmaps = self.heatmaps
heatmaps_arr_aug = self.heatmaps_aug.get_arr()
aug_det = iaa.Lambda(func_heatmaps=self.func_heatmaps)\
.to_deterministic()
for _ in sm.xrange(3):
observed = aug_det.augment_heatmaps(heatmaps)
assert observed.shape == (3, 3, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), heatmaps_arr_aug)
def test_segmentation_maps(self):
segmaps = self.segmentation_maps
segmaps_arr_aug = self.segmentation_maps_aug.get_arr()
aug = iaa.Lambda(func_segmentation_maps=self.func_segmaps)
for _ in sm.xrange(3):
observed = aug.augment_segmentation_maps(segmaps)
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.get_arr(), segmaps_arr_aug)
def test_segmentation_maps_deterministic(self):
segmaps = self.segmentation_maps
segmaps_arr_aug = self.segmentation_maps_aug.get_arr()
aug_det = iaa.Lambda(func_segmentation_maps=self.func_segmaps)\
.to_deterministic()
for _ in sm.xrange(3):
observed = aug_det.augment_segmentation_maps(segmaps)
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.get_arr(), segmaps_arr_aug)
def test_keypoints(self):
kpsoi = self.keypoints
aug = iaa.Lambda(func_keypoints=self.func_keypoints)
for _ in sm.xrange(3):
observed = aug.augment_keypoints(kpsoi)
expected = self.keypoints_aug
assert_cbaois_equal(observed, expected)
def test_keypoints_deterministic(self):
kpsoi = self.keypoints
aug = iaa.Lambda(func_keypoints=self.func_keypoints)
aug = aug.to_deterministic()
for _ in sm.xrange(3):
observed = aug.augment_keypoints(kpsoi)
expected = self.keypoints_aug
assert_cbaois_equal(observed, expected)
def test_polygons(self):
psois = self.polygons
aug = iaa.Lambda(func_polygons=self.func_polygons)
for _ in sm.xrange(3):
observed = aug.augment_polygons(psois)
expected_psoi = self.polygons_aug
assert_cbaois_equal(observed, expected_psoi)
def test_polygons_deterministic(self):
psois = self.polygons
aug = iaa.Lambda(func_polygons=self.func_polygons)
aug = aug.to_deterministic()
for _ in sm.xrange(3):
observed = aug.augment_polygons(psois)
expected_psoi = self.polygons_aug
assert_cbaois_equal(observed, expected_psoi)
def test_line_strings(self):
lsois = self.lsoi
aug = iaa.Lambda(func_line_strings=self.func_line_strings)
for _ in sm.xrange(3):
observed = aug.augment_line_strings(lsois)
expected_lsoi = self.lsoi_aug
assert_cbaois_equal(observed, expected_lsoi)
def test_line_strings_deterministic(self):
lsois = self.lsoi
aug = iaa.Lambda(func_line_strings=self.func_line_strings)
aug = aug.to_deterministic()
for _ in sm.xrange(3):
observed = aug.augment_line_strings(lsois)
expected_lsoi = self.lsoi_aug
assert_cbaois_equal(observed, expected_lsoi)
def test_bounding_boxes(self):
bbsoi = self.bbsoi
aug = iaa.Lambda(func_bounding_boxes=self.func_bbs)
for _ in sm.xrange(3):
observed = aug.augment_bounding_boxes(bbsoi)
expected = self.bbsoi_aug
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_deterministic(self):
bbsoi = self.bbsoi
aug = iaa.Lambda(func_bounding_boxes=self.func_bbs)
aug = aug.to_deterministic()
for _ in sm.xrange(3):
observed = aug.augment_bounding_boxes(bbsoi)
expected = self.bbsoi_aug
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_x1_x2_coords_can_get_flipped(self):
# Verify that if any augmented BB ends up with x1 > x2 that the
# x-coordinates will be flipped to ensure that x1 is always below x2
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
], shape=(10, 10, 3))
def _func_bbs(bounding_boxes_on_images, random_state, parents, hooks):
bounding_boxes_on_images[0].bounding_boxes[0].x1 += 10
return bounding_boxes_on_images
aug = iaa.Lambda(func_bounding_boxes=_func_bbs)
for _ in sm.xrange(3):
observed = aug.augment_bounding_boxes(bbsoi)
assert np.allclose(
observed.bounding_boxes[0].coords,
[(2, 1), (0+10, 3)]
)
def test_bounding_boxes_y1_y2_coords_can_get_flipped(self):
# Verify that if any augmented BB ends up with y1 > y2 that the
# x-coordinates will be flipped to ensure that y1 is always below y2
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
], shape=(10, 10, 3))
def _func_bbs(bounding_boxes_on_images, random_state, parents, hooks):
bounding_boxes_on_images[0].bounding_boxes[0].y1 += 10
return bounding_boxes_on_images
aug = iaa.Lambda(func_bounding_boxes=_func_bbs)
for _ in sm.xrange(3):
observed = aug.augment_bounding_boxes(bbsoi)
assert np.allclose(
observed.bounding_boxes[0].coords,
[(0, 3), (2, 1+10)]
)
def test_keypoints_empty(self):
kpsoi = ia.KeypointsOnImage([], shape=(1, 2, 3))
aug = iaa.Lambda(func_keypoints=self.func_keypoints)
observed = aug.augment_keypoints(kpsoi)
assert_cbaois_equal(observed, kpsoi)
def test_polygons_empty(self):
psoi = ia.PolygonsOnImage([], shape=(1, 2, 3))
aug = iaa.Lambda(func_polygons=self.func_polygons)
observed = aug.augment_polygons(psoi)
assert_cbaois_equal(observed, psoi)
def test_line_strings_empty(self):
lsoi = ia.LineStringsOnImage([], shape=(1, 2, 3))
aug = iaa.Lambda(func_line_strings=self.func_line_strings)
observed = aug.augment_line_strings(lsoi)
assert_cbaois_equal(observed, lsoi)
def test_bounding_boxes_empty(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(1, 2, 3))
aug = iaa.Lambda(func_bounding_boxes=self.func_bbs)
observed = aug.augment_bounding_boxes(bbsoi)
assert_cbaois_equal(observed, bbsoi)
# TODO add tests when funcs are not set in Lambda
def test_other_dtypes_bool(self):
def func_images(images, random_state, parents, hooks):
aug = iaa.Flipud(1.0) # flipud is know to work with all dtypes
return aug.augment_images(images)
aug = iaa.Lambda(func_images=func_images)
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
expected = np.zeros((3, 3), dtype=bool)
expected[2, 0] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == "bool"
assert np.all(image_aug == expected)
def test_other_dtypes_uint_int(self):
def func_images(images, random_state, parents, hooks):
aug = iaa.Flipud(1.0) # flipud is know to work with all dtypes
return aug.augment_images(images)
aug = iaa.Lambda(func_images=func_images)
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
expected = np.zeros((3, 3), dtype=dtype)
expected[2, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, expected)
def test_other_dtypes_float(self):
def func_images(images, random_state, parents, hooks):
aug = iaa.Flipud(1.0) # flipud is know to work with all dtypes
return aug.augment_images(images)
aug = iaa.Lambda(func_images=func_images)
try:
f128 = [np.dtype("float128").name]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
expected = np.zeros((3, 3), dtype=dtype)
expected[2, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == expected)
def test_pickleable(self):
aug = iaa.Lambda(
func_images=_lambda_pickleable_callback_images,
seed=1)
runtest_pickleable_uint8_img(aug)
def _lambda_pickleable_callback_images(images, random_state, parents, hooks):
aug = iaa.Flipud(0.5, seed=random_state)
return aug.augment_images(images)
class TestAssertLambda(unittest.TestCase):
DTYPES_UINT = ["uint8", "uint16", "uint32", "uint64"]
DTYPES_INT = ["int8", "int32", "int64"]
DTYPES_FLOAT = (
["float16", "float32", "float64"]
+ (
["float128"] if hasattr(np, "float128") else []
)
)
def setUp(self):
reseed()
@property
def image(self):
base_img = np.array([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]], dtype=np.uint8)
return np.atleast_3d(base_img)
@property
def images(self):
return np.array([self.image])
@property
def heatmaps(self):
heatmaps_arr = np.float32([[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0]])
return ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def segmaps(self):
segmaps_arr = np.int32([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]])
return SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
return ia.KeypointsOnImage(kps, shape=self.image.shape)
@property
def psoi(self):
polygons = [ia.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])]
return ia.PolygonsOnImage(polygons, shape=self.image.shape)
@property
def lsoi(self):
lss = [ia.LineString([(0, 0), (2, 0), (2, 2), (0, 2)])]
return ia.LineStringsOnImage(lss, shape=self.image.shape)
@property
def bbsoi(self):
bb = ia.BoundingBox(x1=0, y1=0, x2=2, y2=2)
return ia.BoundingBoxesOnImage([bb], shape=self.image.shape)
@property
def aug_succeeds(self):
def _func_images_succeeds(images, random_state, parents, hooks):
return images[0][0, 0] == 0 and images[0][2, 2] == 1
def _func_heatmaps_succeeds(heatmaps, random_state, parents, hooks):
return heatmaps[0].arr_0to1[0, 0] < 0 + 1e-6
def _func_segmaps_succeeds(segmaps, random_state, parents, hooks):
return segmaps[0].arr[0, 0] == 0
def _func_keypoints_succeeds(keypoints_on_images, random_state, parents,
hooks):
return (
keypoints_on_images[0].keypoints[0].x == 0
and keypoints_on_images[0].keypoints[2].x == 2
)
def _func_bounding_boxes_succeeds(bounding_boxes_on_images,
random_state, parents, hooks):
return (bounding_boxes_on_images[0].items[0].x1 == 0
and bounding_boxes_on_images[0].items[0].x2 == 2)
def _func_polygons_succeeds(polygons_on_images, random_state, parents,
hooks):
return (polygons_on_images[0].polygons[0].exterior[0][0] == 0
and polygons_on_images[0].polygons[0].exterior[2][1] == 2)
def _func_line_strings_succeeds(line_strings_on_image, random_state,
parents, hooks):
return (line_strings_on_image[0].items[0].coords[0][0] == 0
and line_strings_on_image[0].items[0].coords[2][1] == 2)
return iaa.AssertLambda(
func_images=_func_images_succeeds,
func_heatmaps=_func_heatmaps_succeeds,
func_segmentation_maps=_func_segmaps_succeeds,
func_keypoints=_func_keypoints_succeeds,
func_bounding_boxes=_func_bounding_boxes_succeeds,
func_polygons=_func_polygons_succeeds,
func_line_strings=_func_line_strings_succeeds)
@property
def aug_fails(self):
def _func_images_fails(images, random_state, parents, hooks):
return images[0][0, 0] == 1
def _func_heatmaps_fails(heatmaps, random_state, parents, hooks):
return heatmaps[0].arr_0to1[0, 0] > 0 + 1e-6
def _func_segmaps_fails(segmaps, random_state, parents, hooks):
return segmaps[0].arr[0, 0] == 1
def _func_keypoints_fails(keypoints_on_images, random_state, parents,
hooks):
return keypoints_on_images[0].keypoints[0].x == 2
def _func_bounding_boxes_fails(bounding_boxes_on_images, random_state,
parents, hooks):
return bounding_boxes_on_images[0].items[0].x1 == 2
def _func_polygons_fails(polygons_on_images, random_state, parents,
hooks):
return polygons_on_images[0].polygons[0].exterior[0][0] == 2
def _func_line_strings_fails(line_strings_on_images, random_state,
parents, hooks):
return line_strings_on_images[0].items[0].coords[0][0] == 2
return iaa.AssertLambda(
func_images=_func_images_fails,
func_heatmaps=_func_heatmaps_fails,
func_segmentation_maps=_func_segmaps_fails,
func_keypoints=_func_keypoints_fails,
func_bounding_boxes=_func_bounding_boxes_fails,
func_polygons=_func_polygons_fails,
func_line_strings=_func_line_strings_fails)
def test_images_as_array_with_assert_that_succeeds(self):
observed = self.aug_succeeds.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_images_as_array_with_assert_that_fails(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_images(self.images)
def test_images_as_array_with_assert_that_succeeds__deterministic(self):
aug_succeeds_det = self.aug_succeeds.to_deterministic()
observed = aug_succeeds_det.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_images_as_array_with_assert_that_fails__deterministic(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_images(self.images)
def test_images_as_list_with_assert_that_succeeds(self):
observed = self.aug_succeeds.augment_images([self.images[0]])
expected = [self.images[0]]
assert array_equal_lists(observed, expected)
def test_images_as_list_with_assert_that_fails(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_images([self.images[0]])
def test_images_as_list_with_assert_that_succeeds__deterministic(self):
aug_succeeds_det = self.aug_succeeds.to_deterministic()
observed = aug_succeeds_det.augment_images([self.images[0]])
expected = [self.images[0]]
assert array_equal_lists(observed, expected)
def test_images_as_list_with_assert_that_fails__deterministic(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_images([self.images[0]])
def test_heatmaps_with_assert_that_succeeds(self):
observed = self.aug_succeeds.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 3, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_heatmaps_with_assert_that_fails(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_heatmaps(self.heatmaps)
def test_heatmaps_with_assert_that_succeeds__deterministic(self):
aug_succeeds_det = self.aug_succeeds.to_deterministic()
observed = aug_succeeds_det.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 3, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_heatmaps_with_assert_that_fails__deterministic(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_heatmaps(self.heatmaps)
def test_segmaps_with_assert_that_succeeds(self):
observed = self.aug_succeeds.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.get_arr(), self.segmaps.get_arr())
def test_segmaps_with_assert_that_fails(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_segmentation_maps(self.segmaps)
def test_segmaps_with_assert_that_succeeds__deterministic(self):
aug_succeeds_det = self.aug_succeeds.to_deterministic()
observed = aug_succeeds_det.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.get_arr(), self.segmaps.get_arr())
def test_segmaps_with_assert_that_fails__deterministic(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_segmentation_maps(self.segmaps)
def test_keypoints_with_assert_that_succeeds(self):
observed = self.aug_succeeds.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_keypoints_with_assert_that_fails(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_keypoints(self.kpsoi)
def test_keypoints_with_assert_that_succeeds__deterministic(self):
aug_succeeds_det = self.aug_succeeds.to_deterministic()
observed = aug_succeeds_det.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_keypoints_with_assert_that_fails__deterministic(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_keypoints(self.kpsoi)
def test_polygons_with_assert_that_succeeds(self):
observed = self.aug_succeeds.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_polygons_with_assert_that_fails(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_polygons(self.psoi)
def test_polygons_with_assert_that_succeeds__deterministic(self):
aug_succeeds_det = self.aug_succeeds.to_deterministic()
observed = aug_succeeds_det.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_polygons_with_assert_that_fails__deterministic(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_polygons(self.psoi)
def test_line_strings_with_assert_that_succeeds(self):
observed = self.aug_succeeds.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_line_strings_with_assert_that_fails(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_line_strings(self.lsoi)
def test_line_strings_with_assert_that_succeeds__deterministic(self):
aug_succeeds_det = self.aug_succeeds.to_deterministic()
observed = aug_succeeds_det.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_line_strings_with_assert_that_fails__deterministic(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_line_strings(self.lsoi)
def test_bounding_boxes_with_assert_that_succeeds(self):
observed = self.aug_succeeds.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_bounding_boxes_with_assert_that_fails(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_bounding_boxes(self.bbsoi)
def test_bounding_boxes_with_assert_that_succeeds__deterministic(self):
aug_succeeds_det = self.aug_succeeds.to_deterministic()
observed = aug_succeeds_det.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_bounding_boxes_with_assert_that_fails__deterministic(self):
with self.assertRaises(AssertionError):
_ = self.aug_fails.augment_bounding_boxes(self.bbsoi)
def test_other_dtypes_bool__with_assert_that_succeeds(self):
def func_images_succeeds(images, random_state, parents, hooks):
return np.allclose(images[0][0, 0], 1, rtol=0, atol=1e-6)
aug = iaa.AssertLambda(func_images=func_images_succeeds)
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert np.all(image_aug == image)
def test_other_dtypes_uint_int__with_assert_that_succeeds(self):
def func_images_succeeds(images, random_state, parents, hooks):
return np.allclose(images[0][0, 0], 1, rtol=0, atol=1e-6)
aug = iaa.AssertLambda(func_images=func_images_succeeds)
dtypes = self.DTYPES_UINT + self.DTYPES_INT
for dtype in dtypes:
with self.subTest(dtype=dtype):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = 1
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, image)
def test_other_dtypes_float__with_assert_that_succeeds(self):
def func_images_succeeds(images, random_state, parents, hooks):
return np.allclose(images[0][0, 0], 1, rtol=0, atol=1e-6)
aug = iaa.AssertLambda(func_images=func_images_succeeds)
dtypes = self.DTYPES_FLOAT
for dtype in dtypes:
with self.subTest(dtype=dtype):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = 1
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == image)
def test_other_dtypes_bool__with_assert_that_fails(self):
def func_images_fails(images, random_state, parents, hooks):
return np.allclose(images[0][0, 1], 1, rtol=0, atol=1e-6)
aug = iaa.AssertLambda(func_images=func_images_fails)
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
with self.assertRaises(AssertionError):
_ = aug.augment_image(image)
def test_other_dtypes_uint_int__with_assert_that_fails(self):
def func_images_fails(images, random_state, parents, hooks):
return np.allclose(images[0][0, 1], 1, rtol=0, atol=1e-6)
aug = iaa.AssertLambda(func_images=func_images_fails)
dtypes = self.DTYPES_UINT + self.DTYPES_INT
for dtype in dtypes:
with self.subTest(dtype=dtype):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = 1
with self.assertRaises(AssertionError):
_ = aug.augment_image(image)
def test_other_dtypes_float__with_assert_that_fails(self):
def func_images_fails(images, random_state, parents, hooks):
return np.allclose(images[0][0, 1], 1, rtol=0, atol=1e-6)
aug = iaa.AssertLambda(func_images=func_images_fails)
dtypes = self.DTYPES_FLOAT
for dtype in dtypes:
with self.subTest(dtype=dtype):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = 1
with self.assertRaises(AssertionError):
_ = aug.augment_image(image)
def test_pickleable(self):
aug = iaa.AssertLambda(
func_images=_assertlambda_pickleable_callback_images,
seed=1)
runtest_pickleable_uint8_img(aug, iterations=2)
# in py3+, this could be a classmethod of TestAssertLambda,
# but in py2.7 such classmethods are not pickle-able and would cause an error
def _assertlambda_pickleable_callback_images(images, random_state,
parents, hooks):
return np.any(images[0] > 0)
class TestAssertShape(unittest.TestCase):
DTYPES_UINT = ["uint8", "uint16", "uint32", "uint64"]
DTYPES_INT = ["int8", "int32", "int64"]
DTYPES_FLOAT = (
["float16", "float32", "float64"]
+ (
["float128"] if hasattr(np, "float128") else []
)
)
def setUp(self):
reseed()
@property
def image(self):
base_img = np.array([[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 1, 1, 0]], dtype=np.uint8)
return np.atleast_3d(base_img)
@property
def images(self):
return np.array([self.image])
@property
def heatmaps(self):
heatmaps_arr = np.float32([[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 1.0, 0.0]])
return ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 4, 3))
@property
def segmaps(self):
segmaps_arr = np.int32([[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 1, 1, 0]])
return SegmentationMapsOnImage(segmaps_arr, shape=(3, 4, 3))
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
return ia.KeypointsOnImage(kps, shape=self.image.shape)
@property
def psoi(self):
polygons = [ia.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])]
return ia.PolygonsOnImage(polygons, shape=self.image.shape)
@property
def lsoi(self):
lss = [ia.LineString([(0, 0), (2, 0), (2, 2), (0, 2)])]
return ia.LineStringsOnImage(lss, shape=self.image.shape)
@property
def bbsoi(self):
bb = ia.BoundingBox(x1=0, y1=0, x2=2, y2=2)
return ia.BoundingBoxesOnImage([bb], shape=self.image.shape)
@property
def image_h4(self):
base_img_h4 = np.array([[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 1, 1, 0],
[1, 0, 1, 0]], dtype=np.uint8)
return np.atleast_3d(base_img_h4)
@property
def images_h4(self):
return np.array([self.image_h4])
@property
def heatmaps_h4(self):
heatmaps_arr_h4 = np.float32([[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 1.0, 1.0, 0.0],
[1.0, 0.0, 1.0, 0.0]])
return ia.HeatmapsOnImage(heatmaps_arr_h4, shape=(4, 4, 3))
@property
def segmaps_h4(self):
segmaps_arr_h4 = np.int32([[0, 0, 1, 0],
[0, 0, 1, 0],
[0, 1, 1, 0],
[1, 0, 1, 0]])
return SegmentationMapsOnImage(segmaps_arr_h4, shape=(4, 4, 3))
@property
def kpsoi_h4(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
return ia.KeypointsOnImage(kps, shape=self.image_h4.shape)
@property
def psoi_h4(self):
polygons = [ia.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])]
return ia.PolygonsOnImage(polygons, shape=self.image_h4.shape)
@property
def lsoi_h4(self):
lss = [ia.LineString([(0, 0), (2, 0), (2, 2), (0, 2)])]
return ia.LineStringsOnImage(lss, shape=self.image_h4.shape)
@property
def bbsoi_h4(self):
bb = ia.BoundingBox(x1=0, y1=0, x2=2, y2=2)
return ia.BoundingBoxesOnImage([bb], shape=self.image_h4.shape)
@property
def aug_exact_shape(self):
return iaa.AssertShape((1, 3, 4, 1))
@property
def aug_none_in_shape(self):
return iaa.AssertShape((None, 3, 4, 1))
@property
def aug_list_in_shape(self):
return iaa.AssertShape((1, [1, 3, 5], 4, 1))
@property
def aug_tuple_in_shape(self):
return iaa.AssertShape((1, (1, 4), 4, 1))
def test_images_with_exact_shape__succeeds(self):
aug = self.aug_exact_shape
observed = aug.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_images_with_exact_shape__succeeds__deterministic(self):
aug_det = self.aug_exact_shape.to_deterministic()
observed = aug_det.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_images_with_exact_shape__succeeds__list(self):
aug = self.aug_exact_shape
observed = aug.augment_images([self.images[0]])
expected = [self.images[0]]
assert array_equal_lists(observed, expected)
def test_images_with_exact_shape__succeeds__deterministic__list(self):
aug_det = self.aug_exact_shape.to_deterministic()
observed = aug_det.augment_images([self.images[0]])
expected = [self.images[0]]
assert array_equal_lists(observed, expected)
def test_heatmaps_with_exact_shape__succeeds(self):
aug = self.aug_exact_shape
observed = aug.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 4, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_heatmaps_with_exact_shape__succeeds__deterministic(self):
aug_det = self.aug_exact_shape.to_deterministic()
observed = aug_det.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 4, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_segmaps_with_exact_shape__succeeds(self):
aug = self.aug_exact_shape
observed = aug.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 4, 3)
assert np.array_equal(observed.get_arr(), self.segmaps.get_arr())
def test_segmaps_with_exact_shape__succeeds__deterministic(self):
aug_det = self.aug_exact_shape.to_deterministic()
observed = aug_det.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 4, 3)
assert np.array_equal(observed.get_arr(), self.segmaps.get_arr())
def test_keypoints_with_exact_shape__succeeds(self):
aug = self.aug_exact_shape
observed = aug.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_keypoints_with_exact_shape__succeeds__deterministic(self):
aug_det = self.aug_exact_shape.to_deterministic()
observed = aug_det.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_polygons_with_exact_shape__succeeds(self):
aug = self.aug_exact_shape
observed = aug.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_polygons_with_exact_shape__succeeds__deterministic(self):
aug_det = self.aug_exact_shape.to_deterministic()
observed = aug_det.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_line_strings_with_exact_shape__succeeds(self):
aug = self.aug_exact_shape
observed = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_line_strings_with_exact_shape__succeeds__deterministic(self):
aug_det = self.aug_exact_shape.to_deterministic()
observed = aug_det.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_bounding_boxes_with_exact_shape__succeeds(self):
aug = self.aug_exact_shape
observed = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_bounding_boxes_with_exact_shape__succeeds__deterministic(self):
aug_det = self.aug_exact_shape.to_deterministic()
observed = aug_det.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_images_with_exact_shape__fails(self):
aug = self.aug_exact_shape
with self.assertRaises(AssertionError):
_ = aug.augment_images(self.images_h4)
def test_heatmaps_with_exact_shape__fails(self):
aug = self.aug_exact_shape
with self.assertRaises(AssertionError):
_ = aug.augment_heatmaps(self.heatmaps_h4)
def test_keypoints_with_exact_shape__fails(self):
aug = self.aug_exact_shape
with self.assertRaises(AssertionError):
_ = aug.augment_keypoints(self.kpsoi_h4)
def test_polygons_with_exact_shape__fails(self):
aug = self.aug_exact_shape
with self.assertRaises(AssertionError):
_ = aug.augment_polygons(self.psoi_h4)
def test_line_strings_with_exact_shape__fails(self):
aug = self.aug_exact_shape
with self.assertRaises(AssertionError):
_ = aug.augment_line_strings(self.lsoi_h4)
def test_bounding_boxes_with_exact_shape__fails(self):
aug = self.aug_exact_shape
with self.assertRaises(AssertionError):
_ = aug.augment_bounding_boxes(self.bbsoi_h4)
def test_images_with_none_in_shape__succeeds(self):
aug = self.aug_none_in_shape
observed = aug.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_images_with_none_in_shape__succeeds__deterministic(self):
aug_det = self.aug_none_in_shape.to_deterministic()
observed = aug_det.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_heatmaps_with_none_in_shape__succeeds(self):
aug = self.aug_none_in_shape
observed = aug.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 4, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_heatmaps_with_none_in_shape__succeeds__deterministic(self):
aug_det = self.aug_none_in_shape.to_deterministic()
observed = aug_det.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 4, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_segmaps_with_none_in_shape__succeeds(self):
aug = self.aug_none_in_shape
observed = aug.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 4, 3)
assert np.array_equal(observed.get_arr(), self.heatmaps.get_arr())
def test_segmaps_with_none_in_shape__succeeds__deterministic(self):
aug_det = self.aug_none_in_shape.to_deterministic()
observed = aug_det.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 4, 3)
assert np.array_equal(observed.get_arr(), self.segmaps.get_arr())
def test_keypoints_with_none_in_shape__succeeds(self):
aug = self.aug_none_in_shape
observed = aug.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_keypoints_with_none_in_shape__succeeds__deterministic(self):
aug_det = self.aug_none_in_shape.to_deterministic()
observed = aug_det.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_polygons_with_none_in_shape__succeeds(self):
aug = self.aug_none_in_shape
observed = aug.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_polygons_with_none_in_shape__succeeds__deterministic(self):
aug_det = self.aug_none_in_shape.to_deterministic()
observed = aug_det.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_line_strings_with_none_in_shape__succeeds(self):
aug = self.aug_none_in_shape
observed = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_line_strings_with_none_in_shape__succeeds__deterministic(self):
aug_det = self.aug_none_in_shape.to_deterministic()
observed = aug_det.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_bounding_boxes_with_none_in_shape__succeeds(self):
aug = self.aug_none_in_shape
observed = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_bounding_boxes_with_none_in_shape__succeeds__deterministic(self):
aug_det = self.aug_none_in_shape.to_deterministic()
observed = aug_det.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_images_with_none_in_shape__fails(self):
aug = self.aug_none_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_images(self.images_h4)
def test_heatmaps_with_none_in_shape__fails(self):
aug = self.aug_none_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_heatmaps(self.heatmaps_h4)
def test_keypoints_with_none_in_shape__fails(self):
aug = self.aug_none_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_keypoints(self.kpsoi_h4)
def test_polygons_with_none_in_shape__fails(self):
aug = self.aug_none_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_polygons(self.psoi_h4)
def test_line_strings_with_none_in_shape__fails(self):
aug = self.aug_none_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_line_strings(self.lsoi_h4)
def test_bounding_boxes_with_none_in_shape__fails(self):
aug = self.aug_none_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_bounding_boxes(self.bbsoi_h4)
def test_images_with_list_in_shape__succeeds(self):
aug = self.aug_list_in_shape
observed = aug.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_images_with_list_in_shape__succeeds__deterministic(self):
aug_det = self.aug_list_in_shape.to_deterministic()
observed = aug_det.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_heatmaps_with_list_in_shape__succeeds(self):
aug = self.aug_list_in_shape
observed = aug.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 4, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_heatmaps_with_list_in_shape__succeeds__deterministic(self):
aug_det = self.aug_list_in_shape.to_deterministic()
observed = aug_det.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 4, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_segmaps_with_list_in_shape__succeeds(self):
aug = self.aug_list_in_shape
observed = aug.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 4, 3)
assert np.array_equal(observed.get_arr(), self.segmaps.get_arr())
def test_segmaps_with_list_in_shape__succeeds__deterministic(self):
aug_det = self.aug_list_in_shape.to_deterministic()
observed = aug_det.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 4, 3)
assert np.array_equal(observed.get_arr(), self.segmaps.get_arr())
def test_keypoints_with_list_in_shape__succeeds(self):
aug = self.aug_list_in_shape
observed = aug.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_keypoints_with_list_in_shape__succeeds__deterministic(self):
aug_det = self.aug_list_in_shape.to_deterministic()
observed = aug_det.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_polygons_with_list_in_shape__succeeds(self):
aug = self.aug_list_in_shape
observed = aug.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_polygons_with_list_in_shape__succeeds__deterministic(self):
aug_det = self.aug_list_in_shape.to_deterministic()
observed = aug_det.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_line_strings_with_list_in_shape__succeeds(self):
aug = self.aug_list_in_shape
observed = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_line_strings_with_list_in_shape__succeeds__deterministic(self):
aug_det = self.aug_list_in_shape.to_deterministic()
observed = aug_det.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_bounding_boxes_with_list_in_shape__succeeds(self):
aug = self.aug_list_in_shape
observed = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_bounding_boxes_with_list_in_shape__succeeds__deterministic(self):
aug_det = self.aug_list_in_shape.to_deterministic()
observed = aug_det.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_images_with_list_in_shape__fails(self):
aug = self.aug_list_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_images(self.images_h4)
def test_heatmaps_with_list_in_shape__fails(self):
aug = self.aug_list_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_heatmaps(self.heatmaps_h4)
def test_segmaps_with_list_in_shape__fails(self):
aug = self.aug_list_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_segmentation_maps(self.segmaps_h4)
def test_keypoints_with_list_in_shape__fails(self):
aug = self.aug_list_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_keypoints(self.kpsoi_h4)
def test_polygons_with_list_in_shape__fails(self):
aug = self.aug_list_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_polygons(self.psoi_h4)
def test_line_strings_with_list_in_shape__fails(self):
aug = self.aug_list_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_line_strings(self.lsoi_h4)
def test_bounding_boxes_with_list_in_shape__fails(self):
aug = self.aug_list_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_bounding_boxes(self.bbsoi_h4)
def test_images_with_tuple_in_shape__succeeds(self):
aug = self.aug_tuple_in_shape
observed = aug.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_images_with_tuple_in_shape__succeeds__deterministic(self):
aug_det = self.aug_tuple_in_shape.to_deterministic()
observed = aug_det.augment_images(self.images)
expected = self.images
assert np.array_equal(observed, expected)
def test_heatmaps_with_tuple_in_shape__succeeds(self):
aug = self.aug_tuple_in_shape
observed = aug.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 4, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_heatmaps_with_tuple_in_shape__succeeds__deterministic(self):
aug_det = self.aug_tuple_in_shape.to_deterministic()
observed = aug_det.augment_heatmaps(self.heatmaps)
assert observed.shape == (3, 4, 3)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.get_arr(), self.heatmaps.get_arr())
def test_segmaps_with_tuple_in_shape__succeeds(self):
aug = self.aug_tuple_in_shape
observed = aug.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 4, 3)
assert np.array_equal(observed.get_arr(), self.heatmaps.get_arr())
def test_segmaps_with_tuple_in_shape__succeeds__deterministic(self):
aug_det = self.aug_tuple_in_shape.to_deterministic()
observed = aug_det.augment_segmentation_maps(self.segmaps)
assert observed.shape == (3, 4, 3)
assert np.array_equal(observed.get_arr(), self.heatmaps.get_arr())
def test_keypoints_with_tuple_in_shape__succeeds(self):
aug = self.aug_tuple_in_shape
observed = aug.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_keypoints_with_tuple_in_shape__succeeds__deterministic(self):
aug_det = self.aug_tuple_in_shape.to_deterministic()
observed = aug_det.augment_keypoints(self.kpsoi)
assert_cbaois_equal(observed, self.kpsoi)
def test_polygons_with_tuple_in_shape__succeeds(self):
aug = self.aug_tuple_in_shape
observed = aug.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_polygons_with_tuple_in_shape__succeeds__deterministic(self):
aug_det = self.aug_tuple_in_shape.to_deterministic()
observed = aug_det.augment_polygons(self.psoi)
assert_cbaois_equal(observed, self.psoi)
def test_line_strings_with_tuple_in_shape__succeeds(self):
aug = self.aug_tuple_in_shape
observed = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_line_strings_with_tuple_in_shape__succeeds__deterministic(self):
aug_det = self.aug_tuple_in_shape.to_deterministic()
observed = aug_det.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi)
def test_bounding_boxes_with_tuple_in_shape__succeeds(self):
aug = self.aug_tuple_in_shape
observed = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_bounding_boxes_with_tuple_in_shape__succeeds__deterministic(self):
aug_det = self.aug_tuple_in_shape.to_deterministic()
observed = aug_det.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi)
def test_images_with_tuple_in_shape__fails(self):
aug = self.aug_tuple_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_images(self.images_h4)
def test_heatmaps_with_tuple_in_shape__fails(self):
aug = self.aug_tuple_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_heatmaps(self.heatmaps_h4)
def test_segmaps_with_tuple_in_shape__fails(self):
aug = self.aug_tuple_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_segmentation_maps(self.segmaps_h4)
def test_keypoints_with_tuple_in_shape__fails(self):
aug = self.aug_tuple_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_keypoints(self.kpsoi_h4)
def test_polygons_with_tuple_in_shape__fails(self):
aug = self.aug_tuple_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_polygons(self.psoi_h4)
def test_line_strings_with_tuple_in_shape__fails(self):
aug = self.aug_tuple_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_line_strings(self.lsoi_h4)
def test_bounding_boxes_with_tuple_in_shape__fails(self):
aug = self.aug_tuple_in_shape
with self.assertRaises(AssertionError):
_ = aug.augment_bounding_boxes(self.bbsoi_h4)
def test_fails_if_shape_contains_invalid_datatype(self):
got_exception = False
try:
aug = iaa.AssertShape((1, False, 4, 1))
_ = aug.augment_images(np.zeros((1, 2, 2, 1), dtype=np.uint8))
except Exception as exc:
assert "Invalid datatype " in str(exc)
got_exception = True
assert got_exception
def test_other_dtypes_bool__succeeds(self):
aug = iaa.AssertShape((None, 3, 3, 1))
image = np.zeros((3, 3, 1), dtype=bool)
image[0, 0, 0] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.type == image.dtype.type
assert np.all(image_aug == image)
def test_other_dtypes_uint_int__succeeds(self):
aug = iaa.AssertShape((None, 3, 3, 1))
for dtype in self.DTYPES_UINT + self.DTYPES_INT:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3, 1), dtype=dtype)
image[0, 0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, image)
def test_other_dtypes_float__succeeds(self):
aug = iaa.AssertShape((None, 3, 3, 1))
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(self.DTYPES_FLOAT, values):
with self.subTest(dtype=dtype):
image = np.zeros((3, 3, 1), dtype=dtype)
image[0, 0, 0] = 1
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == image)
def test_other_dtypes_bool__fails(self):
aug = iaa.AssertShape((None, 3, 4, 1))
image = np.zeros((3, 3, 1), dtype=bool)
image[0, 0, 0] = True
with self.assertRaises(AssertionError):
_ = aug.augment_image(image)
def test_other_dtypes_uint_int__fails(self):
aug = iaa.AssertShape((None, 3, 4, 1))
for dtype in self.DTYPES_UINT + self.DTYPES_INT:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3, 1), dtype=dtype)
image[0, 0, 0] = value
with self.assertRaises(AssertionError):
_ = aug.augment_image(image)
def test_other_dtypes_float__fails(self):
aug = iaa.AssertShape((None, 3, 4, 1))
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(self.DTYPES_FLOAT, values):
image = np.zeros((3, 3, 1), dtype=dtype)
image[0, 0, 0] = value
with self.assertRaises(AssertionError):
_ = aug.augment_image(image)
def test_pickleable(self):
aug = iaa.AssertShape(
shape=(None, 15, 15, None), check_images=True,
seed=1)
runtest_pickleable_uint8_img(aug, iterations=2, shape=(15, 15, 1))
def test_clip_augmented_image_():
warnings.resetwarnings()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
image = np.zeros((1, 3), dtype=np.uint8)
image[0, 0] = 10
image[0, 1] = 20
image[0, 2] = 30
image_clipped = iaa.clip_augmented_image_(image,
min_value=15, max_value=25)
assert image_clipped[0, 0] == 15
assert image_clipped[0, 1] == 20
assert image_clipped[0, 2] == 25
assert len(caught_warnings) >= 1
assert "deprecated" in str(caught_warnings[-1].message)
def test_clip_augmented_image():
warnings.resetwarnings()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
image = np.zeros((1, 3), dtype=np.uint8)
image[0, 0] = 10
image[0, 1] = 20
image[0, 2] = 30
image_clipped = iaa.clip_augmented_image(image,
min_value=15, max_value=25)
assert image_clipped[0, 0] == 15
assert image_clipped[0, 1] == 20
assert image_clipped[0, 2] == 25
assert len(caught_warnings) >= 1
assert "deprecated" in str(caught_warnings[-1].message)
def test_clip_augmented_images_():
warnings.resetwarnings()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
images = np.zeros((2, 1, 3), dtype=np.uint8)
images[:, 0, 0] = 10
images[:, 0, 1] = 20
images[:, 0, 2] = 30
imgs_clipped = iaa.clip_augmented_images_(images,
min_value=15, max_value=25)
assert np.all(imgs_clipped[:, 0, 0] == 15)
assert np.all(imgs_clipped[:, 0, 1] == 20)
assert np.all(imgs_clipped[:, 0, 2] == 25)
images = [np.zeros((1, 3), dtype=np.uint8) for _ in sm.xrange(2)]
for i in sm.xrange(len(images)):
images[i][0, 0] = 10
images[i][0, 1] = 20
images[i][0, 2] = 30
imgs_clipped = iaa.clip_augmented_images_(images,
min_value=15, max_value=25)
assert isinstance(imgs_clipped, list)
assert np.all([imgs_clipped[i][0, 0] == 15
for i in sm.xrange(len(images))])
assert np.all([imgs_clipped[i][0, 1] == 20
for i in sm.xrange(len(images))])
assert np.all([imgs_clipped[i][0, 2] == 25
for i in sm.xrange(len(images))])
assert len(caught_warnings) >= 1
assert "deprecated" in str(caught_warnings[-1].message)
def test_clip_augmented_images():
warnings.resetwarnings()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
images = np.zeros((2, 1, 3), dtype=np.uint8)
images[:, 0, 0] = 10
images[:, 0, 1] = 20
images[:, 0, 2] = 30
imgs_clipped = iaa.clip_augmented_images(images,
min_value=15, max_value=25)
assert np.all(imgs_clipped[:, 0, 0] == 15)
assert np.all(imgs_clipped[:, 0, 1] == 20)
assert np.all(imgs_clipped[:, 0, 2] == 25)
images = [np.zeros((1, 3), dtype=np.uint8) for _ in sm.xrange(2)]
for i in sm.xrange(len(images)):
images[i][0, 0] = 10
images[i][0, 1] = 20
images[i][0, 2] = 30
imgs_clipped = iaa.clip_augmented_images(images,
min_value=15, max_value=25)
assert isinstance(imgs_clipped, list)
assert np.all([imgs_clipped[i][0, 0] == 15
for i in sm.xrange(len(images))])
assert np.all([imgs_clipped[i][0, 1] == 20
for i in sm.xrange(len(images))])
assert np.all([imgs_clipped[i][0, 2] ==
25 for i in sm.xrange(len(images))])
assert len(caught_warnings) >= 1
assert "deprecated" in str(caught_warnings[-1].message)
def test_reduce_to_nonempty():
kpsois = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(4, 4, 3)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1), ia.Keypoint(x=1, y=0)],
shape=(4, 4, 3)),
ia.KeypointsOnImage([], shape=(4, 4, 3)),
ia.KeypointsOnImage([ia.Keypoint(x=2, y=2)], shape=(4, 4, 3)),
ia.KeypointsOnImage([], shape=(4, 4, 3))
]
kpsois_reduced, ids = iaa.reduce_to_nonempty(kpsois)
assert kpsois_reduced == [kpsois[0], kpsois[1], kpsois[3]]
assert ids == [0, 1, 3]
kpsois = [
ia.KeypointsOnImage([], shape=(4, 4, 3)),
ia.KeypointsOnImage([], shape=(4, 4, 3))
]
kpsois_reduced, ids = iaa.reduce_to_nonempty(kpsois)
assert kpsois_reduced == []
assert ids == []
kpsois = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(4, 4, 3))
]
kpsois_reduced, ids = iaa.reduce_to_nonempty(kpsois)
assert kpsois_reduced == [kpsois[0]]
assert ids == [0]
kpsois = []
kpsois_reduced, ids = iaa.reduce_to_nonempty(kpsois)
assert kpsois_reduced == []
assert ids == []
def test_invert_reduce_to_nonempty():
kpsois = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1)], shape=(4, 4, 3)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=1),
ia.Keypoint(x=1, y=0)], shape=(4, 4, 3)),
ia.KeypointsOnImage([ia.Keypoint(x=2, y=2)], shape=(4, 4, 3)),
]
kpsois_recovered = iaa.invert_reduce_to_nonempty(
kpsois, [0, 1, 2], ["foo1", "foo2", "foo3"])
assert kpsois_recovered == ["foo1", "foo2", "foo3"]
kpsois_recovered = iaa.invert_reduce_to_nonempty(kpsois, [1], ["foo1"])
assert np.all([
isinstance(kpsoi, ia.KeypointsOnImage)
for kpsoi
in kpsois]) # assert original list not changed
assert kpsois_recovered == [kpsois[0], "foo1", kpsois[2]]
kpsois_recovered = iaa.invert_reduce_to_nonempty(kpsois, [], [])
assert kpsois_recovered == [kpsois[0], kpsois[1], kpsois[2]]
kpsois_recovered = iaa.invert_reduce_to_nonempty([], [], [])
assert kpsois_recovered == []
class _DummyAugmenter(iaa.Augmenter):
def _augment_images(self, images, random_state, parents, hooks):
return images
def get_parameters(self):
return []
class _DummyAugmenterBBs(iaa.Augmenter):
def _augment_images(self, images, random_state, parents, hooks):
return images
def _augment_bounding_boxes(self, bounding_boxes_on_images, random_state,
parents, hooks):
return [bbsoi.shift(x=1)
for bbsoi
in bounding_boxes_on_images]
def get_parameters(self):
return []
# TODO remove _augment_heatmaps() and _augment_keypoints() here once they are
# no longer abstract methods but default to noop
class _DummyAugmenterCallsParent(iaa.Augmenter):
def _augment_images(self, images, random_state, parents, hooks):
return super(_DummyAugmenterCallsParent, self)\
._augment_images(images, random_state, parents, hooks)
def get_parameters(self):
return super(_DummyAugmenterCallsParent, self)\
.get_parameters()
def _same_rs(rs1, rs2):
return rs1.equals(rs2)
# TODO the test in here do not check everything, but instead only the cases
# that were not yet indirectly tested via other tests
class TestAugmenter(unittest.TestCase):
def setUp(self):
reseed()
def test___init___global_rng(self):
aug = _DummyAugmenter()
assert not aug.deterministic
assert aug.random_state.is_global_rng()
def test___init___deterministic(self):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
aug = _DummyAugmenter(deterministic=True)
assert aug.deterministic
assert not aug.random_state.is_global_rng()
assert len(caught_warnings) == 1
assert (
"is deprecated"
in str(caught_warnings[-1].message))
# old name for parameter `seed`
def test___init___random_state_is_rng(self):
rs = iarandom.RNG(123)
aug = _DummyAugmenter(seed=rs)
assert aug.random_state.generator is rs.generator
# old name for parameter `seed`
def test___init___random_state_is_seed(self):
aug = _DummyAugmenter(seed=123)
assert aug.random_state.equals(iarandom.RNG(123))
def test___init___seed_is_random_state(self):
rs = iarandom.RNG(123)
aug = _DummyAugmenter(seed=rs)
assert aug.random_state.generator is rs.generator
def test___init___seed_is_seed(self):
aug = _DummyAugmenter(seed=123)
assert aug.random_state.equals(iarandom.RNG(123))
def test_augment_images_called_probably_with_single_image(self):
aug = _DummyAugmenter()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = aug.augment_images(np.zeros((16, 32, 3), dtype=np.uint8))
assert len(caught_warnings) == 1
assert (
"indicates that you provided a single image with shape (H, W, C)"
in str(caught_warnings[-1].message)
)
def test_augment_images_array_in_list_out(self):
self._test_augment_images_array_in_list_out_varying_channels(
[3] * 20)
def test_augment_images_array_in_list_out_single_channel(self):
self._test_augment_images_array_in_list_out_varying_channels(
[1] * 20)
def test_augment_images_array_in_list_out_no_channels(self):
self._test_augment_images_array_in_list_out_varying_channels(
[None] * 20)
def test_augment_images_array_in_list_out_varying_channels(self):
self._test_augment_images_array_in_list_out_varying_channels(
["random"] * 20)
@classmethod
def _test_augment_images_array_in_list_out_varying_channels(cls,
nb_channels):
assert len(nb_channels) == 20
aug = iaa.Crop(((1, 8), (1, 8), (1, 8), (1, 8)), keep_size=False)
seen = [0, 0]
for nb_channels_i in nb_channels:
if nb_channels_i == "random":
channels = np.random.choice([None, 1, 3, 4, 9], size=(16,))
elif nb_channels_i is None:
channels = np.random.choice([None], size=(16,))
else:
channels = np.random.choice([nb_channels_i], size=(16,))
images = [np.zeros((64, 64), dtype=np.uint8)
if c is None
else np.zeros((64, 64, c), dtype=np.uint8)
for c in channels]
if nb_channels_i != "random":
images = np.array(images)
observed = aug.augment_images(images)
if ia.is_np_array(observed):
seen[0] += 1
else:
seen[1] += 1
for image, c in zip(observed, channels):
if c is None:
assert image.ndim == 2
else:
assert image.ndim == 3
assert image.shape[2] == c
assert 48 <= image.shape[0] <= 62
assert 48 <= image.shape[1] <= 62
assert seen[0] <= 3
assert seen[1] >= 17
def test_augment_images_with_2d_inputs(self):
base_img1 = np.array([[0, 0, 1, 1],
[0, 0, 1, 1],
[0, 1, 1, 1]], dtype=np.uint8)
base_img2 = np.array([[0, 0, 1, 1],
[0, 1, 1, 1],
[0, 1, 0, 0]], dtype=np.uint8)
base_img1_flipped = np.array([[1, 1, 0, 0],
[1, 1, 0, 0],
[1, 1, 1, 0]], dtype=np.uint8)
base_img2_flipped = np.array([[1, 1, 0, 0],
[1, 1, 1, 0],
[0, 0, 1, 0]], dtype=np.uint8)
images = np.array([base_img1, base_img2])
images_flipped = np.array([base_img1_flipped, base_img2_flipped])
images_list = [base_img1, base_img2]
images_flipped_list = [base_img1_flipped, base_img2_flipped]
images_list2d3d = [base_img1, base_img2[:, :, np.newaxis]]
images_flipped_list2d3d = [
base_img1_flipped,
base_img2_flipped[:, :, np.newaxis]]
aug = iaa.Fliplr(1.0)
noaug = iaa.Fliplr(0.0)
# one numpy array as input
observed = aug.augment_images(images)
assert np.array_equal(observed, images_flipped)
observed = noaug.augment_images(images)
assert np.array_equal(observed, images)
# list of 2d images
observed = aug.augment_images(images_list)
assert array_equal_lists(observed, images_flipped_list)
observed = noaug.augment_images(images_list)
assert array_equal_lists(observed, images_list)
# list of images, one 2d and one 3d
observed = aug.augment_images(images_list2d3d)
assert array_equal_lists(observed, images_flipped_list2d3d)
observed = noaug.augment_images(images_list2d3d)
assert array_equal_lists(observed, images_list2d3d)
def test_augment_keypoints_single_instance(self):
kpsoi = ia.KeypointsOnImage([ia.Keypoint(10, 10)], shape=(32, 32, 3))
aug = iaa.Affine(translate_px={"x": 1})
kpsoi_aug = aug.augment_keypoints(kpsoi)
assert len(kpsoi_aug.keypoints) == 1
assert kpsoi_aug.keypoints[0].x == 11
def test_augment_keypoints_single_instance_rot90(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=2, y=5),
ia.Keypoint(x=3, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(5, 10, 3))
aug = iaa.Rot90(1, keep_size=False)
kpsoi_aug = aug.augment_keypoints(kpsoi)
# set offset to -1 if Rot90 uses int-based coordinate transformation
kp_offset = 0
assert np.allclose(kpsoi_aug.keypoints[0].x, 5 - 2 + kp_offset)
assert np.allclose(kpsoi_aug.keypoints[0].y, 1)
assert np.allclose(kpsoi_aug.keypoints[1].x, 5 - 5 + kp_offset)
assert np.allclose(kpsoi_aug.keypoints[1].y, 2)
assert np.allclose(kpsoi_aug.keypoints[2].x, 5 - 3 + kp_offset)
assert np.allclose(kpsoi_aug.keypoints[2].y, 3)
def test_augment_keypoints_many_instances_rot90(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=2, y=5),
ia.Keypoint(x=3, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(5, 10, 3))
aug = iaa.Rot90(1, keep_size=False)
kpsoi_aug = aug.augment_keypoints([kpsoi, kpsoi, kpsoi])
# set offset to -1 if Rot90 uses int-based coordinate transformation
kp_offset = 0
for i in range(3):
assert np.allclose(kpsoi_aug[i].keypoints[0].x, 5 - 2 + kp_offset)
assert np.allclose(kpsoi_aug[i].keypoints[0].y, 1)
assert np.allclose(kpsoi_aug[i].keypoints[1].x, 5 - 5 + kp_offset)
assert np.allclose(kpsoi_aug[i].keypoints[1].y, 2)
assert np.allclose(kpsoi_aug[i].keypoints[2].x, 5 - 3 + kp_offset)
assert np.allclose(kpsoi_aug[i].keypoints[2].y, 3)
def test_augment_keypoints_empty_instance(self):
# test empty KeypointsOnImage objects
kpsoi = ia.KeypointsOnImage([], shape=(32, 32, 3))
aug = iaa.Affine(translate_px={"x": 1})
kpsoi_aug = aug.augment_keypoints([kpsoi])
assert len(kpsoi_aug) == 1
assert len(kpsoi_aug[0].keypoints) == 0
def test_augment_keypoints_mixed_filled_and_empty_instances(self):
kpsoi1 = ia.KeypointsOnImage([], shape=(32, 32, 3))
kpsoi2 = ia.KeypointsOnImage([ia.Keypoint(10, 10)], shape=(32, 32, 3))
aug = iaa.Affine(translate_px={"x": 1})
kpsoi_aug = aug.augment_keypoints([kpsoi1, kpsoi2])
assert len(kpsoi_aug) == 2
assert len(kpsoi_aug[0].keypoints) == 0
assert len(kpsoi_aug[1].keypoints) == 1
assert kpsoi_aug[1].keypoints[0].x == 11
def test_augment_keypoints_aligned_despite_empty_instance(self):
# Test if augmenting lists of KeypointsOnImage is still aligned with
# image augmentation when one KeypointsOnImage instance is empty
# (no keypoints)
kpsoi_lst = [
ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([ia.Keypoint(x=1, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([], shape=(1, 8)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([ia.Keypoint(x=1, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 10)),
ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 10))
]
image = np.zeros((1, 10), dtype=np.uint8)
image[0, 0] = 255
images = np.tile(image[np.newaxis, :, :], (len(kpsoi_lst), 1, 1))
aug = iaa.Affine(translate_px={"x": (0, 8)}, order=0, mode="constant",
cval=0)
for i in sm.xrange(10):
for is_list in [False, True]:
with self.subTest(i=i, is_list=is_list):
aug_det = aug.to_deterministic()
if is_list:
images_aug = aug_det.augment_images(list(images))
else:
images_aug = aug_det.augment_images(images)
kpsoi_lst_aug = aug_det.augment_keypoints(kpsoi_lst)
if is_list:
images_aug = np.array(images_aug, dtype=np.uint8)
translations_imgs = np.argmax(images_aug[:, 0, :], axis=1)
translations_kps = [
kpsoi.keypoints[0].x
if len(kpsoi.keypoints) > 0
else None
for kpsoi
in kpsoi_lst_aug]
assert len([kpresult
for kpresult
in translations_kps
if kpresult is None]) == 1
assert translations_kps[5] is None
translations_imgs = np.concatenate(
[translations_imgs[0:5], translations_imgs[6:]])
translations_kps = np.array(
translations_kps[0:5] + translations_kps[6:],
dtype=translations_imgs.dtype)
translations_kps[2] -= 1
translations_kps[8-1] -= 1
assert np.array_equal(translations_imgs, translations_kps)
def test_augment_keypoints_aligned_despite_nongeometric_image_ops(self):
# Verify for keypoints that adding augmentations that only
# affect images doesn't lead to misalignments between image
# and keypoint transformations
augs = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.AdditiveGaussianNoise(scale=(0.01, 0.1)),
iaa.Affine(translate_px={"x": (-10, 10), "y": (-10, 10)},
order=0, mode="constant", cval=0),
iaa.AddElementwise((0, 1)),
iaa.Flipud(0.5)
], random_order=True)
kps = [ia.Keypoint(x=15.5, y=12.5), ia.Keypoint(x=23.5, y=20.5),
ia.Keypoint(x=61.5, y=36.5), ia.Keypoint(x=47.5, y=32.5)]
kpsoi = ia.KeypointsOnImage(kps, shape=(50, 80, 4))
image = kpsoi.to_keypoint_image(size=1)
images = np.tile(image[np.newaxis, ...], (20, 1, 1, 1))
for _ in sm.xrange(50):
images_aug, kpsois_aug = augs(images=images,
keypoints=[kpsoi]*len(images))
for image_aug, kpsoi_aug in zip(images_aug, kpsois_aug):
kpsoi_recovered = ia.KeypointsOnImage.from_keypoint_image(
image_aug, nb_channels=4, threshold=100
)
for kp, kp_image in zip(kpsoi_aug.keypoints,
kpsoi_recovered.keypoints):
distance = np.sqrt((kp.x - kp_image.x)**2
+ (kp.y - kp_image.y)**2)
assert distance <= 1
def test_augment_bounding_boxes(self):
aug = _DummyAugmenterBBs()
bb = ia.BoundingBox(x1=1, y1=4, x2=2, y2=5)
bbs = [bb]
bbsois = [ia.BoundingBoxesOnImage(bbs, shape=(10, 10, 3))]
bbsois_aug = aug.augment_bounding_boxes(bbsois)
bb_aug = bbsois_aug[0].bounding_boxes[0]
assert bb_aug.x1 == 1+1
assert bb_aug.y1 == 4
assert bb_aug.x2 == 2+1
assert bb_aug.y2 == 5
def test_augment_bounding_boxes_empty_bboi(self):
aug = _DummyAugmenterBBs()
bbsois = [ia.BoundingBoxesOnImage([], shape=(10, 10, 3))]
bbsois_aug = aug.augment_bounding_boxes(bbsois)
assert len(bbsois_aug) == 1
assert bbsois_aug[0].bounding_boxes == []
def test_augment_bounding_boxes_empty_list(self):
aug = _DummyAugmenterBBs()
bbsois_aug = aug.augment_bounding_boxes([])
assert bbsois_aug == []
def test_augment_bounding_boxes_single_instance(self):
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=1, x2=3, y1=4, y2=5),
ia.BoundingBox(x1=2.5, x2=3, y1=0, y2=2)
], shape=(5, 10, 3))
aug = iaa.Identity()
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
for bb_aug, bb in zip(bbsoi_aug.bounding_boxes, bbsoi.bounding_boxes):
assert np.allclose(bb_aug.x1, bb.x1)
assert np.allclose(bb_aug.x2, bb.x2)
assert np.allclose(bb_aug.y1, bb.y1)
assert np.allclose(bb_aug.y2, bb.y2)
def test_augment_bounding_boxes_single_instance_rot90(self):
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=1, x2=3, y1=4, y2=5),
ia.BoundingBox(x1=2.5, x2=3, y1=0, y2=2)
], shape=(5, 10, 3))
aug = iaa.Rot90(1, keep_size=False)
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
# set offset to -1 if Rot90 uses int-based coordinate transformation
kp_offset = 0
# Note here that the new coordinates are minima/maxima of the BB, so
# not as straight forward to compute the new coords as for keypoint
# augmentation
bb0 = bbsoi_aug.bounding_boxes[0]
bb1 = bbsoi_aug.bounding_boxes[1]
assert np.allclose(bb0.x1, 5 - 5 + kp_offset)
assert np.allclose(bb0.x2, 5 - 4 + kp_offset)
assert np.allclose(bb0.y1, 1)
assert np.allclose(bb0.y2, 3)
assert np.allclose(bb1.x1, 5 - 2 + kp_offset)
assert np.allclose(bb1.x2, 5 - 0 + kp_offset)
assert np.allclose(bb1.y1, 2.5)
assert np.allclose(bb1.y2, 3)
def test_augment_bounding_box_list_of_many_instances(self):
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=1, x2=3, y1=4, y2=5),
ia.BoundingBox(x1=2.5, x2=3, y1=0, y2=2)
], shape=(5, 10, 3))
aug = iaa.Rot90(1, keep_size=False)
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi, bbsoi])
# set offset to -1 if Rot90 uses int-based coordinate transformation
kp_offset = 0
for i in range(3):
bb0 = bbsoi_aug[i].bounding_boxes[0]
bb1 = bbsoi_aug[i].bounding_boxes[1]
assert np.allclose(bb0.x1, 5 - 5 + kp_offset)
assert np.allclose(bb0.x2, 5 - 4 + kp_offset)
assert np.allclose(bb0.y1, 1)
assert np.allclose(bb0.y2, 3)
assert np.allclose(bb1.x1, 5 - 2 + kp_offset)
assert np.allclose(bb1.x2, 5 - 0 + kp_offset)
assert np.allclose(bb1.y1, 2.5)
assert np.allclose(bb1.y2, 3)
def test_augment_heatmaps_noop_single_heatmap(self):
heatmap_arr = np.linspace(0.0, 1.0, num=4*4).reshape((4, 4, 1))
heatmap = ia.HeatmapsOnImage(heatmap_arr.astype(np.float32),
shape=(4, 4, 3))
aug = iaa.Identity()
heatmap_aug = aug.augment_heatmaps(heatmap)
assert np.allclose(heatmap_aug.arr_0to1, heatmap.arr_0to1)
def test_augment_heatmaps_rot90_single_heatmap(self):
heatmap_arr = np.linspace(0.0, 1.0, num=4*4).reshape((4, 4, 1))
heatmap = ia.HeatmapsOnImage(heatmap_arr.astype(np.float32),
shape=(4, 4, 3))
aug = iaa.Rot90(1, keep_size=False)
heatmap_aug = aug.augment_heatmaps(heatmap)
assert np.allclose(heatmap_aug.arr_0to1, np.rot90(heatmap.arr_0to1, -1))
def test_augment_heatmaps_rot90_list_of_many_heatmaps(self):
heatmap_arr = np.linspace(0.0, 1.0, num=4*4).reshape((4, 4, 1))
heatmap = ia.HeatmapsOnImage(heatmap_arr.astype(np.float32),
shape=(4, 4, 3))
aug = iaa.Rot90(1, keep_size=False)
heatmaps_aug = aug.augment_heatmaps([heatmap] * 3)
for hm in heatmaps_aug:
assert np.allclose(hm.arr_0to1, np.rot90(heatmap.arr_0to1, -1))
def test_legacy_fallback_to_kp_aug_for_cbaois(self):
class _LegacyAugmenter(iaa.Augmenter):
def _augment_keypoints(self, keypoints_on_images, random_state,
parents, hooks):
return [kpsoi.shift(x=1) for kpsoi in keypoints_on_images]
def get_parameters(self):
return []
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)
], shape=(4, 5, 3))
psoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (1, 0), (1, 1)])
], shape=(4, 5, 3))
lsoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (1, 0), (1, 1)])
], shape=(4, 5, 3))
aug = _LegacyAugmenter()
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
psoi_aug = aug.augment_polygons(psoi)
lsoi_aug = aug.augment_line_strings(lsoi)
assert bbsoi_aug[0].coords_almost_equals(bbsoi[0].shift(x=1))
assert psoi_aug[0].coords_almost_equals(psoi[0].shift(x=1))
assert lsoi_aug[0].coords_almost_equals(lsoi[0].shift(x=1))
def test_localize_random_state(self):
aug = _DummyAugmenter()
aug_localized = aug.localize_random_state()
assert aug_localized is not aug
assert aug.random_state.is_global_rng()
assert not aug_localized.random_state.is_global_rng()
def test_seed_(self):
aug1 = _DummyAugmenter()
aug2 = _DummyAugmenter().to_deterministic()
aug0 = iaa.Sequential([aug1, aug2])
aug0_copy = aug0.deepcopy()
assert _same_rs(aug0.random_state, aug0_copy.random_state)
assert _same_rs(aug0[0].random_state, aug0_copy[0].random_state)
assert _same_rs(aug0[1].random_state, aug0_copy[1].random_state)
aug0_copy.seed_()
assert not _same_rs(aug0.random_state, aug0_copy.random_state)
assert not _same_rs(aug0[0].random_state, aug0_copy[0].random_state)
assert _same_rs(aug0[1].random_state, aug0_copy[1].random_state)
def test_seed__deterministic_too(self):
aug1 = _DummyAugmenter()
aug2 = _DummyAugmenter().to_deterministic()
aug0 = iaa.Sequential([aug1, aug2])
aug0_copy = aug0.deepcopy()
assert _same_rs(aug0.random_state, aug0_copy.random_state)
assert _same_rs(aug0[0].random_state, aug0_copy[0].random_state)
assert _same_rs(aug0[1].random_state, aug0_copy[1].random_state)
aug0_copy.seed_(deterministic_too=True)
assert not _same_rs(aug0.random_state, aug0_copy.random_state)
assert not _same_rs(aug0[0].random_state, aug0_copy[0].random_state)
assert not _same_rs(aug0[1].random_state, aug0_copy[1].random_state)
def test_seed__with_integer(self):
aug1 = _DummyAugmenter()
aug2 = _DummyAugmenter().to_deterministic()
aug0 = iaa.Sequential([aug1, aug2])
aug0_copy = aug0.deepcopy()
assert _same_rs(aug0.random_state, aug0_copy.random_state)
assert _same_rs(aug0[0].random_state, aug0_copy[0].random_state)
assert _same_rs(aug0[1].random_state, aug0_copy[1].random_state)
aug0_copy.seed_(123)
assert not _same_rs(aug0.random_state, aug0_copy.random_state)
assert not _same_rs(aug0[0].random_state, aug0_copy[0].random_state)
assert _same_rs(aug0_copy.random_state, iarandom.RNG(123))
expected = iarandom.RNG(123).derive_rng_()
assert _same_rs(aug0_copy[0].random_state, expected)
def test_seed__with_rng(self):
aug1 = _DummyAugmenter()
aug2 = _DummyAugmenter().to_deterministic()
aug0 = iaa.Sequential([aug1, aug2])
aug0_copy = aug0.deepcopy()
assert _same_rs(aug0.random_state, aug0_copy.random_state)
assert _same_rs(aug0[0].random_state, aug0_copy[0].random_state)
assert _same_rs(aug0[1].random_state, aug0_copy[1].random_state)
aug0_copy.seed_(iarandom.RNG(123))
assert not _same_rs(aug0.random_state, aug0_copy.random_state)
assert not _same_rs(aug0[0].random_state, aug0_copy[0].random_state)
assert _same_rs(aug0[1].random_state, aug0_copy[1].random_state)
assert _same_rs(aug0_copy.random_state,
iarandom.RNG(123))
expected = iarandom.RNG(123).derive_rng_()
assert _same_rs(aug0_copy[0].random_state, expected)
def test_get_parameters(self):
# test for "raise NotImplementedError"
aug = _DummyAugmenterCallsParent()
with self.assertRaises(NotImplementedError):
aug.get_parameters()
def test_get_all_children_flat(self):
aug1 = _DummyAugmenter()
aug21 = _DummyAugmenter()
aug2 = iaa.Sequential([aug21])
aug0 = iaa.Sequential([aug1, aug2])
children = aug0.get_all_children(flat=True)
assert isinstance(children, list)
assert children[0] == aug1
assert children[1] == aug2
assert children[2] == aug21
def test_get_all_children_not_flat(self):
aug1 = _DummyAugmenter()
aug21 = _DummyAugmenter()
aug2 = iaa.Sequential([aug21])
aug0 = iaa.Sequential([aug1, aug2])
children = aug0.get_all_children(flat=False)
assert isinstance(children, list)
assert children[0] == aug1
assert children[1] == aug2
assert isinstance(children[2], list)
assert children[2][0] == aug21
def test___repr___and___str__(self):
class DummyAugmenterRepr(iaa.Augmenter):
def _augment_images(self, images, random_state, parents, hooks):
return images
def _augment_heatmaps(self, heatmaps, random_state, parents, hooks):
return heatmaps
def _augment_keypoints(self, keypoints_on_images, random_state,
parents, hooks):
return keypoints_on_images
def get_parameters(self):
return ["A", "B", "C"]
aug1 = DummyAugmenterRepr(name="Example")
aug2 = DummyAugmenterRepr(name="Example").to_deterministic()
expected1 = (
"DummyAugmenterRepr("
"name=Example, parameters=[A, B, C], deterministic=False"
")")
expected2 = (
"DummyAugmenterRepr("
"name=Example, parameters=[A, B, C], deterministic=True"
")")
assert aug1.__repr__() == aug1.__str__() == expected1
assert aug2.__repr__() == aug2.__str__() == expected2
# -----------
# lambda functions used in Test TestAugmenter_augment_batches
# in test method test_augment_batches_with_many_different_augmenters().
# They are here instead of in the test method, because otherwise there were
# issues with spawn mode not being able to pickle functions,
# see issue #414.
def _augment_batches__lambda_func_images(
images, random_state, parents, hooks):
return images
def _augment_batches__lambda_func_keypoints(
keypoints_on_images, random_state, parents, hooks):
return keypoints_on_images
def _augment_batches__assertlambda_func_images(
images, random_state, parents, hooks):
return True
def _augment_batches__assertlambda_func_keypoints(
keypoints_on_images, random_state, parents, hooks):
return True
# -----------
class TestAugmenter_augment_batches(unittest.TestCase):
def setUp(self):
reseed()
def test_augment_batches_list_of_empty_list_deprecated(self):
with warnings.catch_warnings(record=True) as caught_warnings:
aug = _DummyAugmenter()
batches_aug = list(aug.augment_batches([[]]))
assert isinstance(batches_aug, list)
assert len(batches_aug) == 1
assert isinstance(batches_aug[0], list)
assert len(caught_warnings) == 1
assert "deprecated" in str(caught_warnings[-1].message)
def test_augment_batches_list_of_arrays_deprecated(self):
with warnings.catch_warnings(record=True) as caught_warnings:
aug = _DummyAugmenter()
image_batches = [np.zeros((1, 2, 2, 3), dtype=np.uint8)]
batches_aug = list(aug.augment_batches(image_batches))
assert isinstance(batches_aug, list)
assert len(batches_aug) == 1
assert array_equal_lists(batches_aug, image_batches)
assert len(caught_warnings) == 1
assert "deprecated" in str(caught_warnings[-1].message)
def test_augment_batches_list_of_list_of_arrays_deprecated(self):
with warnings.catch_warnings(record=True) as caught_warnings:
aug = _DummyAugmenter()
image_batches = [[np.zeros((2, 2, 3), dtype=np.uint8),
np.zeros((2, 3, 3))]]
batches_aug = list(aug.augment_batches(image_batches))
assert isinstance(batches_aug, list)
assert len(batches_aug) == 1
assert array_equal_lists(batches_aug[0], image_batches[0])
assert len(caught_warnings) == 1
assert "deprecated" in str(caught_warnings[-1].message)
def test_augment_batches_invalid_datatype(self):
aug = _DummyAugmenter()
with self.assertRaises(Exception):
_ = list(aug.augment_batches(None))
def test_augment_batches_list_of_invalid_datatype(self):
aug = _DummyAugmenter()
got_exception = False
try:
_ = list(aug.augment_batches([None]))
except Exception as exc:
got_exception = True
assert "Unknown datatype of batch" in str(exc)
assert got_exception
def test_augment_batches_list_of_list_of_invalid_datatype(self):
aug = _DummyAugmenter()
got_exception = False
try:
_ = list(aug.augment_batches([[None]]))
except Exception as exc:
got_exception = True
assert "Unknown datatype in batch[0]" in str(exc)
assert got_exception
def test_augment_batches_batch_with_list_of_images(self):
image = np.array([[0, 0, 1, 1, 1],
[0, 0, 1, 1, 1],
[0, 1, 1, 1, 1]], dtype=np.uint8)
image_flipped = np.fliplr(image)
keypoint = ia.Keypoint(x=2, y=1)
keypoints = [ia.KeypointsOnImage([keypoint], shape=image.shape + (1,))]
kp_flipped = ia.Keypoint(
x=image.shape[1]-keypoint.x,
y=keypoint.y
)
# basic functionality test (images as list)
for bg in [True, False]:
seq = iaa.Fliplr(1.0)
batches = [ia.Batch(images=[np.copy(image)], keypoints=keypoints)]
batches_aug = list(seq.augment_batches(batches, background=bg))
baug0 = batches_aug[0]
assert np.array_equal(baug0.images_aug[0], image_flipped)
assert baug0.keypoints_aug[0].keypoints[0].x == kp_flipped.x
assert baug0.keypoints_aug[0].keypoints[0].y == kp_flipped.y
assert np.array_equal(baug0.images_unaug[0], image)
assert baug0.keypoints_unaug[0].keypoints[0].x == keypoint.x
assert baug0.keypoints_unaug[0].keypoints[0].y == keypoint.y
def test_augment_batches_batch_with_array_of_images(self):
image = np.array([[0, 0, 1, 1, 1],
[0, 0, 1, 1, 1],
[0, 1, 1, 1, 1]], dtype=np.uint8)
image_flipped = np.fliplr(image)
keypoint = ia.Keypoint(x=2, y=1)
keypoints = [ia.KeypointsOnImage([keypoint], shape=image.shape + (1,))]
kp_flipped = ia.Keypoint(
x=image.shape[1]-keypoint.x,
y=keypoint.y
)
# basic functionality test (images as array)
for bg in [True, False]:
seq = iaa.Fliplr(1.0)
batches = [ia.Batch(images=np.uint8([np.copy(image)]),
keypoints=keypoints)]
batches_aug = list(seq.augment_batches(batches, background=bg))
baug0 = batches_aug[0]
assert np.array_equal(baug0.images_aug, np.uint8([image_flipped]))
assert baug0.keypoints_aug[0].keypoints[0].x == kp_flipped.x
assert baug0.keypoints_aug[0].keypoints[0].y == kp_flipped.y
assert np.array_equal(baug0.images_unaug, np.uint8([image]))
assert baug0.keypoints_unaug[0].keypoints[0].x == keypoint.x
assert baug0.keypoints_unaug[0].keypoints[0].y == keypoint.y
def test_augment_batches_background(self):
image = np.array([[0, 0, 1, 1, 1],
[0, 0, 1, 1, 1],
[0, 1, 1, 1, 1]], dtype=np.uint8)
image_flipped = np.fliplr(image)
kps = ia.Keypoint(x=2, y=1)
kpsoi = ia.KeypointsOnImage([kps], shape=image.shape + (1,))
kp_flipped = ia.Keypoint(
x=image.shape[1]-kps.x,
y=kps.y
)
seq = iaa.Fliplr(0.5)
for bg, as_array in itertools.product([False, True], [False, True]):
# with images as list
nb_flipped_images = 0
nb_flipped_keypoints = 0
nb_iterations = 1000
images = (
np.uint8([np.copy(image)])
if as_array
else [np.copy(image)])
batches = [
ia.Batch(images=images,
keypoints=[kpsoi.deepcopy()])
for _ in sm.xrange(nb_iterations)
]
batches_aug = list(seq.augment_batches(batches, background=bg))
for batch_aug in batches_aug:
image_aug = batch_aug.images_aug[0]
keypoint_aug = batch_aug.keypoints_aug[0].keypoints[0]
img_matches_unflipped = np.array_equal(image_aug, image)
img_matches_flipped = np.array_equal(image_aug, image_flipped)
assert img_matches_unflipped or img_matches_flipped
if img_matches_flipped:
nb_flipped_images += 1
kp_matches_unflipped = (
np.isclose(keypoint_aug.x, kps.x)
and np.isclose(keypoint_aug.y, kps.y))
kp_matches_flipped = (
np.isclose(keypoint_aug.x, kp_flipped.x)
and np.isclose(keypoint_aug.y, kp_flipped.y))
assert kp_matches_flipped or kp_matches_unflipped
if kp_matches_flipped:
nb_flipped_keypoints += 1
assert 0.4*nb_iterations <= nb_flipped_images <= 0.6*nb_iterations
assert nb_flipped_images == nb_flipped_keypoints
def test_augment_batches_with_many_different_augmenters(self):
image = np.array([[0, 0, 1, 1, 1],
[0, 0, 1, 1, 1],
[0, 1, 1, 1, 1]], dtype=np.uint8)
keypoint = ia.Keypoint(x=2, y=1)
keypoints = [ia.KeypointsOnImage([keypoint], shape=image.shape + (1,))]
augs = [
iaa.Sequential([iaa.Fliplr(1.0), iaa.Flipud(1.0)]),
iaa.SomeOf(1, [iaa.Fliplr(1.0), iaa.Flipud(1.0)]),
iaa.OneOf([iaa.Fliplr(1.0), iaa.Flipud(1.0)]),
iaa.Sometimes(1.0, iaa.Fliplr(1)),
iaa.WithColorspace("HSV", children=iaa.Add((-50, 50))),
iaa.WithChannels([0], iaa.Add((-50, 50))),
iaa.Identity(name="Identity-nochange"),
iaa.Lambda(
func_images=_augment_batches__lambda_func_images,
func_keypoints=_augment_batches__lambda_func_keypoints,
name="Lambda-nochange"
),
iaa.AssertLambda(
func_images=_augment_batches__assertlambda_func_images,
func_keypoints=_augment_batches__assertlambda_func_keypoints,
name="AssertLambda-nochange"
),
iaa.AssertShape(
(None, 64, 64, 3),
check_keypoints=False,
name="AssertShape-nochange"
),
iaa.Resize((0.5, 0.9)),
iaa.CropAndPad(px=(-50, 50)),
iaa.Pad(px=(1, 50)),
iaa.Crop(px=(1, 50)),
iaa.Fliplr(1.0),
iaa.Flipud(1.0),
iaa.Superpixels(p_replace=(0.25, 1.0), n_segments=(16, 128)),
iaa.ChangeColorspace(to_colorspace="GRAY"),
iaa.Grayscale(alpha=(0.1, 1.0)),
iaa.GaussianBlur(1.0),
iaa.AverageBlur(5),
iaa.MedianBlur(5),
iaa.Convolve(np.array([[0, 1, 0],
[1, -4, 1],
[0, 1, 0]])),
iaa.Sharpen(alpha=(0.1, 1.0), lightness=(0.8, 1.2)),
iaa.Emboss(alpha=(0.1, 1.0), strength=(0.8, 1.2)),
iaa.EdgeDetect(alpha=(0.1, 1.0)),
iaa.DirectedEdgeDetect(alpha=(0.1, 1.0), direction=(0.0, 1.0)),
iaa.Add((-50, 50)),
iaa.AddElementwise((-50, 50)),
iaa.AdditiveGaussianNoise(scale=(0.1, 1.0)),
iaa.Multiply((0.6, 1.4)),
iaa.MultiplyElementwise((0.6, 1.4)),
iaa.Dropout((0.3, 0.5)),
iaa.CoarseDropout((0.3, 0.5), size_percent=(0.05, 0.2)),
iaa.Invert(0.5),
iaa.Affine(
scale=(0.7, 1.3),
translate_percent=(-0.1, 0.1),
rotate=(-20, 20),
shear=(-20, 20),
order=ia.ALL,
mode=ia.ALL,
cval=(0, 255)),
iaa.PiecewiseAffine(scale=(0.1, 0.3)),
iaa.ElasticTransformation(alpha=2.0)
]
nb_iterations = 100
image = ia.data.quokka(size=(64, 64))
batches = [ia.Batch(images=[np.copy(image)],
keypoints=[keypoints[0].deepcopy()])
for _ in sm.xrange(nb_iterations)]
for aug in augs:
nb_changed = 0
batches_aug = list(aug.augment_batches(batches, background=True))
for batch_aug in batches_aug:
image_aug = batch_aug.images_aug[0]
if (image.shape != image_aug.shape
or not np.array_equal(image, image_aug)):
nb_changed += 1
if nb_changed > 10:
break
if "-nochange" not in aug.name:
assert nb_changed > 0
else:
assert nb_changed == 0
class TestAugmenter_augment_batch(unittest.TestCase):
def test_deprecation(self):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
aug = _InplaceDummyAugmenterImgsArray(1)
batch = ia.UnnormalizedBatch(
images=np.zeros((1, 1, 1, 3), dtype=np.uint8))
_batch_aug = aug.augment_batch(batch)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[0].message)
def test_augments_correctly_images(self):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
image = np.arange(10*20).astype(np.uint8).reshape((10, 20, 1))
image = np.tile(image, (1, 1, 3))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
images = image[np.newaxis, :, :, :]
image_cp = np.copy(image)
aug = _InplaceDummyAugmenterImgsArray(1)
batch = ia.UnnormalizedBatch(images=images)
batch_aug = aug.augment_batch(batch)
image_unaug = batch_aug.images_unaug[0, :, :, :]
image_aug = batch_aug.images_aug[0, :, :, :]
assert batch_aug is batch
assert batch_aug.images_aug is not batch.images_unaug
assert batch_aug.images_aug is not batch_aug.images_unaug
assert np.array_equal(image, image_cp)
assert np.array_equal(image_unaug, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
class TestAugmenter_augment_batch_(unittest.TestCase):
def setUp(self):
reseed()
def test_verify_inplace_aug__imgs__unnormalized_batch(self):
image = np.arange(10*20).astype(np.uint8).reshape((10, 20, 1))
image = np.tile(image, (1, 1, 3))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
images = image[np.newaxis, :, :, :]
image_cp = np.copy(image)
aug = _InplaceDummyAugmenterImgsArray(1)
batch = ia.UnnormalizedBatch(images=images)
batch_aug = aug.augment_batch_(batch)
image_unaug = batch_aug.images_unaug[0, :, :, :]
image_aug = batch_aug.images_aug[0, :, :, :]
assert batch_aug is batch
assert batch_aug.images_aug is not batch.images_unaug
assert batch_aug.images_aug is not batch_aug.images_unaug
assert np.array_equal(image, image_cp)
assert np.array_equal(image_unaug, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
def test_verify_inplace_aug__imgs__normalized_batch(self):
image = np.arange(10*20).astype(np.uint8).reshape((10, 20, 1))
image = np.tile(image, (1, 1, 3))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
images = image[np.newaxis, :, :, :]
image_cp = np.copy(image)
aug = _InplaceDummyAugmenterImgsArray(1)
batch = ia.Batch(images=images)
batch_aug = aug.augment_batch_(batch)
image_unaug = batch_aug.images_unaug[0, :, :, :]
image_aug = batch_aug.images_aug[0, :, :, :]
assert batch_aug is batch
assert batch_aug.images_aug is not batch.images_unaug
assert batch_aug.images_aug is not batch_aug.images_unaug
assert np.array_equal(image, image_cp)
assert np.array_equal(image_unaug, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
def test_verify_inplace_aug__imgs__batchinaug(self):
image = np.arange(10*20).astype(np.uint8).reshape((10, 20, 1))
image = np.tile(image, (1, 1, 3))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
images = image[np.newaxis, :, :, :]
image_cp = np.copy(image)
aug = _InplaceDummyAugmenterImgsArray(1)
batch = _BatchInAugmentation(images=images)
batch_aug = aug.augment_batch_(batch)
image_aug = batch_aug.images[0, :, :, :]
assert batch_aug is batch
assert batch_aug.images is batch.images
assert not np.array_equal(image, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
def test_verify_inplace_aug__segmaps__normalized_batch(self):
segmap_arr = np.zeros((10, 20, 3), dtype=np.int32)
segmap_arr[3:6, 3:9] = 1
segmap = ia.SegmentationMapsOnImage(segmap_arr, shape=(10, 20, 3))
segmap_cp = ia.SegmentationMapsOnImage(np.copy(segmap_arr),
shape=(10, 20, 3))
aug = _InplaceDummyAugmenterSegMaps(1)
batch = ia.Batch(segmentation_maps=[segmap])
batch_aug = aug.augment_batch_(batch)
segmap_unaug = batch_aug.segmentation_maps_unaug[0]
segmap_aug = batch_aug.segmentation_maps_aug[0]
assert batch_aug is batch
assert (batch_aug.segmentation_maps_aug
is not batch.segmentation_maps_unaug)
assert (batch_aug.segmentation_maps_aug
is not batch_aug.segmentation_maps_unaug)
assert np.array_equal(segmap.get_arr(), segmap_cp.get_arr())
assert np.array_equal(segmap_unaug.get_arr(), segmap_cp.get_arr())
assert np.array_equal(segmap_aug.get_arr(), segmap_cp.get_arr() + 1)
def test_verify_inplace_aug__keypoints_normalized_batch(self):
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)],
shape=(10, 20, 3))
kpsoi_cp = ia.KeypointsOnImage([ia.Keypoint(x=1, y=2)],
shape=(10, 20, 3))
aug = _InplaceDummyAugmenterKeypoints(x=1, y=3)
batch = ia.Batch(keypoints=[kpsoi])
batch_aug = aug.augment_batch_(batch)
kpsoi_unaug = batch_aug.keypoints_unaug[0]
kpsoi_aug = batch_aug.keypoints_aug[0]
assert batch_aug is batch
assert (batch_aug.keypoints_aug
is not batch.keypoints_unaug)
assert (batch_aug.keypoints_aug
is not batch_aug.keypoints_unaug)
assert np.allclose(kpsoi.to_xy_array(), kpsoi_cp.to_xy_array())
assert np.allclose(kpsoi_unaug.to_xy_array(), kpsoi_cp.to_xy_array())
assert np.allclose(kpsoi_aug.to_xy_array()[:, 0],
kpsoi_cp.to_xy_array()[:, 0] + 1)
assert np.allclose(kpsoi_aug.to_xy_array()[:, 1],
kpsoi_cp.to_xy_array()[:, 1] + 3)
def test_call_changes_global_rng_state(self):
state_before = copy.deepcopy(iarandom.get_global_rng().state)
aug = iaa.Rot90(k=(0, 3))
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
batch = ia.UnnormalizedBatch(images=[image])
_batch_aug = aug.augment_batch_(batch)
state_after = iarandom.get_global_rng().state
assert repr(state_before) != repr(state_after)
def test_multiple_calls_produce_not_the_same_results(self):
aug = iaa.Rot90(k=(0, 3))
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
nb_images = 1000
batch1 = ia.UnnormalizedBatch(images=[image] * nb_images)
batch2 = ia.UnnormalizedBatch(images=[image] * nb_images)
batch3 = ia.UnnormalizedBatch(images=[image] * nb_images)
batch_aug1 = aug.augment_batch_(batch1)
batch_aug2 = aug.augment_batch_(batch2)
batch_aug3 = aug.augment_batch_(batch3)
assert batch_aug1 is not batch_aug2
assert batch_aug1 is not batch_aug2
assert batch_aug2 is not batch_aug3
nb_equal = [0, 0, 0]
for image_aug1, image_aug2, image_aug3 in zip(batch_aug1.images_aug,
batch_aug2.images_aug,
batch_aug3.images_aug):
nb_equal[0] += int(np.array_equal(image_aug1, image_aug2))
nb_equal[1] += int(np.array_equal(image_aug1, image_aug3))
nb_equal[2] += int(np.array_equal(image_aug2, image_aug3))
assert nb_equal[0] < (0.25 + 0.1) * nb_images
assert nb_equal[1] < (0.25 + 0.1) * nb_images
assert nb_equal[2] < (0.25 + 0.1) * nb_images
def test_calls_affect_other_augmenters_with_global_rng(self):
# with calling aug1
iarandom.seed(1)
aug1 = iaa.Rot90(k=(0, 3))
aug2 = iaa.Add((0, 255))
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
nb_images = 50
batch1 = ia.UnnormalizedBatch(images=[image] * 1)
batch2 = ia.UnnormalizedBatch(images=[image] * nb_images)
batch_aug11 = aug1.augment_batch_(batch1)
batch_aug12 = aug2.augment_batch_(batch2)
# with calling aug1, repetition (to see that seed() works)
iarandom.seed(1)
aug1 = iaa.Rot90(k=(0, 3))
aug2 = iaa.Add((0, 255))
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
nb_images = 50
batch1 = ia.UnnormalizedBatch(images=[image] * 1)
batch2 = ia.UnnormalizedBatch(images=[image] * nb_images)
batch_aug21 = aug1.augment_batch_(batch1)
batch_aug22 = aug2.augment_batch_(batch2)
# without calling aug1
iarandom.seed(1)
aug2 = iaa.Add((0, 255))
image = np.arange(4*4).astype(np.uint8).reshape((4, 4))
nb_images = 50
batch2 = ia.UnnormalizedBatch(images=[image] * nb_images)
batch_aug32 = aug2.augment_batch_(batch2)
# comparison
assert np.array_equal(
np.array(batch_aug12.images_aug, dtype=np.uint8),
np.array(batch_aug22.images_aug, dtype=np.uint8)
)
assert not np.array_equal(
np.array(batch_aug12.images_aug, dtype=np.uint8),
np.array(batch_aug32.images_aug, dtype=np.uint8)
)
class TestAugmenter_augment_segmentation_maps(unittest.TestCase):
def setUp(self):
reseed()
def test_augment_segmentation_maps_single_instance(self):
arr = np.int32([
[0, 1, 1],
[0, 1, 1],
[0, 1, 1]
])
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
aug = iaa.Identity()
segmap_aug = aug.augment_segmentation_maps(segmap)
assert np.array_equal(segmap_aug.arr, segmap.arr)
def test_augment_segmentation_maps_list_of_single_instance(self):
arr = np.int32([
[0, 1, 1],
[0, 1, 1],
[0, 1, 1]
])
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
aug = iaa.Identity()
segmap_aug = aug.augment_segmentation_maps([segmap])[0]
assert np.array_equal(segmap_aug.arr, segmap.arr)
def test_augment_segmentation_maps_affine(self):
arr = np.int32([
[0, 1, 1],
[0, 1, 1],
[0, 1, 1]
])
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
aug = iaa.Affine(translate_px={"x": 1})
segmap_aug = aug.augment_segmentation_maps(segmap)
expected = np.int32([
[0, 0, 1],
[0, 0, 1],
[0, 0, 1]
])
expected = expected[:, :, np.newaxis]
assert np.array_equal(segmap_aug.arr, expected)
def test_augment_segmentation_maps_pad(self):
arr = np.int32([
[0, 1, 1],
[0, 1, 1],
[0, 1, 1]
])
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
aug = iaa.Pad(px=(1, 0, 0, 0), keep_size=False)
segmap_aug = aug.augment_segmentation_maps(segmap)
expected = np.int32([
[0, 0, 0],
[0, 1, 1],
[0, 1, 1],
[0, 1, 1]
])
expected = expected[:, :, np.newaxis]
assert np.array_equal(segmap_aug.arr, expected)
def test_augment_segmentation_maps_pad_some_classes_not_provided(self):
# only classes 0 and 3
arr = np.int32([
[0, 3, 3],
[0, 3, 3],
[0, 3, 3]
])
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
aug = iaa.Pad(px=(1, 0, 0, 0), keep_size=False)
segmap_aug = aug.augment_segmentation_maps(segmap)
expected = np.int32([
[0, 0, 0],
[0, 3, 3],
[0, 3, 3],
[0, 3, 3]
])
expected = expected[:, :, np.newaxis]
assert np.array_equal(segmap_aug.arr, expected)
def test_augment_segmentation_maps_pad_only_background_class(self):
# only class 0
arr = np.int32([
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
])
segmap = ia.SegmentationMapsOnImage(arr, shape=(3, 3))
aug = iaa.Pad(px=(1, 0, 0, 0), keep_size=False)
segmap_aug = aug.augment_segmentation_maps(segmap)
expected = np.int32([
[0, 0, 0],
[0, 0, 0],
[0, 0, 0],
[0, 0, 0]
])
expected = expected[:, :, np.newaxis]
assert np.array_equal(segmap_aug.arr, expected)
def test_augment_segmentation_maps_multichannel_rot90(self):
segmap = ia.SegmentationMapsOnImage(
np.arange(0, 4*4).reshape((4, 4, 1)).astype(np.int32),
shape=(4, 4, 3)
)
aug = iaa.Rot90(1, keep_size=False)
segmaps_aug = aug.augment_segmentation_maps([segmap, segmap, segmap])
for i in range(3):
assert np.allclose(segmaps_aug[i].arr, np.rot90(segmap.arr, -1))
class TestAugmenter_draw_grid(unittest.TestCase):
def setUp(self):
reseed()
def test_draw_grid_list_of_3d_arrays(self):
# list, shape (3, 3, 3)
aug = _DummyAugmenter()
image = np.zeros((3, 3, 3), dtype=np.uint8)
image[0, 0, :] = 10
image[0, 1, :] = 50
image[1, 1, :] = 255
grid = aug.draw_grid([image], rows=2, cols=2)
grid_expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert np.array_equal(grid, grid_expected)
def test_draw_grid_list_of_2d_arrays(self):
# list, shape (3, 3)
aug = _DummyAugmenter()
image = np.zeros((3, 3, 3), dtype=np.uint8)
image[0, 0, :] = 10
image[0, 1, :] = 50
image[1, 1, :] = 255
grid = aug.draw_grid([image[..., 0]], rows=2, cols=2)
grid_expected = np.vstack([
np.hstack([image[..., 0:1], image[..., 0:1]]),
np.hstack([image[..., 0:1], image[..., 0:1]])
])
grid_expected = np.tile(grid_expected, (1, 1, 3))
assert np.array_equal(grid, grid_expected)
def test_draw_grid_list_of_1d_arrays_fails(self):
# list, shape (2,)
aug = _DummyAugmenter()
with self.assertRaises(Exception):
_ = aug.draw_grid([np.zeros((2,), dtype=np.uint8)], rows=2, cols=2)
def test_draw_grid_4d_array(self):
# array, shape (1, 3, 3, 3)
aug = _DummyAugmenter()
image = np.zeros((3, 3, 3), dtype=np.uint8)
image[0, 0, :] = 10
image[0, 1, :] = 50
image[1, 1, :] = 255
grid = aug.draw_grid(np.uint8([image]), rows=2, cols=2)
grid_expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert np.array_equal(grid, grid_expected)
def test_draw_grid_3d_array(self):
# array, shape (3, 3, 3)
aug = _DummyAugmenter()
image = np.zeros((3, 3, 3), dtype=np.uint8)
image[0, 0, :] = 10
image[0, 1, :] = 50
image[1, 1, :] = 255
grid = aug.draw_grid(image, rows=2, cols=2)
grid_expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert np.array_equal(grid, grid_expected)
def test_draw_grid_2d_array(self):
# array, shape (3, 3)
aug = _DummyAugmenter()
image = np.zeros((3, 3, 3), dtype=np.uint8)
image[0, 0, :] = 10
image[0, 1, :] = 50
image[1, 1, :] = 255
grid = aug.draw_grid(image[..., 0], rows=2, cols=2)
grid_expected = np.vstack([
np.hstack([image[..., 0:1], image[..., 0:1]]),
np.hstack([image[..., 0:1], image[..., 0:1]])
])
grid_expected = np.tile(grid_expected, (1, 1, 3))
assert np.array_equal(grid, grid_expected)
def test_draw_grid_1d_array(self):
# array, shape (2,)
aug = _DummyAugmenter()
with self.assertRaises(Exception):
_ = aug.draw_grid(np.zeros((2,), dtype=np.uint8), rows=2, cols=2)
@six.add_metaclass(ABCMeta)
class _TestAugmenter_augment_cbaois(object):
"""Class that is used to test augment_polygons() and augment_line_strings().
Originally this was only used for polygons and then made more flexible.
This is why some descriptions are still geared towards polygons.
Abbreviations:
cba = coordinate based augmentable, e.g. Polygon
cbaoi = coordinate based augmentable on image, e.g. PolygonsOnImage
"""
def setUp(self):
reseed()
@abstractmethod
def _augfunc(self, augmenter, *args, **kwargs):
"""Return augmenter.augment_*(...)."""
@property
@abstractmethod
def _ObjClass(self):
"""Return Polygon, LineString or similar class."""
@property
@abstractmethod
def _ObjOnImageClass(self):
"""Return PolygonsOnImage, LineStringsOnImage or similar class."""
def _Obj(self, *args, **kwargs):
return self._ObjClass(*args, **kwargs)
def _ObjOnImage(self, *args, **kwargs):
return self._ObjOnImageClass(*args, **kwargs)
def _compare_coords_of_cba(self, observed, expected, atol=1e-4, rtol=0):
return np.allclose(observed, expected, atol=atol, rtol=rtol)
def test_single_empty_instance(self):
# single instance of PolygonsOnImage with 0 polygons
aug = iaa.Rot90(1, keep_size=False)
cbaoi = self._ObjOnImage([], shape=(10, 11, 3))
cbaoi_aug = self._augfunc(aug, cbaoi)
assert isinstance(cbaoi_aug, self._ObjOnImageClass)
assert cbaoi_aug.empty
assert cbaoi_aug.shape == (11, 10, 3)
def test_list_of_single_empty_instance(self):
# list of PolygonsOnImage with 0 polygons
aug = iaa.Rot90(1, keep_size=False)
cbaoi = self._ObjOnImage([], shape=(10, 11, 3))
cbaois_aug = self._augfunc(aug, [cbaoi])
assert isinstance(cbaois_aug, list)
assert isinstance(cbaois_aug[0], self._ObjOnImageClass)
assert cbaois_aug[0].empty
assert cbaois_aug[0].shape == (11, 10, 3)
def test_two_cbaois_each_two_cbas(self):
# 2 PolygonsOnImage, each 2 polygons
aug = iaa.Rot90(1, keep_size=False)
cbaois = [
self._ObjOnImage(
[self._Obj([(0, 0), (5, 0), (5, 5)]),
self._Obj([(1, 1), (6, 1), (6, 6)])],
shape=(10, 10, 3)),
self._ObjOnImage(
[self._Obj([(2, 2), (7, 2), (7, 7)]),
self._Obj([(3, 3), (8, 3), (8, 8)])],
shape=(10, 10, 3)),
]
cbaois_aug = self._augfunc(aug, cbaois)
assert isinstance(cbaois_aug, list)
assert isinstance(cbaois_aug[0], self._ObjOnImageClass)
assert isinstance(cbaois_aug[0], self._ObjOnImageClass)
assert len(cbaois_aug[0].items) == 2
assert len(cbaois_aug[1].items) == 2
kp_offset = 0
assert self._compare_coords_of_cba(
cbaois_aug[0].items[0].coords,
[(10-0+kp_offset, 0), (10-0+kp_offset, 5), (10-5+kp_offset, 5)],
atol=1e-4, rtol=0
)
assert self._compare_coords_of_cba(
cbaois_aug[0].items[1].coords,
[(10-1+kp_offset, 1), (10-1+kp_offset, 6), (10-6+kp_offset, 6)],
atol=1e-4, rtol=0
)
assert self._compare_coords_of_cba(
cbaois_aug[1].items[0].coords,
[(10-2+kp_offset, 2), (10-2+kp_offset, 7), (10-7+kp_offset, 7)],
atol=1e-4, rtol=0
)
assert self._compare_coords_of_cba(
cbaois_aug[1].items[1].coords,
[(10-3+kp_offset, 3), (10-3+kp_offset, 8), (10-8+kp_offset, 8)],
atol=1e-4, rtol=0
)
assert cbaois_aug[0].shape == (10, 10, 3)
assert cbaois_aug[1].shape == (10, 10, 3)
def test_randomness_between_and_within_batches(self):
# test whether there is randomness within each batch and between
# batches
aug = iaa.Rot90((0, 3), keep_size=False)
cba = self._Obj([(0, 0), (5, 0), (5, 5)])
cbaoi = self._ObjOnImage(
[cba.deepcopy() for _ in sm.xrange(1)],
shape=(10, 11, 3)
)
cbaois = [cbaoi.deepcopy() for _ in sm.xrange(100)]
cbaois_aug1 = self._augfunc(aug, cbaois)
cbaois_aug2 = self._augfunc(aug, cbaois)
# --> different between runs
cbas1 = [cba
for cbaoi in cbaois_aug1
for cba in cbaoi.items]
cbas2 = [cba
for cbaoi in cbaois_aug2
for cba in cbaoi.items]
assert len(cbas1) == len(cbas2)
same = []
for cba1, cba2 in zip(cbas1, cbas2):
points1 = np.float32(cba1.coords)
points2 = np.float32(cba2.coords)
same.append(self._compare_coords_of_cba(points1, points2,
atol=1e-2, rtol=0))
assert not np.all(same)
# --> different between PolygonOnImages
same = []
points1 = np.float32([cba.coords
for cba
in cbaois_aug1[0].items])
for cba in cbaois_aug1[1:]:
points2 = np.float32([cba.coords
for cba
in cba.items])
same.append(self._compare_coords_of_cba(points1, points2,
atol=1e-2, rtol=0))
assert not np.all(same)
# --> different between polygons
points1 = set()
for cba in cbaois_aug1[0].items:
for point in cba.coords:
points1.add(tuple(
[int(point[0]*10), int(point[1]*10)]
))
assert len(points1) > 1
def test_determinism(self):
aug = iaa.Rot90((0, 3), keep_size=False)
aug_det = aug.to_deterministic()
cba = self._Obj([(0, 0), (5, 0), (5, 5)])
cbaoi = self._ObjOnImage(
[cba.deepcopy() for _ in sm.xrange(1)],
shape=(10, 11, 3)
)
cbaois = [cbaoi.deepcopy() for _ in sm.xrange(100)]
cbaois_aug1 = self._augfunc(aug_det, cbaois)
cbaois_aug2 = self._augfunc(aug_det, cbaois)
# --> different between PolygonsOnImages
same = []
points1 = np.float32([cba.coords
for cba
in cbaois_aug1[0].items])
for cbaoi in cbaois_aug1[1:]:
points2 = np.float32([cba.coords
for cba
in cbaoi.items])
same.append(self._compare_coords_of_cba(points1, points2,
atol=1e-2, rtol=0))
assert not np.all(same)
# --> similar between augmentation runs
cbas1 = [cba
for cbaoi in cbaois_aug1
for cba in cbaoi.items]
cbas2 = [cba
for cbaoi in cbaois_aug2
for cba in cbaoi.items]
assert len(cbas1) == len(cbas2)
for cba1, cba2 in zip(cbas1, cbas2):
points1 = np.float32(cba1.coords)
points2 = np.float32(cba2.coords)
assert self._compare_coords_of_cba(points1, points2,
atol=1e-2, rtol=0)
def test_aligned_with_images(self):
aug = iaa.Rot90((0, 3), keep_size=False)
aug_det = aug.to_deterministic()
image = np.zeros((10, 20), dtype=np.uint8)
image[5, :] = 255
image[2:5, 10] = 255
image_rots = [iaa.Rot90(k, keep_size=False).augment_image(image)
for k in [0, 1, 2, 3]]
cba = self._Obj([(0, 0), (10, 0), (10, 20)])
kp_offs = 0 # offset
cbas_rots = [
[(0, 0), (10, 0), (10, 20)],
[(10-0+kp_offs, 0), (10-0+kp_offs, 10), (10-20+kp_offs, 10)],
[(20-0+kp_offs, 10), (20-10+kp_offs, 10), (20-10+kp_offs, -10)],
[(10-10+kp_offs, 20), (10-10+kp_offs, 10), (10-(-10)+kp_offs, 10)]
]
cbaois = [self._ObjOnImage([cba], shape=image.shape)
for _ in sm.xrange(50)]
images_aug = aug_det.augment_images([image] * 50)
cbaois_aug = self._augfunc(aug_det, cbaois)
seen = set()
for image_aug, cbaoi_aug in zip(images_aug, cbaois_aug):
found_image = False
for img_rot_idx, img_rot in enumerate(image_rots):
if (image_aug.shape == img_rot.shape
and np.allclose(image_aug, img_rot)):
found_image = True
break
found_cba = False
for poly_rot_idx, cba_rot in enumerate(cbas_rots):
coords_observed = cbaoi_aug.items[0].coords
if self._compare_coords_of_cba(coords_observed, cba_rot):
found_cba = True
break
assert found_image
assert found_cba
assert img_rot_idx == poly_rot_idx
seen.add((img_rot_idx, poly_rot_idx))
assert 2 <= len(seen) <= 4 # assert not always the same rot
def test_aligned_with_images_despite_empty_instances(self):
# Test if augmenting lists of e.g. PolygonsOnImage is still aligned
# with image augmentation when one e.g. PolygonsOnImage instance is
# empty (e.g. contains no polygons)
cba = self._Obj([(0, 0), (5, 0), (5, 5), (0, 5)])
cbaoi_lst = [
self._ObjOnImage([cba.deepcopy()], shape=(10, 20)),
self._ObjOnImage([cba.deepcopy()], shape=(10, 20)),
self._ObjOnImage([cba.shift(x=1)], shape=(10, 20)),
self._ObjOnImage([cba.deepcopy()], shape=(10, 20)),
self._ObjOnImage([cba.deepcopy()], shape=(10, 20)),
self._ObjOnImage([], shape=(1, 8)),
self._ObjOnImage([cba.deepcopy()], shape=(10, 20)),
self._ObjOnImage([cba.deepcopy()], shape=(10, 20)),
self._ObjOnImage([cba.shift(x=1)], shape=(10, 20)),
self._ObjOnImage([cba.deepcopy()], shape=(10, 20)),
self._ObjOnImage([cba.deepcopy()], shape=(10, 20))
]
image = np.zeros((10, 20), dtype=np.uint8)
image[0, 0] = 255
image[0, 5] = 255
image[5, 5] = 255
image[5, 0] = 255
images = np.tile(image[np.newaxis, :, :], (len(cbaoi_lst), 1, 1))
aug = iaa.Affine(translate_px={"x": (0, 8)}, order=0, mode="constant",
cval=0)
for _ in sm.xrange(10):
for is_list in [False, True]:
aug_det = aug.to_deterministic()
inputs = images
if is_list:
inputs = list(inputs)
images_aug = aug_det.augment_images(inputs)
cbaoi_aug_lst = self._augfunc(aug_det, cbaoi_lst)
if is_list:
images_aug = np.array(images_aug, dtype=np.uint8)
translations_imgs = np.argmax(images_aug[:, 0, :], axis=1)
translations_points = [
(cbaoi.items[0].coords[0][0] if not cbaoi.empty else None)
for cbaoi
in cbaoi_aug_lst]
assert len([
pointresult for
pointresult
in translations_points
if pointresult is None
]) == 1
assert translations_points[5] is None
translations_imgs = np.concatenate(
[translations_imgs[0:5], translations_imgs[6:]])
translations_points = np.array(
translations_points[0:5] + translations_points[6:],
dtype=translations_imgs.dtype)
translations_points[2] -= 1
translations_points[8-1] -= 1
assert np.array_equal(translations_imgs, translations_points)
# This is the same as _ConcavePolygonRecoverer, but we make sure that we
# always sample random values. This is to advance the state of random_state
# and ensure that this breaks not alignment.
class _DummyRecoverer(_ConcavePolygonRecoverer):
def recover_from(self, new_exterior, old_polygon, random_state=0):
# sample lots of values to ensure that the RNG is advanced
_ = random_state.integers(0, 2**30, 100)
return super(_DummyRecoverer, self).recover_from(
new_exterior, old_polygon, random_state=random_state)
class _DummyAugmenterWithRecoverer(iaa.Augmenter):
def __init__(self, use_recoverer=True):
super(_DummyAugmenterWithRecoverer, self).__init__()
self.random_samples_images = []
self.random_samples_kps = []
if use_recoverer:
self.recoverer = _DummyRecoverer()
else:
self.recoverer = None
def _augment_images(self, images, random_state, parents, hooks):
sample = random_state.integers(0, 2**30)
self.random_samples_images.append(sample)
return images
def _augment_polygons(self, polygons_on_images, random_state, parents,
hooks):
return self._augment_polygons_as_keypoints(
polygons_on_images, random_state, parents, hooks,
recoverer=self.recoverer)
def _augment_keypoints(self, keypoints_on_images, random_state, parents,
hooks):
sample = random_state.integers(0, 2**30)
self.random_samples_kps.append(sample)
assert len(keypoints_on_images) in [1, 2]
assert len(keypoints_on_images[0].keypoints) == 7
result = []
for _ in keypoints_on_images:
# every second call of _augment_polygons()...
if len(self.random_samples_kps) % 2 == 1:
# not concave
kpsoi = ia.KeypointsOnImage([
ia.Keypoint(x=0, y=0),
ia.Keypoint(x=10, y=0),
ia.Keypoint(x=10, y=4),
ia.Keypoint(x=-1, y=5),
ia.Keypoint(x=10, y=6),
ia.Keypoint(x=10, y=10),
ia.Keypoint(x=0, y=10)
], shape=(10, 10, 3))
else:
# concave
kpsoi = ia.KeypointsOnImage([
ia.Keypoint(x=0, y=0),
ia.Keypoint(x=10, y=0),
ia.Keypoint(x=10, y=4),
ia.Keypoint(x=10, y=5),
ia.Keypoint(x=10, y=6),
ia.Keypoint(x=10, y=10),
ia.Keypoint(x=0, y=10)
], shape=(10, 10, 3))
result.append(kpsoi)
return result
def get_parameters(self):
return []
class TestAugmenter_augment_polygons(_TestAugmenter_augment_cbaois,
unittest.TestCase):
def _augfunc(self, augmenter, *args, **kwargs):
return augmenter.augment_polygons(*args, **kwargs)
@property
def _ObjClass(self):
return ia.Polygon
@property
def _ObjOnImageClass(self):
return ia.PolygonsOnImage
def _coords(self, obj):
return obj.exterior
def _entities(self, obj_on_image):
return obj_on_image.polygons
def test_polygon_recoverer(self):
# This is mostly a dummy polygon. The augmenter always returns the
# same non-concave polygon.
poly = ia.Polygon([(0, 0), (10, 0),
(10, 4), (10, 5), (10, 6),
(10, 10), (0, 10)])
psoi = ia.PolygonsOnImage([poly], shape=(10, 10, 3))
aug = _DummyAugmenterWithRecoverer()
psoi_aug = aug.augment_polygons(psoi)
poly_aug = psoi_aug.polygons[0]
bb = ia.BoundingBox(x1=0, y1=0, x2=10, y2=10)
bb_aug = ia.BoundingBox(
x1=np.min(poly_aug.exterior[:, 0]),
y1=np.min(poly_aug.exterior[:, 1]),
x2=np.max(poly_aug.exterior[:, 0]),
y2=np.max(poly_aug.exterior[:, 1])
)
assert bb.iou(bb_aug) > 0.9
assert psoi_aug.polygons[0].is_valid
def test_polygon_aligned_without_recoverer(self):
# This is mostly a dummy polygon. The augmenter always returns the
# same non-concave polygon.
poly = ia.Polygon([(0, 0), (10, 0),
(10, 4), (10, 5), (10, 6),
(10, 10), (0, 10)])
psoi = ia.PolygonsOnImage([poly], shape=(10, 10, 3))
image = np.zeros((10, 10, 3))
aug = _DummyAugmenterWithRecoverer(use_recoverer=False)
images_aug1, psois_aug1 = aug(images=[image, image],
polygons=[psoi, psoi])
images_aug2, psois_aug2 = aug(images=[image, image],
polygons=[psoi, psoi])
images_aug3, psois_aug3 = aug(images=[image, image],
polygons=[psoi, psoi])
images_aug4, psois_aug4 = aug(images=[image, image],
polygons=[psoi, psoi])
assert not psois_aug1[0].polygons[0].is_valid
assert not psois_aug1[1].polygons[0].is_valid
assert psois_aug2[0].polygons[0].is_valid
assert psois_aug2[1].polygons[0].is_valid
assert not psois_aug3[0].polygons[0].is_valid
assert not psois_aug3[1].polygons[0].is_valid
assert psois_aug4[0].polygons[0].is_valid
assert psois_aug4[1].polygons[0].is_valid
assert aug.random_samples_images == aug.random_samples_kps
def test_polygon_aligned_with_recoverer(self):
# This is mostly a dummy polygon. The augmenter always returns the
# same non-concave polygon.
poly = ia.Polygon([(0, 0), (10, 0),
(10, 4), (10, 5), (10, 6),
(10, 10), (0, 10)])
psoi = ia.PolygonsOnImage([poly], shape=(10, 10, 3))
image = np.zeros((10, 10, 3))
aug = _DummyAugmenterWithRecoverer(use_recoverer=True)
images_aug1, psois_aug1 = aug(images=[image, image],
polygons=[psoi, psoi])
images_aug2, psois_aug2 = aug(images=[image, image],
polygons=[psoi, psoi])
images_aug3, psois_aug3 = aug(images=[image, image],
polygons=[psoi, psoi])
images_aug4, psois_aug4 = aug(images=[image, image],
polygons=[psoi, psoi])
assert psois_aug1[0].polygons[0].is_valid
assert psois_aug1[1].polygons[0].is_valid
assert psois_aug2[0].polygons[0].is_valid
assert psois_aug2[1].polygons[0].is_valid
assert psois_aug3[0].polygons[0].is_valid
assert psois_aug3[1].polygons[0].is_valid
assert psois_aug4[0].polygons[0].is_valid
assert psois_aug4[1].polygons[0].is_valid
assert aug.random_samples_images == aug.random_samples_kps
class TestAugmenter_augment_line_strings(_TestAugmenter_augment_cbaois,
unittest.TestCase):
def _augfunc(self, augmenter, *args, **kwargs):
return augmenter.augment_line_strings(*args, **kwargs)
@property
def _ObjClass(self):
return ia.LineString
@property
def _ObjOnImageClass(self):
return ia.LineStringsOnImage
class TestAugmenter_augment_bounding_boxes(_TestAugmenter_augment_cbaois,
unittest.TestCase):
def _augfunc(self, augmenter, *args, **kwargs):
return augmenter.augment_bounding_boxes(*args, **kwargs)
@property
def _ObjClass(self):
return ia.BoundingBox
@property
def _ObjOnImageClass(self):
return ia.BoundingBoxesOnImage
def _Obj(self, *args, **kwargs):
assert len(args) == 1
coords = np.float32(args[0]).reshape((-1, 2))
x1 = np.min(coords[:, 0])
y1 = np.min(coords[:, 1])
x2 = np.max(coords[:, 0])
y2 = np.max(coords[:, 1])
return self._ObjClass(x1=x1, y1=y1, x2=x2, y2=y2, **kwargs)
def _compare_coords_of_cba(self, observed, expected, atol=1e-4, rtol=0):
observed = np.float32(observed).reshape((-1, 2))
expected = np.float32(expected).reshape((-1, 2))
assert observed.shape[0] == 2
assert expected.shape[1] == 2
obs_x1 = np.min(observed[:, 0])
obs_y1 = np.min(observed[:, 1])
obs_x2 = np.max(observed[:, 0])
obs_y2 = np.max(observed[:, 1])
exp_x1 = np.min(expected[:, 0])
exp_y1 = np.min(expected[:, 1])
exp_x2 = np.max(expected[:, 0])
exp_y2 = np.max(expected[:, 1])
return np.allclose(
[obs_x1, obs_y1, obs_x2, obs_y2],
[exp_x1, exp_y1, exp_x2, exp_y2],
atol=atol, rtol=rtol)
# the method is mostly tested indirectly, so very few tests here
class TestAugmenter_augment_bounding_boxes_by_keypoints(unittest.TestCase):
def test_x_min_max(self):
# ensure that min() and max() are applied to augmented x-coordinates
# when they are converted back to BBs
class _ShiftingXCoordAugmenter(iaa.Augmenter):
def _augment_images(self, images, random_state, parents, hooks):
return images
def _augment_bounding_boxes(self, bounding_boxes_on_images,
random_state, parents, hooks):
return self._augment_bounding_boxes_as_keypoints(
bounding_boxes_on_images, random_state, parents, hooks)
def _augment_keypoints(self, keypoints_on_images, random_state,
parents, hooks):
keypoints_on_images[0].keypoints[0].x += 10
keypoints_on_images[0].keypoints[1].x -= 10
return keypoints_on_images
def get_parameters(self):
return []
aug = _ShiftingXCoordAugmenter()
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)], shape=(10, 10, 3))
observed = aug(bounding_boxes=bbsoi)
assert np.allclose(
observed.bounding_boxes[0].coords,
[(2-10, 1), (0+10, 3)]
)
def test_y_min_max(self):
# ensure that min() and max() are applied to augmented y-coordinates
# when they are converted back to BBs
class _ShiftingYCoordAugmenter(iaa.Augmenter):
def _augment_images(self, images, random_state, parents, hooks):
return images
def _augment_bounding_boxes(self, bounding_boxes_on_images,
random_state, parents, hooks):
return self._augment_bounding_boxes_as_keypoints(
bounding_boxes_on_images, random_state, parents, hooks)
def _augment_keypoints(self, keypoints_on_images, random_state,
parents, hooks):
keypoints_on_images[0].keypoints[0].y += 10
keypoints_on_images[0].keypoints[1].y -= 10
return keypoints_on_images
def get_parameters(self):
return []
aug = _ShiftingYCoordAugmenter()
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)], shape=(10, 10, 3))
observed = aug(bounding_boxes=bbsoi)
assert np.allclose(
observed.bounding_boxes[0].coords,
[(0, 1-10), (2, 1+10)]
)
def test_x1_x2_can_get_flipped(self):
# ensure that augmented x-coordinates where x1>x2 are flipped
# before creating BBs from them
class _FlippingX1X2Augmenter(iaa.Augmenter):
def _augment_images(self, images, random_state, parents, hooks):
return images
def _augment_bounding_boxes(self, bounding_boxes_on_images,
random_state, parents, hooks):
return self._augment_bounding_boxes_as_keypoints(
bounding_boxes_on_images, random_state, parents, hooks)
def _augment_keypoints(self, keypoints_on_images, random_state,
parents, hooks):
keypoints_on_images[0].keypoints[0].x += 10 # top left
keypoints_on_images[0].keypoints[3].x += 10 # bottom left
return keypoints_on_images
def get_parameters(self):
return []
aug = _FlippingX1X2Augmenter()
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)], shape=(10, 10, 3))
observed = aug(bounding_boxes=bbsoi)
assert np.allclose(
observed.bounding_boxes[0].coords,
[(2, 1), (0+10, 3)]
)
def test_y1_y2_can_get_flipped(self):
# ensure that augmented y-coordinates where y1>y2 are flipped
# before creating BBs from them
class _FlippingY1Y2Augmenter(iaa.Augmenter):
def _augment_images(self, images, random_state, parents, hooks):
return images
def _augment_bounding_boxes(self, bounding_boxes_on_images,
random_state, parents, hooks):
return self._augment_bounding_boxes_as_keypoints(
bounding_boxes_on_images, random_state, parents, hooks)
def _augment_keypoints(self, keypoints_on_images, random_state,
parents, hooks):
keypoints_on_images[0].keypoints[0].y += 10 # top left
keypoints_on_images[0].keypoints[1].y += 10 # top right
return keypoints_on_images
def get_parameters(self):
return []
aug = _FlippingY1Y2Augmenter()
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)], shape=(10, 10, 3))
observed = aug(bounding_boxes=bbsoi)
assert np.allclose(
observed.bounding_boxes[0].coords,
[(0, 3), (2, 1+10)]
)
class TestAugmenter_augment(unittest.TestCase):
def setUp(self):
reseed()
def test_image(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
image_aug = aug.augment(image=image)
assert image_aug.shape == image.shape
assert np.array_equal(image_aug, image)
def test_images_list(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
images_aug = aug.augment(images=[image])
assert images_aug[0].shape == image.shape
assert np.array_equal(images_aug[0], image)
def test_images_and_heatmaps(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap((128, 128), extract="square")
images_aug, heatmaps_aug = aug.augment(images=[image],
heatmaps=[heatmaps])
assert np.array_equal(images_aug[0], image)
assert np.allclose(heatmaps_aug[0].arr_0to1, heatmaps.arr_0to1)
def test_images_and_segmentation_maps(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
images_aug, segmaps_aug = aug.augment(images=[image],
segmentation_maps=[segmaps])
assert np.array_equal(images_aug[0], image)
assert np.allclose(segmaps_aug[0].arr, segmaps.arr)
def test_images_and_keypoints(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
images_aug, keypoints_aug = aug.augment(images=[image],
keypoints=[keypoints])
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(keypoints_aug[0], keypoints)
def test_images_and_polygons(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
images_aug, polygons_aug = aug.augment(images=[image],
polygons=[polygons])
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(polygons_aug[0], polygons)
def test_images_and_line_strings(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
psoi = ia.data.quokka_polygons((128, 128), extract="square")
lsoi = ia.LineStringsOnImage([
psoi.polygons[0].to_line_string(closed=False)
], shape=psoi.shape)
images_aug, lsoi_aug = aug.augment(images=[image],
line_strings=[lsoi])
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(lsoi_aug[0], lsoi)
def test_images_and_bounding_boxes(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
bbs = ia.data.quokka_bounding_boxes((128, 128), extract="square")
images_aug, bbs_aug = aug.augment(images=[image], bounding_boxes=[bbs])
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(bbs_aug[0], bbs)
def test_image_return_batch(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
batch = aug.augment(image=image, return_batch=True)
image_aug = batch.images_aug[0]
assert np.array_equal(image, image_aug)
def test_images_and_heatmaps_return_batch(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap((128, 128), extract="square")
batch = aug.augment(images=[image], heatmaps=[heatmaps],
return_batch=True)
images_aug = batch.images_aug
heatmaps_aug = batch.heatmaps_aug
assert np.array_equal(images_aug[0], image)
assert np.allclose(heatmaps_aug[0].arr_0to1, heatmaps.arr_0to1)
def test_images_and_segmentation_maps_return_batch(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
batch = aug.augment(images=[image], segmentation_maps=[segmaps],
return_batch=True)
images_aug = batch.images_aug
segmaps_aug = batch.segmentation_maps_aug
assert np.array_equal(images_aug[0], image)
assert np.allclose(segmaps_aug[0].arr, segmaps.arr)
def test_images_and_keypoints_return_batch(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
batch = aug.augment(images=[image], keypoints=[keypoints],
return_batch=True)
images_aug = batch.images_aug
keypoints_aug = batch.keypoints_aug
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(keypoints_aug[0], keypoints)
def test_images_and_polygons_return_batch(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
batch = aug.augment(images=[image], polygons=[polygons],
return_batch=True)
images_aug = batch.images_aug
polygons_aug = batch.polygons_aug
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(polygons_aug[0], polygons)
def test_images_and_line_strings_return_batch(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
psoi = ia.data.quokka_polygons((128, 128), extract="square")
lsoi = ia.LineStringsOnImage([
psoi.polygons[0].to_line_string(closed=False)
], shape=psoi.shape)
batch = aug.augment(images=[image], line_strings=[lsoi],
return_batch=True)
images_aug = batch.images_aug
lsoi_aug = batch.line_strings_aug
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(lsoi_aug[0], lsoi)
def test_images_and_bounding_boxes_return_batch(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
bbs = ia.data.quokka_bounding_boxes((128, 128), extract="square")
batch = aug.augment(images=[image], bounding_boxes=[bbs],
return_batch=True)
images_aug = batch.images_aug
bbs_aug = batch.bounding_boxes_aug
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(bbs_aug[0], bbs)
def test_non_image_data(self):
aug = iaa.Identity()
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
batch = aug.augment(segmentation_maps=[segmaps], keypoints=[keypoints],
polygons=[polygons], return_batch=True)
segmaps_aug = batch.segmentation_maps_aug
keypoints_aug = batch.keypoints_aug
polygons_aug = batch.polygons_aug
assert np.allclose(segmaps_aug[0].arr, segmaps.arr)
assert_cbaois_equal(keypoints_aug[0], keypoints)
assert_cbaois_equal(polygons_aug[0], polygons)
def test_non_image_data_unexpected_args_order(self):
aug = iaa.Identity()
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
batch = aug.augment(polygons=[polygons], segmentation_maps=[segmaps],
keypoints=[keypoints], return_batch=True)
segmaps_aug = batch.segmentation_maps_aug
keypoints_aug = batch.keypoints_aug
polygons_aug = batch.polygons_aug
assert np.allclose(segmaps_aug[0].arr, segmaps.arr)
assert np.allclose(keypoints_aug[0].to_xy_array(),
keypoints.to_xy_array())
for polygon_aug, polygon in zip(polygons_aug[0].polygons,
polygons.polygons):
assert polygon_aug.exterior_almost_equals(polygon)
def test_with_affine(self):
# make sure that augment actually does something
aug = iaa.Affine(translate_px={"x": 1}, order=0, mode="constant",
cval=0)
image = np.zeros((4, 4, 1), dtype=np.uint8) + 255
heatmaps = np.ones((1, 4, 4, 1), dtype=np.float32)
segmaps = np.ones((1, 4, 4, 1), dtype=np.int32)
kps = [(0, 0), (1, 2)]
bbs = [(0, 0, 1, 1), (1, 2, 2, 3)]
polygons = [(0, 0), (1, 0), (1, 1)]
ls = [(0, 0), (1, 0), (1, 1)]
image_aug = aug.augment(image=image)
_, heatmaps_aug = aug.augment(image=image, heatmaps=heatmaps)
_, segmaps_aug = aug.augment(image=image, segmentation_maps=segmaps)
_, kps_aug = aug.augment(image=image, keypoints=kps)
_, bbs_aug = aug.augment(image=image, bounding_boxes=bbs)
_, polygons_aug = aug.augment(image=image, polygons=polygons)
_, ls_aug = aug.augment(image=image, line_strings=ls)
# all augmentables must have been moved to the right by 1px
assert np.all(image_aug[:, 0] == 0)
assert np.all(image_aug[:, 1:] == 255)
assert np.allclose(heatmaps_aug[0][:, 0], 0.0)
assert np.allclose(heatmaps_aug[0][:, 1:], 1.0)
assert np.all(segmaps_aug[0][:, 0] == 0)
assert np.all(segmaps_aug[0][:, 1:] == 1)
assert np.allclose(kps_aug, [(1, 0), (2, 2)])
assert np.allclose(bbs_aug, [(1, 0, 2, 1), (2, 2, 3, 3)])
assert np.allclose(polygons_aug, [(1, 0), (2, 0), (2, 1)])
assert np.allclose(ls_aug, [(1, 0), (2, 0), (2, 1)])
def test_alignment(self):
# make sure that changes from augment() are aligned and vary between
# call
aug = iaa.Affine(translate_px={"x": (0, 100)}, order=0, mode="constant",
cval=0)
image = np.zeros((1, 100, 1), dtype=np.uint8) + 255
heatmaps = np.ones((1, 1, 100, 1), dtype=np.float32)
segmaps = np.ones((1, 1, 100, 1), dtype=np.int32)
kps = [(0, 0)]
bbs = [(0, 0, 1, 1)]
polygons = [(0, 0), (1, 0), (1, 1)]
ls = [(0, 0), (1, 0), (1, 1)]
seen = []
for _ in sm.xrange(10):
batch_aug = aug.augment(image=image, heatmaps=heatmaps,
segmentation_maps=segmaps, keypoints=kps,
bounding_boxes=bbs, polygons=polygons,
line_strings=ls, return_batch=True)
shift_image = np.sum(batch_aug.images_aug[0][0, :] == 0)
shift_heatmaps = np.sum(
np.isclose(batch_aug.heatmaps_aug[0][0, :, 0], 0.0))
shift_segmaps = np.sum(
batch_aug.segmentation_maps_aug[0][0, :, 0] == 0)
shift_kps = batch_aug.keypoints_aug[0][0]
shift_bbs = batch_aug.bounding_boxes_aug[0][0]
shift_polygons = batch_aug.polygons_aug[0][0]
shift_ls = batch_aug.line_strings_aug[0][0]
assert len({shift_image, shift_heatmaps, shift_segmaps,
shift_kps, shift_bbs, shift_polygons,
shift_ls}) == 1
seen.append(shift_image)
assert len(set(seen)) > 7
def test_alignment_and_same_outputs_in_deterministic_mode(self):
# make sure that changes from augment() are aligned
# and do NOT vary if the augmenter was already in deterministic mode
aug = iaa.Affine(translate_px={"x": (0, 100)}, order=0, mode="constant",
cval=0)
aug = aug.to_deterministic()
image = np.zeros((1, 100, 1), dtype=np.uint8) + 255
heatmaps = np.ones((1, 1, 100, 1), dtype=np.float32)
segmaps = np.ones((1, 1, 100, 1), dtype=np.int32)
kps = [(0, 0)]
bbs = [(0, 0, 1, 1)]
polygons = [(0, 0), (1, 0), (1, 1)]
ls = [(0, 0), (1, 0), (1, 1)]
seen = []
for _ in sm.xrange(10):
batch_aug = aug.augment(image=image, heatmaps=heatmaps,
segmentation_maps=segmaps, keypoints=kps,
bounding_boxes=bbs, polygons=polygons,
line_strings=ls,
return_batch=True)
shift_image = np.sum(batch_aug.images_aug[0][0, :] == 0)
shift_heatmaps = np.sum(
np.isclose(batch_aug.heatmaps_aug[0][0, :, 0], 0.0))
shift_segmaps = np.sum(
batch_aug.segmentation_maps_aug[0][0, :, 0] == 0)
shift_kps = batch_aug.keypoints_aug[0][0]
shift_bbs = batch_aug.bounding_boxes_aug[0][0]
shift_polygons = batch_aug.polygons_aug[0][0]
shift_ls = batch_aug.line_strings_aug[0][0]
assert len({shift_image, shift_heatmaps, shift_segmaps,
shift_kps, shift_bbs, shift_polygons,
shift_ls}) == 1
seen.append(shift_image)
assert len(set(seen)) == 1
def test_arrays_become_lists_if_augmenter_changes_shapes(self):
# make sure that arrays (of images, heatmaps, segmaps) get split to
# lists of arrays if the augmenter changes shapes in non-uniform
# (between images) ways
# we augment 100 images here with rotation of either 0deg or 90deg
# and do not resize back to the original image size afterwards, so
# shapes change
aug = iaa.Rot90([0, 1], keep_size=False)
# base_arr is (100, 1, 2) array, each containing [[0, 1]]
base_arr = np.tile(np.arange(1*2).reshape((1, 2))[np.newaxis, :, :],
(100, 1, 1))
images = np.copy(base_arr)[:, :, :, np.newaxis].astype(np.uint8)
heatmaps = (
np.copy(base_arr)[:, :, :, np.newaxis].astype(np.float32)
/ np.max(base_arr)
)
segmaps = np.copy(base_arr)[:, :, :, np.newaxis].astype(np.int32)
batch_aug = aug.augment(images=images, heatmaps=heatmaps,
segmentation_maps=segmaps,
return_batch=True)
assert isinstance(batch_aug.images_aug, list)
assert isinstance(batch_aug.heatmaps_aug, list)
assert isinstance(batch_aug.segmentation_maps_aug, list)
shapes_images = [arr.shape for arr in batch_aug.images_aug]
shapes_heatmaps = [arr.shape for arr in batch_aug.heatmaps_aug]
shapes_segmaps = [arr.shape for arr in batch_aug.segmentation_maps_aug]
assert (
[shape[0:2] for shape in shapes_images]
== [shape[0:2] for shape in shapes_heatmaps]
== [shape[0:2] for shape in shapes_segmaps]
)
assert len(set(shapes_images)) == 2
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_two_outputs_none_of_them_images(self):
aug = iaa.Identity()
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
keypoints_aug, polygons_aug = aug.augment(keypoints=[keypoints],
polygons=[polygons])
assert_cbaois_equal(keypoints_aug[0], keypoints)
assert_cbaois_equal(polygons_aug[0], polygons)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_two_outputs_none_of_them_images_inverted(self):
aug = iaa.Identity()
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
polygons_aug, keypoints_aug = aug.augment(polygons=[polygons],
keypoints=[keypoints])
assert_cbaois_equal(keypoints_aug[0], keypoints)
assert_cbaois_equal(polygons_aug[0], polygons)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_two_outputs_inverted_order_heatmaps(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap((128, 128), extract="square")
heatmaps_aug, images_aug = aug.augment(heatmaps=[heatmaps],
images=[image])
assert np.array_equal(images_aug[0], image)
assert np.allclose(heatmaps_aug[0].arr_0to1, heatmaps.arr_0to1)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_two_outputs_inverted_order_segmaps(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
segmaps_aug, images_aug = aug.augment(segmentation_maps=[segmaps],
images=[image])
assert np.array_equal(images_aug[0], image)
assert np.array_equal(segmaps_aug[0].arr, segmaps.arr)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_two_outputs_inverted_order_keypoints(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
keypoints_aug, images_aug = aug.augment(keypoints=[keypoints],
images=[image])
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(keypoints_aug[0], keypoints)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_two_outputs_inverted_order_bbs(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
bbs = ia.data.quokka_bounding_boxes((128, 128), extract="square")
bbs_aug, images_aug = aug.augment(bounding_boxes=[bbs],
images=[image])
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(bbs_aug[0], bbs)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_two_outputs_inverted_order_polygons(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
polygons_aug, images_aug = aug.augment(polygons=[polygons],
images=[image])
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(polygons_aug[0], polygons)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_two_outputs_inverted_order_line_strings(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
psoi = ia.data.quokka_polygons((128, 128), extract="square")
lsoi = ia.LineStringsOnImage([
psoi.polygons[0].to_line_string(closed=False)
], shape=psoi.shape)
lsoi_aug, images_aug = aug.augment(line_strings=[lsoi],
images=[image])
assert np.array_equal(images_aug[0], image)
assert_cbaois_equal(lsoi_aug[0], lsoi)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_three_inputs_expected_order(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap((128, 128), extract="square")
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
images_aug, heatmaps_aug, segmaps_aug = aug.augment(
images=[image],
heatmaps=[heatmaps],
segmentation_maps=[segmaps])
assert np.array_equal(images_aug[0], image)
assert np.allclose(heatmaps_aug[0].arr_0to1, heatmaps.arr_0to1)
assert np.array_equal(segmaps_aug[0].arr, segmaps.arr)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_three_inputs_expected_order2(self):
aug = iaa.Identity()
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
segmaps_aug, keypoints_aug, polygons_aug = aug.augment(
segmentation_maps=[segmaps],
keypoints=[keypoints],
polygons=[polygons])
assert np.array_equal(segmaps_aug[0].arr, segmaps.arr)
assert_cbaois_equal(keypoints_aug[0], keypoints)
assert_cbaois_equal(polygons_aug[0], polygons)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_three_inputs_inverted_order(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap((128, 128), extract="square")
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
segmaps_aug, heatmaps_aug, images_aug = aug.augment(
segmentation_maps=[segmaps],
heatmaps=[heatmaps],
images=[image])
assert np.array_equal(images_aug[0], image)
assert np.allclose(heatmaps_aug[0].arr_0to1, heatmaps.arr_0to1)
assert np.array_equal(segmaps_aug[0].arr, segmaps.arr)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_three_inputs_inverted_order2(self):
aug = iaa.Identity()
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
polygons_aug, keypoints_aug, segmaps_aug = aug.augment(
polygons=[polygons],
keypoints=[keypoints],
segmentation_maps=[segmaps])
assert np.array_equal(segmaps_aug[0].arr, segmaps.arr)
assert_cbaois_equal(keypoints_aug[0], keypoints)
assert_cbaois_equal(polygons_aug[0], polygons)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_all_inputs_expected_order(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap((128, 128), extract="square")
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
bbs = ia.data.quokka_bounding_boxes((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
lsoi = ia.LineStringsOnImage([
polygons.polygons[0].to_line_string(closed=False)
], shape=polygons.shape)
images_aug, heatmaps_aug, segmaps_aug, keypoints_aug, bbs_aug, \
polygons_aug, lsoi_aug = aug.augment(
images=[image],
heatmaps=[heatmaps],
segmentation_maps=[segmaps],
keypoints=[keypoints],
bounding_boxes=[bbs],
polygons=[polygons],
line_strings=[lsoi])
assert np.array_equal(images_aug[0], image)
assert np.allclose(heatmaps_aug[0].arr_0to1, heatmaps.arr_0to1)
assert np.array_equal(segmaps_aug[0].arr, segmaps.arr)
assert_cbaois_equal(keypoints_aug[0], keypoints)
assert_cbaois_equal(bbs_aug[0], bbs)
assert_cbaois_equal(polygons_aug[0], polygons)
assert_cbaois_equal(lsoi_aug[0], lsoi)
@unittest.skipIf(not IS_PY36_OR_HIGHER,
"Behaviour is only supported in python 3.6+")
def test_py_gte_36_all_inputs_inverted_order(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap((128, 128), extract="square")
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
bbs = ia.data.quokka_bounding_boxes((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
lsoi = ia.LineStringsOnImage([
polygons.polygons[0].to_line_string(closed=False)
], shape=polygons.shape)
lsoi_aug, polygons_aug, bbs_aug, keypoints_aug, segmaps_aug, \
heatmaps_aug, images_aug = aug.augment(
line_strings=[lsoi],
polygons=[polygons],
bounding_boxes=[bbs],
keypoints=[keypoints],
segmentation_maps=[segmaps],
heatmaps=[heatmaps],
images=[image])
assert np.array_equal(images_aug[0], image)
assert np.allclose(heatmaps_aug[0].arr_0to1, heatmaps.arr_0to1)
assert np.array_equal(segmaps_aug[0].arr, segmaps.arr)
assert_cbaois_equal(keypoints_aug[0], keypoints)
assert_cbaois_equal(bbs_aug[0], bbs)
assert_cbaois_equal(polygons_aug[0], polygons)
assert_cbaois_equal(lsoi_aug[0], lsoi)
@unittest.skipIf(IS_PY36_OR_HIGHER,
"Test checks behaviour for python <=3.5")
def test_py_lte_35_calls_without_images_fail(self):
aug = iaa.Identity()
keypoints = ia.data.quokka_keypoints((128, 128), extract="square")
polygons = ia.data.quokka_polygons((128, 128), extract="square")
got_exception = False
try:
_ = aug.augment(keypoints=[keypoints], polygons=[polygons])
except Exception as exc:
msg = "Requested two outputs from augment() that were not 'images'"
assert msg in str(exc)
got_exception = True
assert got_exception
@unittest.skipIf(IS_PY36_OR_HIGHER,
"Test checks behaviour for python <=3.5")
def test_py_lte_35_calls_with_more_than_three_args_fail(self):
aug = iaa.Identity()
image = ia.data.quokka((128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap((128, 128), extract="square")
segmaps = ia.data.quokka_segmentation_map((128, 128), extract="square")
got_exception = False
try:
_ = aug.augment(images=[image], heatmaps=[heatmaps],
segmentation_maps=[segmaps])
except Exception as exc:
assert "Requested more than two outputs" in str(exc)
got_exception = True
assert got_exception
class TestAugmenter___call__(unittest.TestCase):
def setUp(self):
reseed()
def test_with_two_augmentables(self):
image = ia.data.quokka(size=(128, 128), extract="square")
heatmaps = ia.data.quokka_heatmap(size=(128, 128), extract="square")
images_aug, heatmaps_aug = iaa.Identity()(images=[image],
heatmaps=[heatmaps])
assert np.array_equal(images_aug[0], image)
assert np.allclose(heatmaps_aug[0].arr_0to1, heatmaps.arr_0to1)
class TestAugmenter_pool(unittest.TestCase):
def setUp(self):
reseed()
def test_pool(self):
augseq = iaa.Identity()
mock_Pool = mock.MagicMock()
mock_Pool.return_value = mock_Pool
mock_Pool.__enter__.return_value = None
mock_Pool.__exit__.return_value = None
with mock.patch("imgaug.multicore.Pool", mock_Pool):
with augseq.pool(processes=2, maxtasksperchild=10, seed=17):
pass
assert mock_Pool.call_count == 1
assert mock_Pool.__enter__.call_count == 1
assert mock_Pool.__exit__.call_count == 1
assert mock_Pool.call_args[0][0] == augseq
assert mock_Pool.call_args[1]["processes"] == 2
assert mock_Pool.call_args[1]["maxtasksperchild"] == 10
assert mock_Pool.call_args[1]["seed"] == 17
class TestAugmenter_find_augmenters_by_name(unittest.TestCase):
def setUp(self):
reseed()
@property
def seq(self):
noop1 = iaa.Identity(name="Identity")
fliplr = iaa.Fliplr(name="Fliplr")
flipud = iaa.Flipud(name="Flipud")
noop2 = iaa.Identity(name="Identity2")
seq2 = iaa.Sequential([flipud, noop2], name="Seq2")
seq1 = iaa.Sequential([noop1, fliplr, seq2], name="Seq")
return seq1, seq2
def test_find_top_element(self):
seq1, seq2 = self.seq
augs = seq1.find_augmenters_by_name("Seq")
assert len(augs) == 1
assert augs[0] == seq1
def test_find_nested_element(self):
seq1, seq2 = self.seq
augs = seq1.find_augmenters_by_name("Seq2")
assert len(augs) == 1
assert augs[0] == seq2
def test_find_list_of_names(self):
seq1, seq2 = self.seq
augs = seq1.find_augmenters_by_names(["Seq", "Seq2"])
assert len(augs) == 2
assert augs[0] == seq1
assert augs[1] == seq2
def test_find_by_regex(self):
seq1, seq2 = self.seq
augs = seq1.find_augmenters_by_name(r"Seq.*", regex=True)
assert len(augs) == 2
assert augs[0] == seq1
assert augs[1] == seq2
class TestAugmenter_find_augmenters(unittest.TestCase):
def setUp(self):
reseed()
@property
def seq(self):
noop1 = iaa.Identity(name="Identity")
fliplr = iaa.Fliplr(name="Fliplr")
flipud = iaa.Flipud(name="Flipud")
noop2 = iaa.Identity(name="Identity2")
seq2 = iaa.Sequential([flipud, noop2], name="Seq2")
seq1 = iaa.Sequential([noop1, fliplr, seq2], name="Seq")
return seq1, seq2
def test_find_by_list_of_names(self):
def _func(aug, parents):
return aug.name in ["Seq", "Seq2"]
seq1, seq2 = self.seq
augs = seq1.find_augmenters(_func)
assert len(augs) == 2
assert augs[0] == seq1
assert augs[1] == seq2
def test_use_parents_arg(self):
def _func(aug, parents):
return (
aug.name in ["Seq", "Seq2"]
and len(parents) > 0
)
seq1, seq2 = self.seq
augs = seq1.find_augmenters(_func)
assert len(augs) == 1
assert augs[0] == seq2
def test_find_by_list_of_names_flat_false(self):
def _func(aug, parents):
return aug.name in ["Seq", "Seq2"]
seq1, seq2 = self.seq
augs = seq1.find_augmenters(_func, flat=False)
assert len(augs) == 2
assert augs[0] == seq1
assert augs[1] == [seq2]
class TestAugmenter_remove(unittest.TestCase):
def setUp(self):
reseed()
@property
def seq(self):
noop1 = iaa.Identity(name="Identity")
fliplr = iaa.Fliplr(name="Fliplr")
flipud = iaa.Flipud(name="Flipud")
noop2 = iaa.Identity(name="Identity2")
seq2 = iaa.Sequential([flipud, noop2], name="Seq2")
seq1 = iaa.Sequential([noop1, fliplr, seq2], name="Seq")
return seq1
def test_remove_by_name(self):
def _func(aug, parents):
return aug.name == "Seq2"
augs = self.seq
augs = augs.remove_augmenters(_func)
seqs = augs.find_augmenters_by_name(r"Seq.*", regex=True)
assert len(seqs) == 1
assert seqs[0].name == "Seq"
def test_remove_by_name_and_parents_arg(self):
def _func(aug, parents):
return aug.name == "Seq2" and len(parents) == 0
augs = self.seq
augs = augs.remove_augmenters(_func)
seqs = augs.find_augmenters_by_name(r"Seq.*", regex=True)
assert len(seqs) == 2
assert seqs[0].name == "Seq"
assert seqs[1].name == "Seq2"
def test_remove_all_without_inplace_removal(self):
def _func(aug, parents):
return True
augs = self.seq
augs = augs.remove_augmenters(_func)
assert augs is not None
assert isinstance(augs, iaa.Identity)
def test_remove_all_with_inplace_removal(self):
def _func(aug, parents):
return aug.name == "Seq"
augs = self.seq
got_exception = False
try:
_ = augs.remove_augmenters(_func, copy=False)
except Exception as exc:
got_exception = True
expected = (
"Inplace removal of topmost augmenter requested, "
"which is currently not possible")
assert expected in str(exc)
assert got_exception
def test_remove_all_without_inplace_removal_and_no_identity(self):
def _func(aug, parents):
return True
augs = self.seq
augs = augs.remove_augmenters(_func, identity_if_topmost=False)
assert augs is None
def test_remove_all_without_inplace_removal_and_no_noop(self):
def _func(aug, parents):
return True
augs = self.seq
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
augs = augs.remove_augmenters(_func, noop_if_topmost=False)
assert len(caught_warnings) == 1
assert "deprecated" in str(caught_warnings[-1].message)
assert augs is None
class TestAugmenter_copy_random_state(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
return ia.data.quokka_square(size=(128, 128))
@property
def images(self):
return np.array([self.image] * 64, dtype=np.uint8)
@property
def source(self):
source = iaa.Sequential([
iaa.Fliplr(0.5, name="hflip"),
iaa.Dropout(0.05, name="dropout"),
iaa.Affine(translate_px=(-10, 10), name="translate",
seed=3),
iaa.GaussianBlur(1.0, name="blur", seed=4)
], seed=5)
return source
@property
def target(self):
target = iaa.Sequential([
iaa.Fliplr(0.5, name="hflip"),
iaa.Dropout(0.05, name="dropout"),
iaa.Affine(translate_px=(-10, 10), name="translate")
])
return target
def test_matching_position(self):
def _func(aug, parents):
return aug.name == "blur"
images = self.images
source = self.source
target = self.target
source.localize_random_state_()
target_cprs = target.copy_random_state(source, matching="position")
source_alt = source.remove_augmenters(_func)
images_aug_source = source_alt.augment_images(images)
images_aug_target = target_cprs.augment_images(images)
assert target_cprs.random_state.equals(source_alt.random_state)
for i in sm.xrange(3):
assert target_cprs[i].random_state.equals(
source_alt[i].random_state)
assert np.array_equal(images_aug_source, images_aug_target)
def test_matching_position_copy_determinism(self):
def _func(aug, parents):
return aug.name == "blur"
images = self.images
source = self.source
target = self.target
source.localize_random_state_()
source[0].deterministic = True
target_cprs = target.copy_random_state(
source, matching="position", copy_determinism=True)
source_alt = source.remove_augmenters(_func)
images_aug_source = source_alt.augment_images(images)
images_aug_target = target_cprs.augment_images(images)
assert target_cprs[0].deterministic is True
assert np.array_equal(images_aug_source, images_aug_target)
def test_matching_name(self):
def _func(aug, parents):
return aug.name == "blur"
images = self.images
source = self.source
target = self.target
source.localize_random_state_()
target_cprs = target.copy_random_state(source, matching="name")
source_alt = source.remove_augmenters(_func)
images_aug_source = source_alt.augment_images(images)
images_aug_target = target_cprs.augment_images(images)
assert np.array_equal(images_aug_source, images_aug_target)
def test_matching_name_copy_determinism(self):
def _func(aug, parents):
return aug.name == "blur"
images = self.images
source = self.source
target = self.target
source.localize_random_state_()
source_alt = source.remove_augmenters(_func)
source_det = source_alt.to_deterministic()
target_cprs_det = target.copy_random_state(
source_det, matching="name", copy_determinism=True)
images_aug_source1 = source_det.augment_images(images)
images_aug_target1 = target_cprs_det.augment_images(images)
images_aug_source2 = source_det.augment_images(images)
images_aug_target2 = target_cprs_det.augment_images(images)
assert np.array_equal(images_aug_source1, images_aug_source2)
assert np.array_equal(images_aug_target1, images_aug_target2)
assert np.array_equal(images_aug_source1, images_aug_target1)
assert np.array_equal(images_aug_source2, images_aug_target2)
def test_copy_fails_when_source_rngs_are_not_localized__name(self):
source = iaa.Fliplr(0.5, name="hflip")
target = iaa.Fliplr(0.5, name="hflip")
got_exception = False
try:
_ = target.copy_random_state(source, matching="name")
except Exception as exc:
got_exception = True
assert "localize_random_state" in str(exc)
assert got_exception
def test_copy_fails_when_source_rngs_are_not_localized__position(self):
source = iaa.Fliplr(0.5, name="hflip")
target = iaa.Fliplr(0.5, name="hflip")
got_exception = False
try:
_ = target.copy_random_state(source, matching="position")
except Exception as exc:
got_exception = True
assert "localize_random_state" in str(exc)
assert got_exception
def test_copy_fails_when_names_not_match_and_matching_not_tolerant(self):
source = iaa.Fliplr(0.5, name="hflip-other-name")
target = iaa.Fliplr(0.5, name="hflip")
source.localize_random_state_()
got_exception = False
try:
_ = target.copy_random_state(
source, matching="name", matching_tolerant=False)
except Exception as exc:
got_exception = True
assert "not found among source augmenters" in str(exc)
assert got_exception
def test_copy_fails_for_not_tolerant_position_matching(self):
source = iaa.Sequential([iaa.Fliplr(0.5, name="hflip"),
iaa.Fliplr(0.5, name="hflip2")])
target = iaa.Sequential([iaa.Fliplr(0.5, name="hflip")])
source.localize_random_state_()
got_exception = False
try:
_ = target.copy_random_state(
source, matching="position", matching_tolerant=False)
except Exception as exc:
got_exception = True
assert "different lengths" in str(exc)
assert got_exception
def test_copy_fails_for_unknown_matching_method(self):
source = iaa.Sequential([iaa.Fliplr(0.5, name="hflip"),
iaa.Fliplr(0.5, name="hflip2")])
target = iaa.Sequential([iaa.Fliplr(0.5, name="hflip")])
source.localize_random_state_()
got_exception = False
try:
_ = target.copy_random_state(source, matching="test")
except Exception as exc:
got_exception = True
assert "Unknown matching method" in str(exc)
assert got_exception
def test_warn_if_multiple_augmenters_with_same_name(self):
source = iaa.Sequential([iaa.Fliplr(0.5, name="hflip"),
iaa.Fliplr(0.5, name="hflip")])
target = iaa.Sequential([iaa.Fliplr(0.5, name="hflip")])
source.localize_random_state_()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = target.copy_random_state(source, matching="name")
assert len(caught_warnings) == 1
assert (
"contains multiple augmenters with the same name"
in str(caught_warnings[-1].message)
)
# TODO these tests change the input type from list to array. Might be
# reasonable to change and test that scenario separetely
class TestAugmenterHooks(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
image = np.array([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]], dtype=np.uint8)
return np.atleast_3d(image)
@property
def image_lr(self):
image_lr = np.array([[1, 0, 0],
[1, 0, 0],
[1, 1, 0]], dtype=np.uint8)
return np.atleast_3d(image_lr)
@property
def image_lrud(self):
image_lrud = np.array([[1, 1, 0],
[1, 0, 0],
[1, 0, 0]], dtype=np.uint8)
return np.atleast_3d(image_lrud)
def test_preprocessor(self):
def preprocessor(images, augmenter, parents):
img = np.copy(images)
img[0][1, 1, 0] += 1
return img
hooks = ia.HooksImages(preprocessor=preprocessor)
seq = iaa.Sequential([iaa.Fliplr(1.0), iaa.Flipud(1.0)])
images_aug = seq.augment_images([self.image], hooks=hooks)
expected = np.copy(self.image_lrud)
expected[1, 1, 0] = 3
assert np.array_equal(images_aug[0], expected)
def test_postprocessor(self):
def postprocessor(images, augmenter, parents):
img = np.copy(images)
img[0][1, 1, 0] += 1
return img
hooks = ia.HooksImages(postprocessor=postprocessor)
seq = iaa.Sequential([iaa.Fliplr(1.0), iaa.Flipud(1.0)])
images_aug = seq.augment_images([self.image], hooks=hooks)
expected = np.copy(self.image_lrud)
expected[1, 1, 0] = 3
assert np.array_equal(images_aug[0], expected)
def test_propagator(self):
def propagator(images, augmenter, parents, default):
if "Seq" in augmenter.name:
return False
else:
return default
hooks = ia.HooksImages(propagator=propagator)
seq = iaa.Sequential([iaa.Fliplr(1.0), iaa.Flipud(1.0)])
images_aug = seq.augment_images([self.image], hooks=hooks)
assert np.array_equal(images_aug[0], self.image)
def test_activator(self):
def activator(images, augmenter, parents, default):
if "Flipud" in augmenter.name:
return False
else:
return default
hooks = ia.HooksImages(activator=activator)
seq = iaa.Sequential([iaa.Fliplr(1.0), iaa.Flipud(1.0)])
images_aug = seq.augment_images([self.image], hooks=hooks)
assert np.array_equal(images_aug[0], self.image_lr)
def test_activator_keypoints(self):
def activator(keypoints_on_images, augmenter, parents, default):
return False
hooks = ia.HooksKeypoints(activator=activator)
kps = [ia.Keypoint(x=1, y=0), ia.Keypoint(x=2, y=0),
ia.Keypoint(x=2, y=1)]
kpsoi = ia.KeypointsOnImage(kps, shape=(5, 10, 3))
aug = iaa.Affine(translate_px=1)
keypoints_aug = aug.augment_keypoints(kpsoi, hooks=hooks)
assert keypoints_equal([keypoints_aug], [kpsoi])
class TestAugmenterWithLoadedImages(unittest.TestCase):
def setUp(self):
reseed()
def test_with_cv2(self):
image = np.arange(10*20).astype(np.uint8).reshape((10, 20, 1))
image = np.tile(image, (1, 1, 3))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
images = image[np.newaxis, :, :, :]
image_cp = np.copy(image)
images_cp = np.copy(images)
aug_arrs = _InplaceDummyAugmenterImgsArray(1)
aug_lists = _InplaceDummyAugmenterImgsList(1)
with TemporaryDirectory() as dirpath:
imgpath = os.path.join(dirpath, "temp_cv2.png")
imageio.imwrite(imgpath, image)
image_reloaded = cv2.imread(imgpath)[:, :, ::-1]
images_reloaded = image_reloaded[np.newaxis, :, :, :]
image_aug = aug_lists(image=image_reloaded)
assert image_aug is not image_reloaded
assert np.array_equal(image_reloaded, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
image_aug = aug_lists.augment_image(image=image_reloaded)
assert image_aug is not image_reloaded
assert np.array_equal(image_reloaded, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
images_aug = aug_arrs(images=images_reloaded)
assert images_aug is not images_reloaded
assert np.array_equal(images_reloaded, images_cp)
assert np.array_equal(images_aug, images_cp + 1)
images_aug = aug_arrs.augment_images(images=images_reloaded)
assert images_aug is not images_reloaded
assert np.array_equal(images_reloaded, images_cp)
assert np.array_equal(images_aug, images_cp + 1)
def test_with_imageio(self):
image = np.arange(10*20).astype(np.uint8).reshape((10, 20, 1))
image = np.tile(image, (1, 1, 3))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
images = image[np.newaxis, :, :, :]
image_cp = np.copy(image)
images_cp = np.copy(images)
aug_arrs = _InplaceDummyAugmenterImgsArray(1)
aug_lists = _InplaceDummyAugmenterImgsList(1)
with TemporaryDirectory() as dirpath:
imgpath = os.path.join(dirpath, "temp_imageio.png")
imageio.imwrite(imgpath, image)
image_reloaded = imageio.imread(imgpath)
images_reloaded = image_reloaded[np.newaxis, :, :, :]
image_aug = aug_lists(image=image_reloaded)
assert image_aug is not image_reloaded
assert np.array_equal(image_reloaded, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
image_aug = aug_lists.augment_image(image=image_reloaded)
assert image_aug is not image_reloaded
assert np.array_equal(image_reloaded, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
images_aug = aug_arrs(images=images_reloaded)
assert images_aug is not images_reloaded
assert np.array_equal(images_reloaded, images_cp)
assert np.array_equal(images_aug, images_cp + 1)
images_aug = aug_arrs.augment_images(images=images_reloaded)
assert images_aug is not images_reloaded
assert np.array_equal(images_reloaded, images_cp)
assert np.array_equal(images_aug, images_cp + 1)
def test_with_pil(self):
fnames = ["asarray", "array"]
for fname in fnames:
with self.subTest(fname=fname):
image = np.arange(10*20).astype(np.uint8).reshape((10, 20, 1))
image = np.tile(image, (1, 1, 3))
image[:, :, 0] += 0
image[:, :, 1] += 1
image[:, :, 2] += 2
images = image[np.newaxis, :, :, :]
image_cp = np.copy(image)
images_cp = np.copy(images)
aug_arrs = _InplaceDummyAugmenterImgsArray(1)
aug_lists = _InplaceDummyAugmenterImgsList(1)
with TemporaryDirectory() as dirpath:
imgpath = os.path.join(dirpath,
"temp_pil_%s.png" % (fname,))
imageio.imwrite(imgpath, image)
image_reloaded = getattr(np, fname)(PIL.Image.open(imgpath))
images_reloaded = image_reloaded[np.newaxis, :, :, :]
image_aug = aug_lists(image=image_reloaded)
assert image_aug is not image_reloaded
assert np.array_equal(image_reloaded, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
image_aug = aug_lists.augment_image(image=image_reloaded)
assert image_aug is not image_reloaded
assert np.array_equal(image_reloaded, image_cp)
assert np.array_equal(image_aug, image_cp + 1)
images_aug = aug_arrs(images=images_reloaded)
assert images_aug is not images_reloaded
assert np.array_equal(images_reloaded, images_cp)
assert np.array_equal(images_aug, images_cp + 1)
images_aug = aug_arrs.augment_images(images=images_reloaded)
assert images_aug is not images_reloaded
assert np.array_equal(images_reloaded, images_cp)
assert np.array_equal(images_aug, images_cp + 1)
class TestSequential(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
image = np.array([[0, 1, 1],
[0, 0, 1],
[0, 0, 1]], dtype=np.uint8) * 255
return np.atleast_3d(image)
@property
def images(self):
return np.array([self.image], dtype=np.uint8)
@property
def image_lr(self):
image_lr = np.array([[1, 1, 0],
[1, 0, 0],
[1, 0, 0]], dtype=np.uint8) * 255
return np.atleast_3d(image_lr)
@property
def images_lr(self):
return np.array([self.image_lr], dtype=np.uint8)
@property
def image_ud(self):
image_ud = np.array([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]], dtype=np.uint8) * 255
return np.atleast_3d(image_ud)
@property
def images_ud(self):
return np.array([self.image_ud], dtype=np.uint8)
@property
def image_lr_ud(self):
image_lr_ud = np.array([[1, 0, 0],
[1, 0, 0],
[1, 1, 0]], dtype=np.uint8) * 255
return np.atleast_3d(image_lr_ud)
@property
def images_lr_ud(self):
return np.array([self.image_lr_ud])
@property
def keypoints(self):
kps = [ia.Keypoint(x=1, y=0),
ia.Keypoint(x=2, y=0),
ia.Keypoint(x=2, y=1)]
return ia.KeypointsOnImage(kps, shape=self.image.shape)
@property
def keypoints_aug(self):
kps = [ia.Keypoint(x=3-1, y=3-0),
ia.Keypoint(x=3-2, y=3-0),
ia.Keypoint(x=3-2, y=3-1)]
return ia.KeypointsOnImage(kps, shape=self.image.shape)
@property
def polygons(self):
polygon = ia.Polygon([(0, 0), (2, 0), (2, 2), (0, 2)])
return ia.PolygonsOnImage([polygon], shape=self.image.shape)
@property
def polygons_aug(self):
polygon = ia.Polygon([(3-0, 3-0), (3-2, 3-0), (3-2, 3-2), (3-0, 3-2)])
return ia.PolygonsOnImage([polygon], shape=self.image.shape)
@property
def lsoi(self):
ls = ia.LineString([(0, 0), (2, 0), (2, 2), (0, 2)])
return ia.LineStringsOnImage([ls], shape=self.image.shape)
@property
def lsoi_aug(self):
ls = ia.LineString([(3-0, 3-0), (3-2, 3-0), (3-2, 3-2), (3-0, 3-2)])
return ia.LineStringsOnImage([ls], shape=self.image.shape)
@property
def bbsoi(self):
bb = ia.BoundingBox(x1=0, y1=0, x2=2, y2=2)
return ia.BoundingBoxesOnImage([bb], shape=self.image.shape)
@property
def bbsoi_aug(self):
x1 = 3-0
x2 = 3-2
y1 = 3-0
y2 = 3-2
bb = ia.BoundingBox(x1=min(x1, x2), y1=min(y1, y2),
x2=max(x1, x2), y2=max(y1, y2))
return ia.BoundingBoxesOnImage([bb], shape=self.image.shape)
@property
def heatmaps(self):
heatmaps_arr = np.float32([[0, 0, 1.0],
[0, 0, 1.0],
[0, 1.0, 1.0]])
return ia.HeatmapsOnImage(heatmaps_arr, shape=self.image.shape)
@property
def heatmaps_aug(self):
heatmaps_arr_expected = np.float32([[1.0, 1.0, 0.0],
[1.0, 0, 0],
[1.0, 0, 0]])
return ia.HeatmapsOnImage(heatmaps_arr_expected, shape=self.image.shape)
@property
def segmaps(self):
segmaps_arr = np.int32([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]])
return ia.SegmentationMapsOnImage(segmaps_arr, shape=self.image.shape)
@property
def segmaps_aug(self):
segmaps_arr_expected = np.int32([[1, 1, 0],
[1, 0, 0],
[1, 0, 0]])
return ia.SegmentationMapsOnImage(segmaps_arr_expected,
shape=self.image.shape)
@property
def seq_two_flips(self):
return iaa.Sequential([
iaa.Fliplr(1.0),
iaa.Flipud(1.0)
])
def test_images__two_flips(self):
aug = self.seq_two_flips
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_lr_ud)
def test_images__two_flips__deterministic(self):
aug = self.seq_two_flips
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert np.array_equal(observed, self.images_lr_ud)
def test_images_as_list__two_flips(self):
aug = self.seq_two_flips
observed = aug.augment_images([self.image])
assert array_equal_lists(observed, [self.image_lr_ud])
def test_images_as_list__two_flips__deterministic(self):
aug = self.seq_two_flips
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.image])
assert array_equal_lists(observed, [self.image_lr_ud])
def test_keypoints__two_flips(self):
aug = self.seq_two_flips
observed = aug.augment_keypoints([self.keypoints])
assert_cbaois_equal(observed, [self.keypoints_aug])
def test_keypoints__two_flips__deterministic(self):
aug = self.seq_two_flips
aug_det = aug.to_deterministic()
observed = aug_det.augment_keypoints([self.keypoints])
assert_cbaois_equal(observed, [self.keypoints_aug])
def test_polygons__two_flips(self):
aug = self.seq_two_flips
observed = aug.augment_polygons(self.polygons)
assert_cbaois_equal(observed, self.polygons_aug)
def test_polygons__two_flips__deterministic(self):
aug = self.seq_two_flips
aug_det = aug.to_deterministic()
observed = aug_det.augment_polygons(self.polygons)
assert_cbaois_equal(observed, self.polygons_aug)
def test_line_strings__two_flips(self):
aug = self.seq_two_flips
observed = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi_aug)
def test_line_strings__two_flips__deterministic(self):
aug = self.seq_two_flips
aug_det = aug.to_deterministic()
observed = aug_det.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi_aug)
def test_bounding_boxes__two_flips(self):
aug = self.seq_two_flips
observed = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi_aug)
def test_bounding_boxes__two_flips__deterministic(self):
aug = self.seq_two_flips
aug_det = aug.to_deterministic()
observed = aug_det.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi_aug)
def test_heatmaps__two_flips(self):
aug = self.seq_two_flips
heatmaps = self.heatmaps
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == (3, 3, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1.0 - 1e-6 < observed.max_value < 1.0 + 1e-6
assert np.allclose(observed.get_arr(),
self.heatmaps_aug.get_arr())
def test_segmentation_maps__two_flips(self):
aug = self.seq_two_flips
segmaps = self.segmaps
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == (3, 3, 1)
assert np.array_equal(observed.get_arr(),
self.segmaps_aug.get_arr())
def test_children_not_provided(self):
aug = iaa.Sequential()
image = np.arange(4*4).reshape((4, 4)).astype(np.uint8)
observed = aug.augment_image(image)
assert np.array_equal(observed, image)
def test_children_are_none(self):
aug = iaa.Sequential(children=None)
image = np.arange(4*4).reshape((4, 4)).astype(np.uint8)
observed = aug.augment_image(image)
assert np.array_equal(observed, image)
def test_children_is_single_augmenter_without_list(self):
aug = iaa.Sequential(iaa.Fliplr(1.0))
image = np.arange(4*4).reshape((4, 4)).astype(np.uint8)
observed = aug.augment_image(image)
assert np.array_equal(observed, np.fliplr(image))
def test_children_is_a_sequential(self):
aug = iaa.Sequential(iaa.Sequential(iaa.Fliplr(1.0)))
image = np.arange(4*4).reshape((4, 4)).astype(np.uint8)
observed = aug.augment_image(image)
assert np.array_equal(observed, np.fliplr(image))
def test_children_is_list_of_sequentials(self):
aug = iaa.Sequential([
iaa.Sequential(iaa.Flipud(1.0)),
iaa.Sequential(iaa.Fliplr(1.0))
])
image = np.arange(4*4).reshape((4, 4)).astype(np.uint8)
observed = aug.augment_image(image)
assert np.array_equal(observed, np.fliplr(np.flipud(image)))
def test_randomness__two_flips(self):
# 50% horizontal flip, 50% vertical flip
aug = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.Flipud(0.5)
])
frac_same = self._test_randomness__two_flips__compute_fraction_same(
aug, 200)
assert np.isclose(frac_same, 0.25, rtol=0, atol=0.1)
def test_randomness__two_flips__deterministic(self):
# 50% horizontal flip, 50% vertical flip
aug = iaa.Sequential([
iaa.Fliplr(0.5),
iaa.Flipud(0.5)
])
aug_det = aug.to_deterministic()
frac_same = self._test_randomness__two_flips__compute_fraction_same(
aug_det, 200)
assert (
np.isclose(frac_same, 0.0, rtol=0, atol=1e-5)
or np.isclose(frac_same, 1.0, rtol=0, atol=1e-5)
)
def _test_randomness__two_flips__compute_fraction_same(self, aug,
nb_iterations):
expected = [self.images, self.images_lr, self.images_ud,
self.images_lr_ud]
last_aug = None
nb_changed_aug = 0
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(self.images)
if i == 0:
last_aug = observed_aug
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
last_aug = observed_aug
assert np.any([np.array_equal(observed_aug, expected_i)
for expected_i in expected])
# should be the same in roughly 25% of all cases
frac_changed = nb_changed_aug / nb_iterations
return 1 - frac_changed
def test_random_order_true_images(self):
aug = iaa.Sequential([
iaa.Affine(translate_px={"x": 1}, mode="constant", cval=0, order=0),
iaa.Fliplr(1.0)
], random_order=True)
frac_12 = self._test_random_order_images_frac_12(aug, 200)
assert np.isclose(frac_12, 0.5, 0.075)
def test_random_order_false_images(self):
aug = iaa.Sequential([
iaa.Affine(translate_px={"x": 1}, mode="constant", cval=0, order=0),
iaa.Fliplr(1.0)
], random_order=False)
frac_12 = self._test_random_order_images_frac_12(aug, 25)
assert frac_12 >= 1.0 - 1e-4
def test_random_order_true_deterministic_images(self):
aug = iaa.Sequential([
iaa.Affine(translate_px={"x": 1}, mode="constant", cval=0, order=0),
iaa.Fliplr(1.0)
], random_order=True)
aug = aug.to_deterministic()
frac_12 = self._test_random_order_images_frac_12(aug, 25)
assert (frac_12 >= 1.0-1e-4 or frac_12 <= 0.0+1e-4)
@classmethod
def _test_random_order_images_frac_12(cls, aug, nb_iterations):
image = np.uint8([[0, 1],
[2, 3]])
image_12 = np.uint8([[0, 0],
[2, 0]])
image_21 = np.uint8([[0, 1],
[0, 3]])
seen = [False, False]
for _ in sm.xrange(nb_iterations):
observed = aug.augment_images([image])[0]
if np.array_equal(observed, image_12):
seen[0] = True
elif np.array_equal(observed, image_21):
seen[1] = True
else:
assert False
frac_12 = seen[0] / np.sum(seen)
return frac_12
# TODO add random_order=False
def test_random_order_heatmaps(self):
aug = iaa.Sequential([
iaa.Affine(translate_px={"x": 1}),
iaa.Fliplr(1.0)
], random_order=True)
heatmaps_arr = np.float32([[0, 0, 1.0],
[0, 0, 1.0],
[0, 1.0, 1.0]])
heatmaps_arr_expected1 = np.float32([[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0]])
heatmaps_arr_expected2 = np.float32([[0.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 1.0, 1.0]])
seen = [False, False]
for _ in sm.xrange(100):
observed = aug.augment_heatmaps([
ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))])[0]
if np.allclose(observed.get_arr(), heatmaps_arr_expected1):
seen[0] = True
elif np.allclose(observed.get_arr(), heatmaps_arr_expected2):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
# TODO add random_order=False
def test_random_order_segmentation_maps(self):
aug = iaa.Sequential([
iaa.Affine(translate_px={"x": 1}),
iaa.Fliplr(1.0)
], random_order=True)
segmaps_arr = np.int32([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]])
segmaps_arr_expected1 = np.int32([[0, 0, 0],
[0, 0, 0],
[1, 0, 0]])
segmaps_arr_expected2 = np.int32([[0, 1, 0],
[0, 1, 0],
[0, 1, 1]])
seen = [False, False]
for _ in sm.xrange(100):
observed = aug.augment_segmentation_maps([
SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))])[0]
if np.array_equal(observed.get_arr(), segmaps_arr_expected1):
seen[0] = True
elif np.array_equal(observed.get_arr(), segmaps_arr_expected2):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
# TODO add random_order=False
def test_random_order_keypoints(self):
KP = ia.Keypoint
kps = [KP(0, 0), KP(2, 0), KP(2, 2)]
kps_12 = [KP((0+1)*2, 0), KP((2+1)*2, 0), KP((2+1)*2, 2)]
kps_21 = [KP((0*2)+1, 0), KP((2*2)+1, 0), KP((2*2)+1, 2)]
kpsoi = ia.KeypointsOnImage(kps, shape=(3, 3))
kpsoi_12 = ia.KeypointsOnImage(kps_12, shape=(3, 3))
kpsoi_21 = ia.KeypointsOnImage(kps_21, shape=(3, 3))
def func1(keypoints_on_images, random_state, parents, hooks):
for kpsoi in keypoints_on_images:
for kp in kpsoi.keypoints:
kp.x += 1
return keypoints_on_images
def func2(keypoints_on_images, random_state, parents, hooks):
for kpsoi in keypoints_on_images:
for kp in kpsoi.keypoints:
kp.x *= 2
return keypoints_on_images
aug_1 = iaa.Lambda(func_keypoints=func1)
aug_2 = iaa.Lambda(func_keypoints=func2)
seq = iaa.Sequential([aug_1, aug_2], random_order=True)
seen = [False, False]
for _ in sm.xrange(100):
observed = seq.augment_keypoints(kpsoi)
if np.allclose(observed.to_xy_array(), kpsoi_12.to_xy_array()):
seen[0] = True
elif np.allclose(observed.to_xy_array(), kpsoi_21.to_xy_array()):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
# TODO add random_order=False
def test_random_order_polygons(self):
cba = ia.Polygon([(0, 0), (1, 0), (1, 1)])
cba_12 = ia.Polygon([(0, 0), (1, 0), ((1+1)*2, 1)])
cba_21 = ia.Polygon([(0, 0), (1, 0), ((1*2)+1, 1)])
cbaoi = ia.PolygonsOnImage([cba], shape=(3, 3))
def func1(polygons_on_images, random_state, parents, hooks):
for cbaoi_ in polygons_on_images:
for cba_ in cbaoi_.items:
cba_.exterior[-1, 0] += 1
return polygons_on_images
def func2(polygons_on_images, random_state, parents, hooks):
for cbaoi_ in polygons_on_images:
for cba_ in cbaoi_.items:
cba_.exterior[-1, 0] *= 2
return polygons_on_images
aug_1 = iaa.Lambda(func_polygons=func1)
aug_2 = iaa.Lambda(func_polygons=func2)
seq = iaa.Sequential([aug_1, aug_2], random_order=True)
seen = [False, False]
for _ in sm.xrange(100):
observed = seq.augment_polygons(cbaoi)
if np.allclose(observed.items[0].coords, cba_12.coords):
seen[0] = True
elif np.allclose(observed.items[0].coords, cba_21.coords):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
# TODO add random_order=False
def test_random_order_line_strings(self):
cba = ia.LineString([(0, 0), (1, 0), (1, 1)])
cba_12 = ia.LineString([(0, 0), (1, 0), ((1+1)*2, 1)])
cba_21 = ia.LineString([(0, 0), (1, 0), ((1*2)+1, 1)])
cbaoi = ia.LineStringsOnImage([cba], shape=(3, 3))
def func1(line_strings_on_images, random_state, parents, hooks):
for cbaoi_ in line_strings_on_images:
for cba_ in cbaoi_.items:
cba_.coords[-1, 0] += 1
return line_strings_on_images
def func2(line_strings_on_images, random_state, parents, hooks):
for cbaoi_ in line_strings_on_images:
for cba_ in cbaoi_.items:
cba_.coords[-1, 0] *= 2
return line_strings_on_images
aug_1 = iaa.Lambda(func_line_strings=func1)
aug_2 = iaa.Lambda(func_line_strings=func2)
seq = iaa.Sequential([aug_1, aug_2], random_order=True)
seen = [False, False]
for _ in sm.xrange(100):
observed = seq.augment_line_strings(cbaoi)
if np.allclose(observed.items[0].coords, cba_12.coords):
seen[0] = True
elif np.allclose(observed.items[0].coords, cba_21.coords):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
# TODO add random_order=False
def test_random_order_bounding_boxes(self):
bbs = [ia.BoundingBox(x1=1, y1=2, x2=30, y2=40)]
bbs_12 = [ia.BoundingBox(x1=(1+1)*2, y1=2, x2=30, y2=40)]
bbs_21 = [ia.BoundingBox(x1=(1*2)+1, y1=2, x2=30, y2=40)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(3, 3))
bbsoi_12 = ia.BoundingBoxesOnImage(bbs_12, shape=(3, 3))
bbsoi_21 = ia.BoundingBoxesOnImage(bbs_21, shape=(3, 3))
def func1(bounding_boxes_on_images, random_state, parents, hooks):
for bbsoi in bounding_boxes_on_images:
for bb in bbsoi.bounding_boxes:
bb.x1 += 1
return bounding_boxes_on_images
def func2(bounding_boxes_on_images, random_state, parents, hooks):
for bbsoi in bounding_boxes_on_images:
for bb in bbsoi.bounding_boxes:
bb.x1 *= 2
return bounding_boxes_on_images
aug_1 = iaa.Lambda(func_bounding_boxes=func1)
aug_2 = iaa.Lambda(func_bounding_boxes=func2)
seq = iaa.Sequential([aug_1, aug_2], random_order=True)
seen = [False, False]
for _ in sm.xrange(100):
observed = seq.augment_bounding_boxes(bbsoi)
if np.allclose(observed.to_xyxy_array(),
bbsoi_12.to_xyxy_array()):
seen[0] = True
elif np.allclose(observed.to_xyxy_array(),
bbsoi_21.to_xyxy_array()):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
for random_order in [False, True]:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Sequential([iaa.Identity()],
random_order=random_order)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
for random_order in [False, True]:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Sequential([iaa.Identity()],
random_order=random_order)
image_aug = aug(image=image)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_add_to_empty_sequential(self):
aug = iaa.Sequential()
aug.add(iaa.Fliplr(1.0))
image = np.arange(4*4).reshape((4, 4)).astype(np.uint8)
observed = aug.augment_image(image)
assert np.array_equal(observed, np.fliplr(image))
def test_add_to_sequential_with_child(self):
aug = iaa.Sequential(iaa.Fliplr(1.0))
aug.add(iaa.Flipud(1.0))
image = np.arange(4*4).reshape((4, 4)).astype(np.uint8)
observed = aug.augment_image(image)
assert np.array_equal(observed, np.fliplr(np.flipud(image)))
def test_get_parameters(self):
aug1 = iaa.Sequential(iaa.Fliplr(1.0), random_order=False)
aug2 = iaa.Sequential(iaa.Fliplr(1.0), random_order=True)
assert aug1.get_parameters() == [False]
assert aug2.get_parameters() == [True]
def test_get_children_lists(self):
flip = iaa.Fliplr(1.0)
aug = iaa.Sequential(flip)
assert aug.get_children_lists() == [aug]
def test_to_deterministic(self):
child = iaa.Identity()
aug = iaa.Sequential([child])
aug_det = aug.to_deterministic()
assert aug_det.random_state is not aug.random_state
assert aug_det.deterministic
assert aug_det[0].deterministic
def test___str___and___repr__(self):
flip = iaa.Fliplr(1.0)
aug = iaa.Sequential(flip, random_order=True)
expected = (
"Sequential("
"name=%s, random_order=%s, children=[%s], deterministic=%s"
")" % (aug.name, "True", str(flip), "False")
)
assert aug.__str__() == aug.__repr__() == expected
def test_other_dtypes_noop__bool(self):
for random_order in [False, True]:
aug = iaa.Sequential([
iaa.Identity(),
iaa.Identity()
], random_order=random_order)
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == "bool"
assert np.all(image_aug == image)
def test_other_dtypes__noop__uint_int(self):
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype, random_order in itertools.product(dtypes, [False, True]):
with self.subTest(dtype=dtype, random_order=random_order):
aug = iaa.Sequential([
iaa.Identity(),
iaa.Identity()
], random_order=random_order)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, image)
def test_other_dtypes_noop__float(self):
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for random_order in [False, True]:
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype, random_order=random_order):
aug = iaa.Sequential([
iaa.Identity(),
iaa.Identity()
], random_order=random_order)
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == image)
def test_other_dtypes_flips__bool(self):
for random_order in [False, True]:
# note that we use 100% probabilities with square images here,
# so random_order does not influence the output
aug = iaa.Sequential([
iaa.Fliplr(1.0),
iaa.Flipud(1.0)
], random_order=random_order)
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
expected = np.zeros((3, 3), dtype=bool)
expected[2, 2] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == "bool"
assert np.all(image_aug == expected)
def test_other_dtypes__flips__uint_int(self):
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype, random_order in itertools.product(dtypes, [False, True]):
with self.subTest(dtype=dtype, random_order=random_order):
# note that we use 100% probabilities with square images here,
# so random_order does not influence the output
aug = iaa.Sequential([
iaa.Fliplr(1.0),
iaa.Flipud(1.0)
], random_order=random_order)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
expected = np.zeros((3, 3), dtype=dtype)
expected[2, 2] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, expected)
def test_other_dtypes_flips__float(self):
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for random_order in [False, True]:
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype, random_order=random_order):
# note that we use 100% probabilities with square images
# here, so random_order does not influence the output
aug = iaa.Sequential([
iaa.Fliplr(1.0),
iaa.Flipud(1.0)
], random_order=random_order)
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
expected = np.zeros((3, 3), dtype=dtype)
expected[2, 2] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == expected)
def test_pickleable(self):
aug = iaa.Sequential(
[iaa.Add(1, seed=1),
iaa.Multiply(3, seed=2)],
random_order=True,
seed=3)
runtest_pickleable_uint8_img(aug, iterations=5)
class TestSomeOf(unittest.TestCase):
def setUp(self):
reseed()
def test_children_are_empty_list(self):
zeros = np.zeros((3, 3, 1), dtype=np.uint8)
aug = iaa.SomeOf(n=0, children=[])
observed = aug.augment_image(zeros)
assert np.array_equal(observed, zeros)
def test_children_are_not_provided(self):
zeros = np.zeros((3, 3, 1), dtype=np.uint8)
aug = iaa.SomeOf(n=0)
observed = aug.augment_image(zeros)
assert np.array_equal(observed, zeros)
def test_several_children_and_various_fixed_n(self):
zeros = np.zeros((3, 3, 1), dtype=np.uint8)
children = [iaa.Add(1), iaa.Add(2), iaa.Add(3)]
ns = [0, 1, 2, 3, 4, None, (2, None), (2, 2),
iap.Deterministic(3)]
expecteds = [[0], # 0
[9*1, 9*2, 9*3], # 1
[9*1+9*2, 9*1+9*3, 9*2+9*3], # 2
[9*1+9*2+9*3], # 3
[9*1+9*2+9*3], # 4
[9*1+9*2+9*3], # None
[9*1+9*2, 9*1+9*3, 9*2+9*3, 9*1+9*2+9*3], # (2, None)
[9*1+9*2, 9*1+9*3, 9*2+9*3], # (2, 2)
[9*1+9*2+9*3]] # Deterministic(3)
for n, expected in zip(ns, expecteds):
with self.subTest(n=n):
aug = iaa.SomeOf(n=n, children=children)
observed = aug.augment_image(zeros)
assert np.sum(observed) in expected
def test_several_children_and_n_as_tuple(self):
zeros = np.zeros((1, 1, 1), dtype=np.uint8)
augs = [iaa.Add(2**0), iaa.Add(2**1), iaa.Add(2**2)]
aug = iaa.SomeOf(n=(0, 3), children=augs)
nb_iterations = 1000
nb_observed = [0, 0, 0, 0]
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(zeros)
s = observed[0, 0, 0]
if s == 0:
nb_observed[0] += 1
else:
if s & 2**0 > 0:
nb_observed[1] += 1
if s & 2**1 > 0:
nb_observed[2] += 1
if s & 2**2 > 0:
nb_observed[3] += 1
p_observed = [n/nb_iterations for n in nb_observed]
assert np.isclose(p_observed[0], 0.25, rtol=0, atol=0.1)
assert np.isclose(p_observed[1], 0.5, rtol=0, atol=0.1)
assert np.isclose(p_observed[2], 0.5, rtol=0, atol=0.1)
assert np.isclose(p_observed[3], 0.5, rtol=0, atol=0.1)
def test_several_children_and_various_fixed_n__heatmaps(self):
augs = [iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": 1})]
heatmaps_arr = np.float32([[1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 0.0, 0.0]])
heatmaps_arr0 = np.float32([[1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 0.0, 0.0]])
heatmaps_arr1 = np.float32([[0.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 1.0, 0.0]])
heatmaps_arr2 = np.float32([[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0]])
heatmaps_arr3 = np.float32([[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0],
[0.0, 0.0, 0.0]])
ns = [0, 1, 2, 3, None]
expecteds = [[heatmaps_arr0],
[heatmaps_arr1],
[heatmaps_arr2],
[heatmaps_arr3],
[heatmaps_arr3]]
for n, expected in zip(ns, expecteds):
with self.subTest(n=n):
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
aug = iaa.SomeOf(n=n, children=augs)
observed = aug.augment_heatmaps(heatmaps)
assert observed.shape == (3, 3, 3)
assert np.isclose(observed.min_value, 0.0)
assert np.isclose(observed.max_value, 1.0)
matches = [
np.allclose(observed.get_arr(), expected_i)
for expected_i in expected]
assert np.any(matches)
def test_several_children_and_various_fixed_n__segmaps(self):
augs = [iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"x": 1})]
segmaps_arr = np.int32([[1, 0, 0],
[1, 0, 0],
[1, 0, 0]])
segmaps_arr0 = np.int32([[1, 0, 0],
[1, 0, 0],
[1, 0, 0]])
segmaps_arr1 = np.int32([[0, 1, 0],
[0, 1, 0],
[0, 1, 0]])
segmaps_arr2 = np.int32([[0, 0, 1],
[0, 0, 1],
[0, 0, 1]])
segmaps_arr3 = np.int32([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
ns = [0, 1, 2, 3, None]
expecteds = [[segmaps_arr0],
[segmaps_arr1],
[segmaps_arr2],
[segmaps_arr3],
[segmaps_arr3]]
for n, expected in zip(ns, expecteds):
with self.subTest(n=n):
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
aug = iaa.SomeOf(n=n, children=augs)
observed = aug.augment_segmentation_maps(segmaps)
assert observed.shape == (3, 3, 3)
matches = [
np.array_equal(observed.get_arr(), expected_i)
for expected_i in expected]
assert np.any(matches)
def _test_several_children_and_various_fixed_n__cbaois(
self, cbaoi, augf_name):
augs = [iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"y": 1})]
cbaoi_x = cbaoi.shift(x=1)
cbaoi_y = cbaoi.shift(y=1)
cbaoi_xy = cbaoi.shift(x=1, y=1)
ns = [0, 1, 2, None]
expecteds = [[cbaoi],
[cbaoi_x, cbaoi_y],
[cbaoi_xy],
[cbaoi_xy]]
for n, expected in zip(ns, expecteds):
with self.subTest(n=n):
aug = iaa.SomeOf(n=n, children=augs)
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
cba = cbaoi_aug.items[0]
assert len(cbaoi_aug.items) == len(cbaoi.items)
assert cbaoi_aug.shape == (5, 6, 3)
if hasattr(cba, "is_valid"):
assert cba.is_valid
matches = [
cba.coords_almost_equals(cbaoi_i.items[0])
for cbaoi_i in expected
]
assert np.any(matches)
def test_several_children_and_various_fixed_n__keypoints(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1)]
kpsoi = ia.KeypointsOnImage(kps, shape=(5, 6, 3))
self._test_several_children_and_various_fixed_n__cbaois(
kpsoi, "augment_keypoints")
def test_several_children_and_various_fixed_n__polygons(self):
ps = [ia.Polygon([(0, 0), (3, 0), (3, 3), (0, 3)])]
psoi = ia.PolygonsOnImage(ps, shape=(5, 6, 3))
self._test_several_children_and_various_fixed_n__cbaois(
psoi, "augment_polygons")
def test_several_children_and_various_fixed_n__line_strings(self):
ls = [ia.LineString([(0, 0), (3, 0), (3, 3), (0, 3)])]
lsoi = ia.LineStringsOnImage(ls, shape=(5, 6, 3))
self._test_several_children_and_various_fixed_n__cbaois(
lsoi, "augment_line_strings")
def test_several_children_and_various_fixed_n__bounding_boxes(self):
bbs = [ia.BoundingBox(x1=0, y1=0, x2=3, y2=3)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(5, 6, 3))
self._test_several_children_and_various_fixed_n__cbaois(
bbsoi, "augment_bounding_boxes")
@classmethod
def _test_empty_cbaoi(cls, cbaoi, augf_name):
augs = [iaa.Affine(translate_px={"x": 1}),
iaa.Affine(translate_px={"y": 1})]
aug = iaa.SomeOf(n=2, children=augs)
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(cbaoi_aug, cbaoi)
def test_empty_keypoints_on_image_instance(self):
kpsoi = ia.KeypointsOnImage([], shape=(5, 6, 3))
self._test_empty_cbaoi(kpsoi, "augment_keypoints")
def test_empty_polygons_on_image_instance(self):
psoi = ia.PolygonsOnImage([], shape=(5, 6, 3))
self._test_empty_cbaoi(psoi, "augment_polygons")
def test_empty_line_strings_on_image_instance(self):
lsoi = ia.LineStringsOnImage([], shape=(5, 6, 3))
self._test_empty_cbaoi(lsoi, "augment_line_strings")
def test_empty_bounding_boxes_on_image_instance(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(5, 6, 3))
self._test_empty_cbaoi(bbsoi, "augment_bounding_boxes")
def test_random_order_false__images(self):
augs = [iaa.Multiply(2.0), iaa.Add(100)]
aug = iaa.SomeOf(n=2, children=augs, random_order=False)
p_observed = self._test_random_order(aug, 10)
assert np.isclose(p_observed[0], 1.0, rtol=0, atol=1e-8)
assert np.isclose(p_observed[1], 0.0, rtol=0, atol=1e-8)
def test_random_order_true__images(self):
augs = [iaa.Multiply(2.0), iaa.Add(100)]
aug = iaa.SomeOf(n=2, children=augs, random_order=True)
p_observed = self._test_random_order(aug, 300)
assert np.isclose(p_observed[0], 0.5, rtol=0, atol=0.15)
assert np.isclose(p_observed[1], 0.5, rtol=0, atol=0.15)
@classmethod
def _test_random_order(cls, aug, nb_iterations):
zeros = np.ones((1, 1, 1), dtype=np.uint8)
nb_observed = [0, 0]
for i in sm.xrange(nb_iterations):
observed = aug.augment_image(zeros)
s = np.sum(observed)
if s == (1*2)+100:
nb_observed[0] += 1
elif s == (1+100)*2:
nb_observed[1] += 1
else:
raise Exception("Unexpected sum: %.8f (@2)" % (s,))
p_observed = [n/nb_iterations for n in nb_observed]
return p_observed
@classmethod
def _test_images_and_cbaoi_aligned(cls, cbaoi, augf_name):
img = np.zeros((3, 3), dtype=np.uint8)
img_x = np.copy(img)
img_y = np.copy(img)
img_xy = np.copy(img)
img[1, 1] = 255
img_x[1, 2] = 255
img_y[2, 1] = 255
img_xy[2, 2] = 255
augs = [
iaa.Affine(translate_px={"x": 1}, order=0),
iaa.Affine(translate_px={"y": 1}, order=0)
]
cbaoi_x = cbaoi.shift(x=1)
cbaoi_y = cbaoi.shift(y=1)
cbaoi_xy = cbaoi.shift(x=1, y=1)
aug = iaa.SomeOf((0, 2), children=augs)
seen = [False, False, False, False]
for _ in sm.xrange(100):
aug_det = aug.to_deterministic()
img_aug = aug_det.augment_image(img)
cbaoi_aug = getattr(aug_det, augf_name)(cbaoi)
if np.array_equal(img_aug, img):
assert_cbaois_equal(cbaoi_aug, cbaoi)
seen[0] = True
elif np.array_equal(img_aug, img_x):
assert_cbaois_equal(cbaoi_aug, cbaoi_x)
seen[1] = True
elif np.array_equal(img_aug, img_y):
assert_cbaois_equal(cbaoi_aug, cbaoi_y)
seen[2] = True
elif np.array_equal(img_aug, img_xy):
assert_cbaois_equal(cbaoi_aug, cbaoi_xy)
seen[3] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_images_and_keypoints_aligned(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1)]
kpsoi = ia.KeypointsOnImage(kps, shape=(5, 6, 3))
self._test_images_and_cbaoi_aligned(kpsoi, "augment_keypoints")
def test_images_and_polygons_aligned(self):
ps = [ia.Polygon([(0, 0), (3, 0), (3, 3), (0, 3)])]
psoi = ia.PolygonsOnImage(ps, shape=(5, 6, 3))
self._test_images_and_cbaoi_aligned(psoi, "augment_polygons")
def test_images_and_line_strings_aligned(self):
ls = [ia.LineString([(0, 0), (3, 0), (3, 3), (0, 3)])]
lsoi = ia.LineStringsOnImage(ls, shape=(5, 6, 3))
self._test_images_and_cbaoi_aligned(lsoi, "augment_line_strings")
def test_images_and_bounding_boxes_aligned(self):
bbs = [ia.BoundingBox(x1=0, y1=0, x2=3, y2=3)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(5, 6, 3))
self._test_images_and_cbaoi_aligned(bbsoi, "augment_bounding_boxes")
def test_invalid_argument_as_children(self):
got_exception = False
try:
_ = iaa.SomeOf(1, children=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_invalid_datatype_as_n(self):
got_exception = False
try:
_ = iaa.SomeOf(False, children=iaa.Fliplr(1.0))
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_invalid_tuple_as_n(self):
got_exception = False
try:
_ = iaa.SomeOf((2, "test"), children=iaa.Fliplr(1.0))
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_invalid_none_none_tuple_as_n(self):
got_exception = False
try:
_ = iaa.SomeOf((None, None), children=iaa.Fliplr(1.0))
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_with_children_that_change_shapes_keep_size_false(self):
# test for https://github.com/aleju/imgaug/issues/143
# (shapes change in child augmenters, leading to problems if input
# arrays are assumed to stay input arrays)
image = np.zeros((8, 8, 3), dtype=np.uint8)
aug = iaa.SomeOf(1, [
iaa.Crop((2, 0, 2, 0), keep_size=False),
iaa.Crop((1, 0, 1, 0), keep_size=False)
])
expected_shapes = [(4, 8, 3), (6, 8, 3)]
for _ in sm.xrange(10):
observed = aug.augment_images(np.uint8([image] * 4))
assert isinstance(observed, list)
assert np.all([img.shape in expected_shapes for img in observed])
observed = aug.augment_images([image] * 4)
assert isinstance(observed, list)
assert np.all([img.shape in expected_shapes for img in observed])
observed = aug.augment_images(np.uint8([image]))
assert isinstance(observed, list)
assert np.all([img.shape in expected_shapes for img in observed])
observed = aug.augment_images([image])
assert isinstance(observed, list)
assert np.all([img.shape in expected_shapes for img in observed])
observed = aug.augment_image(image)
assert ia.is_np_array(image)
assert observed.shape in expected_shapes
def test_with_children_that_change_shapes_keep_size_true(self):
image = np.zeros((8, 8, 3), dtype=np.uint8)
aug = iaa.SomeOf(1, [
iaa.Crop((2, 0, 2, 0), keep_size=True),
iaa.Crop((1, 0, 1, 0), keep_size=True)
])
expected_shapes = [(8, 8, 3)]
for _ in sm.xrange(10):
observed = aug.augment_images(np.uint8([image] * 4))
assert ia.is_np_array(observed)
assert np.all([img.shape in expected_shapes for img in observed])
observed = aug.augment_images([image] * 4)
assert isinstance(observed, list)
assert np.all([img.shape in expected_shapes for img in observed])
observed = aug.augment_images(np.uint8([image]))
assert ia.is_np_array(observed)
assert np.all([img.shape in expected_shapes for img in observed])
observed = aug.augment_images([image])
assert isinstance(observed, list)
assert np.all([img.shape in expected_shapes for img in observed])
observed = aug.augment_image(image)
assert ia.is_np_array(observed)
assert observed.shape in expected_shapes
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
for random_order in [False, True]:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.SomeOf(
1, [iaa.Identity()], random_order=random_order)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
for random_order in [False, True]:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.SomeOf(
1, [iaa.Identity()], random_order=random_order)
image_aug = aug(image=image)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_other_dtypes_via_noop__bool(self):
for random_order in [False, True]:
with self.subTest(random_order=random_order):
aug = iaa.SomeOf(2, [
iaa.Identity(),
iaa.Identity(),
iaa.Identity()
], random_order=random_order)
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert np.all(image_aug == image)
def test_other_dtypes_via_noop__uint_int(self):
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
random_orders = [False, True]
for dtype, random_order in itertools.product(dtypes, random_orders):
with self.subTest(dtype=dtype, random_order=random_order):
aug = iaa.SomeOf(2, [
iaa.Identity(),
iaa.Identity(),
iaa.Identity()
], random_order=random_order)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, image)
def test_other_dtypes_via_noop__float(self):
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
random_orders = [False, True]
for random_order in random_orders:
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype, random_order=random_order):
aug = iaa.SomeOf(2, [
iaa.Identity(),
iaa.Identity(),
iaa.Identity()
], random_order=random_order)
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == image)
def test_other_dtypes_via_flip__bool(self):
for random_order in [False, True]:
with self.subTest(random_order=random_order):
aug = iaa.SomeOf(2, [
iaa.Fliplr(1.0),
iaa.Flipud(1.0),
iaa.Identity()
], random_order=random_order)
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
expected = [np.zeros((3, 3), dtype=bool)
for _ in sm.xrange(3)]
expected[0][0, 2] = True
expected[1][2, 0] = True
expected[2][2, 2] = True
for _ in sm.xrange(10):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert any([np.all(image_aug == expected_i)
for expected_i in expected])
def test_other_dtypes_via_flip__uint_int(self):
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
random_orders = [False, True]
for dtype, random_order in itertools.product(dtypes, random_orders):
with self.subTest(dtype=dtype, random_order=random_order):
aug = iaa.SomeOf(2, [
iaa.Fliplr(1.0),
iaa.Flipud(1.0),
iaa.Identity()
], random_order=random_order)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
expected = [np.zeros((3, 3), dtype=dtype)
for _ in sm.xrange(3)]
expected[0][0, 2] = value
expected[1][2, 0] = value
expected[2][2, 2] = value
for _ in sm.xrange(10):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert any([np.all(image_aug == expected_i)
for expected_i in expected])
def test_other_dtypes_via_flip__float(self):
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
random_orders = [False, True]
for random_order in random_orders:
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype, random_order=random_order):
aug = iaa.SomeOf(2, [
iaa.Fliplr(1.0),
iaa.Flipud(1.0),
iaa.Identity()
], random_order=random_order)
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
expected = [np.zeros((3, 3), dtype=dtype)
for _ in sm.xrange(3)]
expected[0][0, 2] = value
expected[1][2, 0] = value
expected[2][2, 2] = value
for _ in sm.xrange(10):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert any([np.all(image_aug == expected_i)
for expected_i in expected])
def test_pickleable(self):
aug = iaa.SomeOf((0, 3),
[iaa.Add(1, seed=1),
iaa.Add(2, seed=2),
iaa.Multiply(1.5, seed=3),
iaa.Multiply(2.0, seed=4)],
random_order=True,
seed=5)
runtest_pickleable_uint8_img(aug, iterations=5)
def test_get_children_lists(self):
child = iaa.Identity()
aug = iaa.SomeOf(1, [child])
children_lsts = aug.get_children_lists()
assert len(children_lsts) == 1
assert len(children_lsts[0]) == 1
assert children_lsts[0][0] is child
def test_to_deterministic(self):
child = iaa.Identity()
aug = iaa.SomeOf(1, [child])
aug_det = aug.to_deterministic()
assert aug_det.random_state is not aug.random_state
assert aug_det.deterministic
assert aug_det[0].deterministic
class TestOneOf(unittest.TestCase):
def setUp(self):
reseed()
def test_returns_someof(self):
child = iaa.Identity()
aug = iaa.OneOf(children=child)
assert isinstance(aug, iaa.SomeOf)
assert aug.n == 1
assert aug[0] is child
def test_single_child_that_is_augmenter(self):
zeros = np.zeros((3, 3, 1), dtype=np.uint8)
aug = iaa.OneOf(children=iaa.Add(1))
observed = aug.augment_image(zeros)
assert np.array_equal(observed, zeros + 1)
def test_single_child_that_is_sequential(self):
zeros = np.zeros((3, 3, 1), dtype=np.uint8)
aug = iaa.OneOf(children=iaa.Sequential([iaa.Add(1)]))
observed = aug.augment_image(zeros)
assert np.array_equal(observed, zeros + 1)
def test_single_child_that_is_list(self):
zeros = np.zeros((3, 3, 1), dtype=np.uint8)
aug = iaa.OneOf(children=[iaa.Add(1)])
observed = aug.augment_image(zeros)
assert np.array_equal(observed, zeros + 1)
def test_three_children(self):
zeros = np.zeros((1, 1, 1), dtype=np.uint8)
augs = [iaa.Add(1), iaa.Add(2), iaa.Add(3)]
aug = iaa.OneOf(augs)
results = {1: 0, 2: 0, 3: 0}
nb_iterations = 1000
for _ in sm.xrange(nb_iterations):
result = aug.augment_image(zeros)
s = int(np.sum(result))
results[s] += 1
expected = int(nb_iterations / len(augs))
tolerance = int(nb_iterations * 0.05)
for key, val in results.items():
assert np.isclose(val, expected, rtol=0, atol=tolerance)
assert len(list(results.keys())) == 3
def test_pickleable(self):
aug = iaa.OneOf(
[iaa.Add(1, seed=1),
iaa.Add(10, seed=2),
iaa.Multiply(2.0, seed=3)],
seed=4)
runtest_pickleable_uint8_img(aug, iterations=5)
def test_get_children_lists(self):
child = iaa.Identity()
aug = iaa.OneOf([child])
children_lsts = aug.get_children_lists()
assert len(children_lsts) == 1
assert len(children_lsts[0]) == 1
assert children_lsts[0][0] is child
def test_to_deterministic(self):
child = iaa.Identity()
aug = iaa.OneOf([child])
aug_det = aug.to_deterministic()
assert aug_det.random_state is not aug.random_state
assert aug_det.deterministic
assert aug_det[0].deterministic
class TestSometimes(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
image = np.array([[0, 1, 1],
[0, 0, 1],
[0, 0, 1]], dtype=np.uint8) * 255
return np.atleast_3d(image)
@property
def images(self):
return np.uint8([self.image])
@property
def image_lr(self):
image_lr = np.array([[1, 1, 0],
[1, 0, 0],
[1, 0, 0]], dtype=np.uint8) * 255
return np.atleast_3d(image_lr)
@property
def images_lr(self):
return np.uint8([self.image_lr])
@property
def image_ud(self):
image_ud = np.array([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]], dtype=np.uint8) * 255
return np.atleast_3d(image_ud)
@property
def images_ud(self):
return np.uint8([self.image_ud])
@property
def keypoints(self):
keypoints = [ia.Keypoint(x=1, y=0),
ia.Keypoint(x=2, y=0),
ia.Keypoint(x=2, y=1)]
return ia.KeypointsOnImage(keypoints, shape=self.image.shape)
@property
def keypoints_lr(self):
keypoints = [ia.Keypoint(x=3-1, y=0),
ia.Keypoint(x=3-2, y=0),
ia.Keypoint(x=3-2, y=1)]
return ia.KeypointsOnImage(keypoints, shape=self.image.shape)
@property
def keypoints_ud(self):
keypoints = [ia.Keypoint(x=1, y=3-0),
ia.Keypoint(x=2, y=3-0),
ia.Keypoint(x=2, y=3-1)]
return ia.KeypointsOnImage(keypoints, shape=self.image.shape)
@property
def polygons(self):
polygons = [ia.Polygon([(0, 0), (2, 0), (2, 2)])]
return ia.PolygonsOnImage(polygons, shape=self.image.shape)
@property
def polygons_lr(self):
polygons = [ia.Polygon([(3-0, 0), (3-2, 0), (3-2, 2)])]
return ia.PolygonsOnImage(polygons, shape=self.image.shape)
@property
def polygons_ud(self):
polygons = [ia.Polygon([(0, 3-0), (2, 3-0), (2, 3-2)])]
return ia.PolygonsOnImage(polygons, shape=self.image.shape)
@property
def lsoi(self):
lss = [ia.LineString([(0, 0), (2, 0), (2, 2)])]
return ia.LineStringsOnImage(lss, shape=self.image.shape)
@property
def lsoi_lr(self):
lss = [ia.LineString([(3-0, 0), (3-2, 0), (3-2, 2)])]
return ia.LineStringsOnImage(lss, shape=self.image.shape)
@property
def lsoi_ud(self):
lss = [ia.LineString([(0, 3-0), (2, 3-0), (2, 3-2)])]
return ia.LineStringsOnImage(lss, shape=self.image.shape)
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=0, x2=1.5, y2=1.0)]
return ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)
@property
def bbsoi_lr(self):
x1 = 3-0
y1 = 0
x2 = 3-1.5
y2 = 1.0
bbs = [ia.BoundingBox(x1=min([x1, x2]), y1=min([y1, y2]),
x2=max([x1, x2]), y2=max([y1, y2]))]
return ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)
@property
def bbsoi_ud(self):
x1 = 0
y1 = 3-0
x2 = 1.5
y2 = 3-1.0
bbs = [ia.BoundingBox(x1=min([x1, x2]), y1=min([y1, y2]),
x2=max([x1, x2]), y2=max([y1, y2]))]
return ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)
@property
def heatmaps(self):
heatmaps_arr = np.float32([[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0]])
return ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def heatmaps_lr(self):
heatmaps_arr = np.float32([[1.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0]])
return ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def heatmaps_ud(self):
heatmaps_arr = np.float32([[0.0, 1.0, 1.0],
[0.0, 0.0, 1.0],
[0.0, 0.0, 1.0]])
return ia.HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
@property
def segmaps(self):
segmaps_arr = np.int32([[0, 0, 1],
[0, 0, 1],
[0, 1, 1]])
return ia.SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def segmaps_lr(self):
segmaps_arr = np.int32([[1, 0, 0],
[1, 0, 0],
[1, 1, 0]])
return ia.SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
@property
def segmaps_ud(self):
segmaps_arr = np.int32([[0, 1, 1],
[0, 0, 1],
[0, 0, 1]])
return ia.SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
def test_two_branches_always_first__images(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_lr)
def test_two_branches_always_first__images__deterministic(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert np.array_equal(observed, self.images_lr)
def test_two_branches_always_first__images__list(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_images([self.images[0]])
assert array_equal_lists(observed, [self.images_lr[0]])
def test_two_branches_always_first__images__deterministic__list(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.images[0]])
assert array_equal_lists(observed, [self.images_lr[0]])
def test_two_branches_always_first__keypoints(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_keypoints(self.keypoints)
assert keypoints_equal(observed, self.keypoints_lr)
def test_two_branches_always_first__keypoints__deterministic(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_keypoints(self.keypoints)
assert_cbaois_equal(observed, self.keypoints_lr)
def test_two_branches_always_first__polygons(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_polygons([self.polygons])
assert_cbaois_equal(observed, [self.polygons_lr])
def test_two_branches_always_first__polygons__deterministic(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_polygons([self.polygons])
assert_cbaois_equal(observed, [self.polygons_lr])
def test_two_branches_always_first__line_strings(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_line_strings([self.lsoi])
assert_cbaois_equal(observed, [self.lsoi_lr])
def test_two_branches_always_first__line_strings__deterministic(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_line_strings([self.lsoi])
assert_cbaois_equal(observed, [self.lsoi_lr])
def test_two_branches_always_first__bounding_boxes(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_bounding_boxes([self.bbsoi])
assert_cbaois_equal(observed, [self.bbsoi_lr])
def test_two_branches_always_first__bounding_boxes__deterministic(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_bounding_boxes([self.bbsoi])
assert_cbaois_equal(observed, [self.bbsoi_lr])
def test_two_branches_always_first__heatmaps(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_heatmaps([self.heatmaps])[0]
assert observed.shape == self.heatmaps.shape
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.array_equal(observed.get_arr(), self.heatmaps_lr.get_arr())
def test_two_branches_always_first__segmaps(self):
aug = iaa.Sometimes(1.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_segmentation_maps(self.segmaps)
assert observed.shape == self.segmaps.shape
assert np.array_equal(observed.get_arr(), self.segmaps_lr.get_arr())
def test_two_branches_always_second__images(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_images(self.images)
assert np.array_equal(observed, self.images_ud)
def test_two_branches_always_second__images__deterministic(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_images(self.images)
assert np.array_equal(observed, self.images_ud)
def test_two_branches_always_second__images__list(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_images([self.images[0]])
assert array_equal_lists(observed, [self.images_ud[0]])
def test_two_branches_always_second__images__list__deterministic(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_images([self.images[0]])
assert array_equal_lists(observed, [self.images_ud[0]])
def test_two_branches_always_second__keypoints(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_keypoints([self.keypoints])
assert_cbaois_equal(observed[0], self.keypoints_ud)
def test_two_branches_always_second__keypoints__deterministic(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_keypoints([self.keypoints])
assert_cbaois_equal(observed[0], self.keypoints_ud)
def test_two_branches_always_second__polygons(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_polygons(self.polygons)
assert_cbaois_equal(observed, self.polygons_ud)
def test_two_branches_always_second__polygons__deterministic(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_polygons(self.polygons)
assert_cbaois_equal(observed, self.polygons_ud)
def test_two_branches_always_second__line_strings(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi_ud)
def test_two_branches_always_second__line_strings__deterministic(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_line_strings(self.lsoi)
assert_cbaois_equal(observed, self.lsoi_ud)
def test_two_branches_always_second__bounding_boxes(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi_ud)
def test_two_branches_always_second__bounding_boxes__deterministic(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
observed = aug_det.augment_bounding_boxes(self.bbsoi)
assert_cbaois_equal(observed, self.bbsoi_ud)
def test_two_branches_always_second__heatmaps(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_heatmaps(self.heatmaps)
assert observed.shape == self.heatmaps.shape
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.array_equal(observed.get_arr(), self.heatmaps_ud.get_arr())
def test_two_branches_always_second__segmaps(self):
aug = iaa.Sometimes(0.0, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
observed = aug.augment_segmentation_maps(self.segmaps)
assert observed.shape == self.segmaps.shape
assert np.array_equal(observed.get_arr(), self.segmaps_ud.get_arr())
def test_two_branches_both_50_percent__images(self):
aug = iaa.Sometimes(0.5, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
last_aug = None
nb_changed_aug = 0
nb_iterations = 500
nb_images_if_branch = 0
nb_images_else_branch = 0
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(self.images)
if i == 0:
last_aug = observed_aug
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
last_aug = observed_aug
if np.array_equal(observed_aug, self.images_lr):
nb_images_if_branch += 1
elif np.array_equal(observed_aug, self.images_ud):
nb_images_else_branch += 1
else:
raise Exception(
"Received output doesnt match any expected output.")
p_if_branch = nb_images_if_branch / nb_iterations
p_else_branch = nb_images_else_branch / nb_iterations
p_changed = 1 - (nb_changed_aug / nb_iterations)
assert np.isclose(p_if_branch, 0.5, rtol=0, atol=0.1)
assert np.isclose(p_else_branch, 0.5, rtol=0, atol=0.1)
# should be the same in roughly 50% of all cases
assert np.isclose(p_changed, 0.5, rtol=0, atol=0.1)
def test_two_branches_both_50_percent__images__deterministic(self):
aug = iaa.Sometimes(0.5, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
aug_det = aug.to_deterministic()
last_aug_det = None
nb_changed_aug_det = 0
nb_iterations = 20
for i in sm.xrange(nb_iterations):
observed_aug_det = aug_det.augment_images(self.images)
if i == 0:
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug_det = observed_aug_det
assert nb_changed_aug_det == 0
@classmethod
def _test_two_branches_both_50_percent__cbaois(
cls, cbaoi, cbaoi_lr, cbaoi_ud, augf_name):
def _same_coords(cbaoi1, cbaoi2):
assert len(cbaoi1.items) == len(cbaoi2.items)
for i1, i2 in zip(cbaoi1.items, cbaoi2.items):
if not np.allclose(i1.coords, i2.coords, atol=1e-4, rtol=0):
return False
return True
aug = iaa.Sometimes(0.5, [iaa.Fliplr(1.0)], [iaa.Flipud(1.0)])
nb_iterations = 250
nb_if_branch = 0
nb_else_branch = 0
for i in sm.xrange(nb_iterations):
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
# use allclose() instead of coords_almost_equals() for efficiency
if _same_coords(cbaoi_aug, cbaoi_lr):
nb_if_branch += 1
elif _same_coords(cbaoi_aug, cbaoi_ud):
nb_else_branch += 1
else:
raise Exception(
"Received output doesnt match any expected output.")
p_if_branch = nb_if_branch / nb_iterations
p_else_branch = nb_else_branch / nb_iterations
assert np.isclose(p_if_branch, 0.5, rtol=0, atol=0.15)
assert np.isclose(p_else_branch, 0.5, rtol=0, atol=0.15)
def test_two_branches_both_50_percent__keypoints(self):
self._test_two_branches_both_50_percent__cbaois(
self.keypoints, self.keypoints_lr, self.keypoints_ud,
"augment_keypoints")
def test_two_branches_both_50_percent__polygons(self):
self._test_two_branches_both_50_percent__cbaois(
self.polygons, self.polygons_lr, self.polygons_ud,
"augment_polygons")
def test_two_branches_both_50_percent__line_strings(self):
self._test_two_branches_both_50_percent__cbaois(
self.lsoi, self.lsoi_lr, self.lsoi_ud,
"augment_line_strings")
def test_two_branches_both_50_percent__bounding_boxes(self):
self._test_two_branches_both_50_percent__cbaois(
self.bbsoi, self.bbsoi_lr, self.bbsoi_ud,
"augment_bounding_boxes")
def test_one_branch_50_percent__images(self):
aug = iaa.Sometimes(0.5, iaa.Fliplr(1.0))
last_aug = None
nb_changed_aug = 0
nb_iterations = 500
nb_images_if_branch = 0
nb_images_else_branch = 0
for i in sm.xrange(nb_iterations):
observed_aug = aug.augment_images(self.images)
if i == 0:
last_aug = observed_aug
else:
if not np.array_equal(observed_aug, last_aug):
nb_changed_aug += 1
last_aug = observed_aug
if np.array_equal(observed_aug, self.images_lr):
nb_images_if_branch += 1
elif np.array_equal(observed_aug, self.images):
nb_images_else_branch += 1
else:
raise Exception(
"Received output doesnt match any expected output.")
p_if_branch = nb_images_if_branch / nb_iterations
p_else_branch = nb_images_else_branch / nb_iterations
p_changed = 1 - (nb_changed_aug / nb_iterations)
assert np.isclose(p_if_branch, 0.5, rtol=0, atol=0.1)
assert np.isclose(p_else_branch, 0.5, rtol=0, atol=0.1)
# should be the same in roughly 50% of all cases
assert np.isclose(p_changed, 0.5, rtol=0, atol=0.1)
def test_one_branch_50_percent__images__deterministic(self):
aug = iaa.Sometimes(0.5, iaa.Fliplr(1.0))
aug_det = aug.to_deterministic()
last_aug_det = None
nb_changed_aug_det = 0
nb_iterations = 10
for i in sm.xrange(nb_iterations):
observed_aug_det = aug_det.augment_images(self.images)
if i == 0:
last_aug_det = observed_aug_det
else:
if not np.array_equal(observed_aug_det, last_aug_det):
nb_changed_aug_det += 1
last_aug_det = observed_aug_det
assert nb_changed_aug_det == 0
@classmethod
def _test_one_branch_50_percent__cbaois(
cls, cbaoi, cbaoi_lr, augf_name):
def _same_coords(cbaoi1, cbaoi2):
assert len(cbaoi1.items) == len(cbaoi2.items)
for i1, i2 in zip(cbaoi1.items, cbaoi2.items):
if not np.allclose(i1.coords, i2.coords, atol=1e-4, rtol=0):
return False
return True
aug = iaa.Sometimes(0.5, iaa.Fliplr(1.0))
nb_iterations = 250
nb_if_branch = 0
nb_else_branch = 0
for i in sm.xrange(nb_iterations):
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
# use allclose() instead of coords_almost_equals() for efficiency
if _same_coords(cbaoi_aug, cbaoi_lr):
nb_if_branch += 1
elif _same_coords(cbaoi_aug, cbaoi):
nb_else_branch += 1
else:
raise Exception(
"Received output doesnt match any expected output.")
p_if_branch = nb_if_branch / nb_iterations
p_else_branch = nb_else_branch / nb_iterations
assert np.isclose(p_if_branch, 0.5, rtol=0, atol=0.15)
assert np.isclose(p_else_branch, 0.5, rtol=0, atol=0.15)
def test_one_branch_50_percent__keypoints(self):
self._test_one_branch_50_percent__cbaois(
self.keypoints, self.keypoints_lr, "augment_keypoints")
def test_one_branch_50_percent__polygons(self):
self._test_one_branch_50_percent__cbaois(
self.polygons, self.polygons_lr, "augment_polygons")
def test_one_branch_50_percent__bounding_boxes(self):
self._test_one_branch_50_percent__cbaois(
self.bbsoi, self.bbsoi_lr, "augment_bounding_boxes")
@classmethod
def _test_empty_cbaoi(cls, cbaoi, augf_name):
aug = iaa.Sometimes(0.5, iaa.Identity())
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi)
def test_empty_keypoints(self):
kpsoi = ia.KeypointsOnImage([], shape=(1, 2, 3))
self._test_empty_cbaoi(kpsoi, "augment_keypoints")
def test_empty_polygons(self):
psoi = ia.PolygonsOnImage([], shape=(1, 2, 3))
self._test_empty_cbaoi(psoi, "augment_polygons")
def test_empty_line_strings(self):
lsoi = ia.LineStringsOnImage([], shape=(1, 2, 3))
self._test_empty_cbaoi(lsoi, "augment_line_strings")
def test_empty_bounding_boxes(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(1, 2, 3))
self._test_empty_cbaoi(bbsoi, "augment_bounding_boxes")
def test_p_is_stochastic_parameter(self):
image = np.zeros((1, 1), dtype=np.uint8) + 100
images = [image] * 10
aug = iaa.Sometimes(
p=iap.Binomial(iap.Choice([0.0, 1.0])),
then_list=iaa.Add(10))
seen = [0, 0]
for _ in sm.xrange(100):
observed = aug.augment_images(images)
uq = np.unique(np.uint8(observed))
assert len(uq) == 1
if uq[0] == 100:
seen[0] += 1
elif uq[0] == 110:
seen[1] += 1
else:
assert False
assert seen[0] > 20
assert seen[1] > 20
def test_bad_datatype_for_p_fails(self):
got_exception = False
try:
_ = iaa.Sometimes(p="foo")
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_bad_datatype_for_then_list_fails(self):
got_exception = False
try:
_ = iaa.Sometimes(p=0.2, then_list=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_bad_datatype_for_else_list_fails(self):
got_exception = False
try:
_ = iaa.Sometimes(p=0.2, then_list=None, else_list=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_two_branches_both_none(self):
aug = iaa.Sometimes(0.2, then_list=None, else_list=None)
image = np.random.randint(0, 255, size=(16, 16), dtype=np.uint8)
observed = aug.augment_image(image)
assert np.array_equal(observed, image)
def test_using_hooks_to_deactivate_propagation(self):
image = np.random.randint(0, 255-10, size=(16, 16), dtype=np.uint8)
aug = iaa.Sometimes(1.0, iaa.Add(10))
def _propagator(images, augmenter, parents, default):
return False if augmenter == aug else default
hooks = ia.HooksImages(propagator=_propagator)
observed1 = aug.augment_image(image)
observed2 = aug.augment_image(image, hooks=hooks)
assert np.array_equal(observed1, image + 10)
assert np.array_equal(observed2, image)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Sometimes(1.0, iaa.Identity())
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Sometimes(1.0, iaa.Identity())
image_aug = aug(image=image)
assert np.all(image_aug == 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
aug = iaa.Sometimes(0.75)
params = aug.get_parameters()
assert is_parameter_instance(params[0], iap.Binomial)
assert is_parameter_instance(params[0].p, iap.Deterministic)
assert 0.75 - 1e-8 < params[0].p.value < 0.75 + 1e-8
def test___str___and___repr__(self):
then_list = iaa.Add(1)
else_list = iaa.Add(2)
aug = iaa.Sometimes(
0.5,
then_list=then_list,
else_list=else_list,
name="SometimesTest")
expected_then_list = (
"Sequential("
"name=SometimesTest-then, "
"random_order=False, "
"children=[%s], "
"deterministic=False"
")" % (str(then_list),))
expected_else_list = (
"Sequential("
"name=SometimesTest-else, "
"random_order=False, "
"children=[%s], "
"deterministic=False"
")" % (str(else_list),))
expected = (
"Sometimes("
"p=%s, name=%s, then_list=%s, else_list=%s, deterministic=%s"
")" % (
str(aug.p),
"SometimesTest",
expected_then_list,
expected_else_list,
"False"))
observed_str = aug.__str__()
observed_repr = aug.__repr__()
assert observed_str == expected
assert observed_repr == expected
def test___str___and___repr___with_nones_as_children(self):
aug = iaa.Sometimes(
0.5,
then_list=None,
else_list=None,
name="SometimesTest")
expected = (
"Sometimes("
"p=%s, "
"name=%s, "
"then_list=%s, "
"else_list=%s, "
"deterministic=%s"
")" % (
str(aug.p),
"SometimesTest",
"None",
"None",
"False"))
observed_str = aug.__str__()
observed_repr = aug.__repr__()
assert observed_str == expected
assert observed_repr == expected
def test_shapes_changed_by_children__no_keep_size_non_stochastic(self):
# Test for https://github.com/aleju/imgaug/issues/143
# (shapes change in child augmenters, leading to problems if input
# arrays are assumed to stay input arrays)
def _assert_all_valid_shapes(images):
expected_shapes = [(4, 8, 3), (6, 8, 3)]
assert np.all([img.shape in expected_shapes for img in images])
image = np.zeros((8, 8, 3), dtype=np.uint8)
aug = iaa.Sometimes(
0.5,
iaa.Crop((2, 0, 2, 0), keep_size=False),
iaa.Crop((1, 0, 1, 0), keep_size=False)
)
for _ in sm.xrange(10):
observed = aug.augment_images(
np.uint8([image, image, image, image]))
assert isinstance(observed, list) or ia.is_np_array(observed)
_assert_all_valid_shapes(observed)
observed = aug.augment_images([image, image, image, image])
assert isinstance(observed, list)
_assert_all_valid_shapes(observed)
observed = aug.augment_images(np.uint8([image]))
assert isinstance(observed, list) or ia.is_np_array(observed)
_assert_all_valid_shapes(observed)
observed = aug.augment_images([image])
assert isinstance(observed, list)
_assert_all_valid_shapes(observed)
observed = aug.augment_image(image)
assert ia.is_np_array(image)
_assert_all_valid_shapes([observed])
def test_shapes_changed_by_children__no_keep_size_stochastic(self):
def _assert_all_valid_shapes(images):
assert np.all([
16 <= img.shape[0] <= 30
and img.shape[1:] == (32, 3) for img in images
])
image = np.zeros((32, 32, 3), dtype=np.uint8)
aug = iaa.Sometimes(
0.5,
iaa.Crop(((1, 4), 0, (1, 4), 0), keep_size=False),
iaa.Crop(((4, 8), 0, (4, 8), 0), keep_size=False)
)
for _ in sm.xrange(10):
observed = aug.augment_images(
np.uint8([image, image, image, image]))
assert isinstance(observed, list) or ia.is_np_array(observed)
_assert_all_valid_shapes(observed)
observed = aug.augment_images([image, image, image, image])
assert isinstance(observed, list)
_assert_all_valid_shapes(observed)
observed = aug.augment_images(np.uint8([image]))
assert isinstance(observed, list) or ia.is_np_array(observed)
_assert_all_valid_shapes(observed)
observed = aug.augment_images([image])
assert isinstance(observed, list)
_assert_all_valid_shapes(observed)
observed = aug.augment_image(image)
assert ia.is_np_array(image)
_assert_all_valid_shapes([observed])
def test_shapes_changed_by_children__keep_size_non_stochastic(self):
def _assert_all_valid_shapes(images):
expected_shapes = [(8, 8, 3)]
assert np.all([img.shape in expected_shapes for img in images])
image = np.zeros((8, 8, 3), dtype=np.uint8)
aug = iaa.Sometimes(
0.5,
iaa.Crop((2, 0, 2, 0), keep_size=True),
iaa.Crop((1, 0, 1, 0), keep_size=True)
)
for _ in sm.xrange(10):
observed = aug.augment_images(
np.uint8([image, image, image, image]))
assert ia.is_np_array(observed)
_assert_all_valid_shapes(observed)
observed = aug.augment_images([image, image, image, image])
assert isinstance(observed, list)
_assert_all_valid_shapes(observed)
observed = aug.augment_images(np.uint8([image]))
assert ia.is_np_array(observed)
_assert_all_valid_shapes(observed)
observed = aug.augment_images([image])
assert isinstance(observed, list)
_assert_all_valid_shapes(observed)
observed = aug.augment_image(image)
assert ia.is_np_array(observed)
_assert_all_valid_shapes([observed])
def test_shapes_changed_by_children__keep_size_stochastic(self):
def _assert_all_valid_shapes(images):
# only one shape expected here despite stochastic crop ranges
# due to keep_size=True
expected_shapes = [(8, 8, 3)]
assert np.all([img.shape in expected_shapes for img in images])
image = np.zeros((8, 8, 3), dtype=np.uint8)
aug = iaa.Sometimes(
0.5,
iaa.Crop(((1, 4), 0, (1, 4), 0), keep_size=True),
iaa.Crop(((4, 8), 0, (4, 8), 0), keep_size=True)
)
for _ in sm.xrange(10):
observed = aug.augment_images(
np.uint8([image, image, image, image]))
assert ia.is_np_array(observed)
_assert_all_valid_shapes(observed)
observed = aug.augment_images([image, image, image, image])
assert isinstance(observed, list)
_assert_all_valid_shapes(observed)
observed = aug.augment_images(np.uint8([image]))
assert ia.is_np_array(observed)
_assert_all_valid_shapes(observed)
observed = aug.augment_images([image])
assert isinstance(observed, list)
_assert_all_valid_shapes(observed)
observed = aug.augment_image(image)
assert ia.is_np_array(observed)
_assert_all_valid_shapes([observed])
def test_other_dtypes_via_noop__bool(self):
aug = iaa.Sometimes(1.0, iaa.Identity())
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert np.all(image_aug == image)
def test_other_dtypes_via_noop__uint_int(self):
aug = iaa.Sometimes(1.0, iaa.Identity())
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, _center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, image)
def test_other_dtypes_via_noop__float(self):
aug = iaa.Sometimes(1.0, iaa.Identity())
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == image)
def test_other_dtypes_via_flip__bool(self):
aug = iaa.Sometimes(0.5, iaa.Fliplr(1.0), iaa.Flipud(1.0))
image = np.zeros((3, 3), dtype=bool)
image[0, 0] = True
expected = [np.zeros((3, 3), dtype=bool) for _ in sm.xrange(2)]
expected[0][0, 2] = True
expected[1][2, 0] = True
seen = [False, False]
for _ in sm.xrange(100):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
if np.all(image_aug == expected[0]):
seen[0] = True
elif np.all(image_aug == expected[1]):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_other_dtypes_via_flip__uint_int(self):
aug = iaa.Sometimes(0.5, iaa.Fliplr(1.0), iaa.Flipud(1.0))
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, _center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
expected = [np.zeros((3, 3), dtype=dtype) for _ in sm.xrange(2)]
expected[0][0, 2] = value
expected[1][2, 0] = value
seen = [False, False]
for _ in sm.xrange(100):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
if np.all(image_aug == expected[0]):
seen[0] = True
elif np.all(image_aug == expected[1]):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_other_dtypes_via_flip__float(self):
aug = iaa.Sometimes(0.5, iaa.Fliplr(1.0), iaa.Flipud(1.0))
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype):
image = np.zeros((3, 3), dtype=dtype)
image[0, 0] = value
expected = [np.zeros((3, 3), dtype=dtype) for _ in sm.xrange(2)]
expected[0][0, 2] = value
expected[1][2, 0] = value
seen = [False, False]
for _ in sm.xrange(100):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
if np.all(image_aug == expected[0]):
seen[0] = True
elif np.all(image_aug == expected[1]):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_pickleable(self):
aug = iaa.Sometimes(0.5, iaa.Add(10), [iaa.Add(1), iaa.Multiply(2.0)],
seed=1)
runtest_pickleable_uint8_img(aug, iterations=5)
def test_get_children_lists(self):
child = iaa.Identity()
aug = iaa.Sometimes(0.5, [child])
children_lsts = aug.get_children_lists()
assert len(children_lsts) == 1
assert len(children_lsts[0]) == 1
assert children_lsts[0][0] is child
def test_get_children_lists_both_lists(self):
child = iaa.Identity()
child2 = iaa.Identity()
aug = iaa.Sometimes(0.5, [child], [child2])
children_lsts = aug.get_children_lists()
assert len(children_lsts) == 2
assert len(children_lsts[0]) == 1
assert len(children_lsts[1]) == 1
assert children_lsts[0][0] is child
assert children_lsts[1][0] is child2
def test_to_deterministic(self):
child = iaa.Identity()
child2 = iaa.Identity()
aug = iaa.Sometimes(0.5, [child], [child2])
aug_det = aug.to_deterministic()
assert aug_det.deterministic
assert aug_det.random_state is not aug.random_state
assert aug_det.then_list[0].deterministic
assert aug_det.else_list[0].deterministic
class TestWithChannels(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
base_img = np.zeros((3, 3, 2), dtype=np.uint8)
base_img[..., 0] += 100
base_img[..., 1] += 200
return base_img
def test_augment_only_channel_0(self):
aug = iaa.WithChannels(0, iaa.Add(10))
observed = aug.augment_image(self.image)
expected = self.image
expected[..., 0] += 10
assert np.allclose(observed, expected)
def test_augment_only_channel_1(self):
aug = iaa.WithChannels(1, iaa.Add(10))
observed = aug.augment_image(self.image)
expected = self.image
expected[..., 1] += 10
assert np.allclose(observed, expected)
def test_augment_all_channels_via_none(self):
aug = iaa.WithChannels(None, iaa.Add(10))
observed = aug.augment_image(self.image)
expected = self.image + 10
assert np.allclose(observed, expected)
def test_augment_channels_0_and_1_via_list(self):
aug = iaa.WithChannels([0, 1], iaa.Add(10))
observed = aug.augment_image(self.image)
expected = self.image + 10
assert np.allclose(observed, expected)
def test_apply_multiple_augmenters(self):
image = np.zeros((3, 3, 2), dtype=np.uint8)
image[..., 0] += 5
image[..., 1] += 10
aug = iaa.WithChannels(1, [iaa.Add(10), iaa.Multiply(2.0)])
observed = aug.augment_image(image)
expected = np.copy(image)
expected[..., 1] += 10
expected[..., 1] *= 2
assert np.allclose(observed, expected)
def test_multiple_images_given_as_array(self):
images = np.concatenate([
self.image[np.newaxis, ...],
self.image[np.newaxis, ...]],
axis=0)
aug = iaa.WithChannels(1, iaa.Add(10))
observed = aug.augment_images(images)
expected = np.copy(images)
expected[..., 1] += 10
assert np.allclose(observed, expected)
def test_multiple_images_given_as_list_of_arrays(self):
images = [self.image, self.image]
aug = iaa.WithChannels(1, iaa.Add(10))
observed = aug.augment_images(images)
expected = self.image
expected[..., 1] += 10
expected = [expected, expected]
assert array_equal_lists(observed, expected)
def test_children_list_is_none(self):
aug = iaa.WithChannels(1, children=None)
observed = aug.augment_image(self.image)
expected = self.image
assert np.array_equal(observed, expected)
def test_channels_is_empty_list(self):
aug = iaa.WithChannels([], iaa.Add(10))
observed = aug.augment_image(self.image)
expected = self.image
assert np.array_equal(observed, expected)
def test_heatmap_augmentation_single_channel(self):
heatmap_arr = np.float32([
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[1.0, 1.0, 1.0]
])
heatmap = HeatmapsOnImage(heatmap_arr, shape=(3, 3, 3))
affine = iaa.Affine(translate_px={"x": 1})
aug = iaa.WithChannels(1, children=[affine])
heatmap_aug = aug.augment_heatmaps(heatmap)
assert heatmap_aug.shape == (3, 3, 3)
assert np.allclose(heatmap_aug.get_arr(), heatmap_arr)
def test_heatmap_augmentation_multiple_channels(self):
heatmap_arr = np.float32([
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0],
[1.0, 1.0, 1.0]
])
heatmap_arr_shifted = np.float32([
[0.0, 0.0, 0.0],
[0.0, 0.0, 1.0],
[0.0, 1.0, 1.0]
])
heatmap = HeatmapsOnImage(heatmap_arr, shape=(3, 3, 3))
affine = iaa.Affine(translate_px={"x": 1})
aug = iaa.WithChannels([0, 1, 2], children=[affine])
heatmap_aug = aug.augment_heatmaps(heatmap)
assert heatmap_aug.shape == (3, 3, 3)
assert np.allclose(heatmap_aug.get_arr(), heatmap_arr_shifted)
def test_segmentation_map_augmentation_single_channel(self):
segmap_arr = np.int32([
[0, 0, 1],
[0, 1, 1],
[1, 1, 1]
])
segmap = SegmentationMapsOnImage(segmap_arr, shape=(3, 3, 3))
aug = iaa.WithChannels(1, children=[iaa.Affine(translate_px={"x": 1})])
segmap_aug = aug.augment_segmentation_maps(segmap)
assert segmap_aug.shape == (3, 3, 3)
assert np.array_equal(segmap_aug.get_arr(), segmap_arr)
def test_segmentation_map_augmentation_multiple_channels(self):
segmap_arr = np.int32([
[0, 0, 1],
[0, 1, 1],
[1, 1, 1]
])
segmap_arr_shifted = np.int32([
[0, 0, 0],
[0, 0, 1],
[0, 1, 1]
])
segmap = SegmentationMapsOnImage(segmap_arr, shape=(3, 3, 3))
affine = iaa.Affine(translate_px={"x": 1})
aug = iaa.WithChannels([0, 1, 2], children=[affine])
segmap_aug = aug.augment_segmentation_maps(segmap)
assert segmap_aug.shape == (3, 3, 3)
assert np.array_equal(segmap_aug.get_arr(), segmap_arr_shifted)
@classmethod
def _test_cbaoi_augmentation_single_channel(cls, cbaoi, augf_name):
affine = iaa.Affine(translate_px={"x": 1})
aug = iaa.WithChannels(1, children=[affine])
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi)
@classmethod
def _test_cbaoi_augmentation_all_channels_via_list(cls, cbaoi, cbaoi_x,
augf_name):
affine = iaa.Affine(translate_px={"x": 1})
aug = iaa.WithChannels([0, 1, 2], children=[affine])
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi_x)
@classmethod
def _test_cbaoi_augmentation_subset_of_channels(cls, cbaoi, cbaoi_x,
augf_name):
affine = iaa.Affine(translate_px={"x": 1})
aug = iaa.WithChannels([0, 1], children=[affine])
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi_x)
@classmethod
def _test_cbaoi_augmentation_with_empty_cbaoi(cls, cbaoi, augf_name):
affine = iaa.Affine(translate_px={"x": 1})
aug = iaa.WithChannels([0, 1], children=[affine])
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi)
def test_keypoint_augmentation_single_channel(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=2)]
kpsoi = ia.KeypointsOnImage(kps, shape=(5, 6, 3))
self._test_cbaoi_augmentation_single_channel(kpsoi, "augment_keypoints")
def test_keypoint_augmentation_all_channels_via_list(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=2)]
kpsoi = ia.KeypointsOnImage(kps, shape=(5, 6, 3))
kpsoi_x = kpsoi.shift(x=1)
self._test_cbaoi_augmentation_all_channels_via_list(
kpsoi, kpsoi_x, "augment_keypoints")
def test_keypoint_augmentation_subset_of_channels(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=2)]
kpsoi = ia.KeypointsOnImage(kps, shape=(5, 6, 3))
kpsoi_x = kpsoi.shift(x=1)
self._test_cbaoi_augmentation_subset_of_channels(
kpsoi, kpsoi_x, "augment_keypoints")
def test_keypoint_augmentation_with_empty_keypoints_instance(self):
kpsoi = ia.KeypointsOnImage([], shape=(5, 6, 3))
self._test_cbaoi_augmentation_with_empty_cbaoi(
kpsoi, "augment_keypoints")
def test_polygon_augmentation(self):
polygons = [ia.Polygon([(0, 0), (3, 0), (3, 3), (0, 3)])]
psoi = ia.PolygonsOnImage(polygons, shape=(5, 6, 3))
self._test_cbaoi_augmentation_single_channel(psoi, "augment_polygons")
def test_polygon_augmentation_all_channels_via_list(self):
polygons = [ia.Polygon([(0, 0), (3, 0), (3, 3), (0, 3)])]
psoi = ia.PolygonsOnImage(polygons, shape=(5, 6, 3))
psoi_x = psoi.shift(x=1)
self._test_cbaoi_augmentation_all_channels_via_list(
psoi, psoi_x, "augment_polygons")
def test_polygon_augmentation_subset_of_channels(self):
polygons = [ia.Polygon([(0, 0), (3, 0), (3, 3), (0, 3)])]
psoi = ia.PolygonsOnImage(polygons, shape=(5, 6, 3))
psoi_x = psoi.shift(x=1)
self._test_cbaoi_augmentation_subset_of_channels(
psoi, psoi_x, "augment_polygons")
def test_polygon_augmentation_with_empty_polygons_instance(self):
psoi = ia.PolygonsOnImage([], shape=(5, 6, 3))
self._test_cbaoi_augmentation_with_empty_cbaoi(
psoi, "augment_polygons")
def test_line_string_augmentation(self):
lss = [ia.LineString([(0, 0), (3, 0), (3, 3), (0, 3)])]
lsoi = ia.LineStringsOnImage(lss, shape=(5, 6, 3))
self._test_cbaoi_augmentation_single_channel(
lsoi, "augment_line_strings")
def test_line_string_augmentation_all_channels_via_list(self):
lss = [ia.LineString([(0, 0), (3, 0), (3, 3), (0, 3)])]
lsoi = ia.LineStringsOnImage(lss, shape=(5, 6, 3))
lsoi_x = lsoi.shift(x=1)
self._test_cbaoi_augmentation_all_channels_via_list(
lsoi, lsoi_x, "augment_line_strings")
def test_line_string_augmentation_subset_of_channels(self):
lss = [ia.LineString([(0, 0), (3, 0), (3, 3), (0, 3)])]
lsoi = ia.LineStringsOnImage(lss, shape=(5, 6, 3))
lsoi_x = lsoi.shift(x=1)
self._test_cbaoi_augmentation_subset_of_channels(
lsoi, lsoi_x, "augment_line_strings")
def test_line_string_augmentation_with_empty_polygons_instance(self):
lsoi = ia.LineStringsOnImage([], shape=(5, 6, 3))
self._test_cbaoi_augmentation_with_empty_cbaoi(
lsoi, "augment_line_strings")
def test_bounding_boxes_augmentation(self):
bbs = [ia.BoundingBox(x1=0, y1=0, x2=1.0, y2=1.5)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(5, 6, 3))
self._test_cbaoi_augmentation_single_channel(
bbsoi, "augment_bounding_boxes")
def test_bounding_boxes_augmentation_all_channels_via_list(self):
bbs = [ia.BoundingBox(x1=0, y1=0, x2=1.0, y2=1.5)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(5, 6, 3))
bbsoi_x = bbsoi.shift(x=1)
self._test_cbaoi_augmentation_all_channels_via_list(
bbsoi, bbsoi_x, "augment_bounding_boxes")
def test_bounding_boxes_augmentation_subset_of_channels(self):
bbs = [ia.BoundingBox(x1=0, y1=0, x2=1.0, y2=1.5)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(5, 6, 3))
bbsoi_x = bbsoi.shift(x=1)
self._test_cbaoi_augmentation_subset_of_channels(
bbsoi, bbsoi_x, "augment_bounding_boxes")
def test_bounding_boxes_augmentation_with_empty_bb_instance(self):
bbsoi = ia.BoundingBoxesOnImage([], shape=(5, 6, 3))
self._test_cbaoi_augmentation_with_empty_cbaoi(
bbsoi, "augment_bounding_boxes")
def test_invalid_datatype_for_channels_fails(self):
got_exception = False
try:
_ = iaa.WithChannels(False, iaa.Add(10))
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_invalid_datatype_for_children_fails(self):
got_exception = False
try:
_ = iaa.WithChannels(1, False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.WithChannels([0], iaa.Add(1))
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.WithChannels([0], iaa.Add(1))
image_aug = aug(image=image)
assert np.all(image_aug[..., 0] == 1)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
aug = iaa.WithChannels([1], iaa.Add(10))
params = aug.get_parameters()
assert len(params) == 1
assert params[0] == [1]
def test_get_children_lists(self):
children = iaa.Sequential([iaa.Add(10)])
aug = iaa.WithChannels(1, children)
assert aug.get_children_lists() == [children]
def test_to_deterministic(self):
child = iaa.Identity()
aug = iaa.WithChannels(1, [child])
aug_det = aug.to_deterministic()
assert aug_det.deterministic
assert aug_det.random_state is not aug.random_state
assert aug_det.children[0].deterministic
def test___repr___and___str__(self):
children = iaa.Sequential([iaa.Identity()])
aug = iaa.WithChannels(1, children, name="WithChannelsTest")
expected = (
"WithChannels("
"channels=[1], "
"name=WithChannelsTest, "
"children=%s, "
"deterministic=False"
")" % (str(children),))
assert aug.__repr__() == expected
assert aug.__str__() == expected
def test_other_dtypes_via_noop__bool(self):
aug = iaa.WithChannels([0], iaa.Identity())
image = np.zeros((3, 3, 2), dtype=bool)
image[0, 0, :] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert np.all(image_aug == image)
def test_other_dtypes_via_noop__uint_int(self):
aug = iaa.WithChannels([0], iaa.Identity())
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3, 2), dtype=dtype)
image[0, 0, :] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, image)
def test_other_dtypes_via_noop__float(self):
aug = iaa.WithChannels([0], iaa.Identity())
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype):
image = np.zeros((3, 3, 2), dtype=dtype)
image[0, 0, :] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == image)
def test_other_dtypes_via_flips__bool(self):
aug = iaa.WithChannels([0], iaa.Fliplr(1.0))
image = np.zeros((3, 3, 2), dtype=bool)
image[0, 0, :] = True
expected = np.zeros((3, 3, 2), dtype=bool)
expected[0, 2, 0] = True
expected[0, 0, 1] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert np.all(image_aug == expected)
def test_other_dtypes_via_flips__uint_int(self):
aug = iaa.WithChannels([0], iaa.Fliplr(1.0))
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3, 2), dtype=dtype)
image[0, 0, :] = value
expected = np.zeros((3, 3, 2), dtype=dtype)
expected[0, 2, 0] = value
expected[0, 0, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.array_equal(image_aug, expected)
def test_other_dtypes_via_flips__float(self):
aug = iaa.WithChannels([0], iaa.Fliplr(1.0))
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype):
image = np.zeros((3, 3, 2), dtype=dtype)
image[0, 0, :] = value
expected = np.zeros((3, 3, 2), dtype=dtype)
expected[0, 2, 0] = value
expected[0, 0, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert np.all(image_aug == expected)
def test_pickleable(self):
aug = iaa.WithChannels([0], iaa.Add((1, 10), seed=2),
seed=1)
runtest_pickleable_uint8_img(aug, iterations=5)
class TestChannelShuffle(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.ChannelShuffle(p=0.9, channels=[0, 2])
assert is_parameter_instance(aug.p, iap.Binomial)
assert is_parameter_instance(aug.p.p, iap.Deterministic)
assert np.allclose(aug.p.p.value, 0.9)
assert aug.channels == [0, 2]
def test_p_is_1(self):
aug = iaa.ChannelShuffle(p=1.0)
img = np.uint8([0, 1]).reshape((1, 1, 2))
expected = [
np.uint8([0, 1]).reshape((1, 1, 2)),
np.uint8([1, 0]).reshape((1, 1, 2))
]
seen = [False, False]
for _ in sm.xrange(100):
img_aug = aug.augment_image(img)
if np.array_equal(img_aug, expected[0]):
seen[0] = True
elif np.array_equal(img_aug, expected[1]):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_p_is_0(self):
aug = iaa.ChannelShuffle(p=0)
img = np.uint8([0, 1]).reshape((1, 1, 2))
for _ in sm.xrange(20):
img_aug = aug.augment_image(img)
assert np.array_equal(img_aug, img)
def test_p_is_1_and_channels_is_limited_subset(self):
aug = iaa.ChannelShuffle(p=1.0, channels=[0, 2])
img = np.uint8([0, 1, 2]).reshape((1, 1, 3))
expected = [
np.uint8([0, 1, 2]).reshape((1, 1, 3)),
np.uint8([2, 1, 0]).reshape((1, 1, 3))
]
seen = [False, False]
for _ in sm.xrange(100):
img_aug = aug.augment_image(img)
if np.array_equal(img_aug, expected[0]):
seen[0] = True
elif np.array_equal(img_aug, expected[1]):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_get_parameters(self):
aug = iaa.ChannelShuffle(p=1.0, channels=[0, 2])
assert aug.get_parameters()[0] == aug.p
assert aug.get_parameters()[1] == aug.channels
def test_heatmaps_must_not_change(self):
aug = iaa.ChannelShuffle(p=1.0)
hm = ia.HeatmapsOnImage(np.float32([[0, 0.5, 1.0]]), shape=(4, 4, 3))
hm_aug = aug.augment_heatmaps([hm])[0]
assert hm_aug.shape == (4, 4, 3)
assert hm_aug.arr_0to1.shape == (1, 3, 1)
assert np.allclose(hm.arr_0to1, hm_aug.arr_0to1)
def test_segmentation_maps_must_not_change(self):
aug = iaa.ChannelShuffle(p=1.0)
segmap = SegmentationMapsOnImage(np.int32([[0, 1, 2]]), shape=(4, 4, 3))
segmap_aug = aug.augment_segmentation_maps([segmap])[0]
assert segmap_aug.shape == (4, 4, 3)
assert segmap_aug.arr.shape == (1, 3, 1)
assert np.array_equal(segmap.arr, segmap_aug.arr)
def test_keypoints_must_not_change(self):
aug = iaa.ChannelShuffle(p=1.0)
kpsoi = ia.KeypointsOnImage([
ia.Keypoint(x=3, y=1), ia.Keypoint(x=2, y=4)
], shape=(10, 10, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert_cbaois_equal(kpsoi_aug, kpsoi)
def test_polygons_must_not_change(self):
aug = iaa.ChannelShuffle(p=1.0)
psoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (5, 0), (5, 5)])
], shape=(10, 10, 3))
psoi_aug = aug.augment_polygons(psoi)
assert_cbaois_equal(psoi_aug, psoi)
def test_line_strings_must_not_change(self):
aug = iaa.ChannelShuffle(p=1.0)
lsoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (5, 0), (5, 5)])
], shape=(10, 10, 3))
lsoi_aug = aug.augment_line_strings(lsoi)
assert_cbaois_equal(lsoi_aug, lsoi)
def test_bounding_boxes_must_not_change(self):
aug = iaa.ChannelShuffle(p=1.0)
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=0, x2=1.0, y2=1.5)
], shape=(10, 10, 3))
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
assert_cbaois_equal(bbsoi_aug, bbsoi)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.ChannelShuffle(1.0)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.ChannelShuffle(1.0)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_other_dtypes_bool(self):
aug = iaa.ChannelShuffle(p=0.5)
image = np.zeros((3, 3, 2), dtype=bool)
image[0, 0, 0] = True
expected = [np.zeros((3, 3, 2), dtype=bool) for _ in sm.xrange(2)]
expected[0][0, 0, 0] = True
expected[1][0, 0, 1] = True
seen = [False, False]
for _ in sm.xrange(100):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
if np.all(image_aug == expected[0]):
seen[0] = True
elif np.all(image_aug == expected[1]):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_other_dtypes_uint_int(self):
aug = iaa.ChannelShuffle(p=0.5)
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
value = max_value
image = np.zeros((3, 3, 2), dtype=dtype)
image[0, 0, 0] = value
expected = [np.zeros((3, 3, 2), dtype=dtype)
for _
in sm.xrange(2)]
expected[0][0, 0, 0] = value
expected[1][0, 0, 1] = value
seen = [False, False]
for _ in sm.xrange(100):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
if np.all(image_aug == expected[0]):
seen[0] = True
elif np.all(image_aug == expected[1]):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_other_dtypes_float(self):
aug = iaa.ChannelShuffle(p=0.5)
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = ["float16", "float32", "float64"] + f128
values = [5000, 1000 ** 2, 1000 ** 3, 1000 ** 4]
for dtype, value in zip(dtypes, values):
with self.subTest(dtype=dtype):
image = np.zeros((3, 3, 2), dtype=dtype)
image[0, 0, 0] = value
expected = [np.zeros((3, 3, 2), dtype=dtype)
for _
in sm.xrange(2)]
expected[0][0, 0, 0] = value
expected[1][0, 0, 1] = value
seen = [False, False]
for _ in sm.xrange(100):
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
if np.all(image_aug == expected[0]):
seen[0] = True
elif np.all(image_aug == expected[1]):
seen[1] = True
else:
assert False
if np.all(seen):
break
assert np.all(seen)
def test_pickleable(self):
aug = iaa.ChannelShuffle(0.5, seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(2, 2, 10))
class TestRemoveCBAsByOutOfImageFraction(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
assert np.isclose(aug.fraction, 0.51)
def test_no_cbas_in_batch(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = [
"uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64",
"float16", "float32", "float64",
"bool"
] + f128
for dt in dtypes:
arr = np.ones((5, 10, 3), dtype=dt)
image_aug = aug(image=arr)
assert image_aug.dtype.name == dt
assert image_aug.shape == (5, 10, 3)
if arr.dtype.kind == "f":
assert np.allclose(image_aug, 1.0)
else:
assert np.all(image_aug == 1)
def test_keypoints(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
item1 = ia.Keypoint(x=5, y=1)
item2 = ia.Keypoint(x=15, y=1)
cbaoi = ia.KeypointsOnImage([item1, item2], shape=(10, 10, 3))
cbaoi_aug = aug(keypoints=cbaoi)
assert len(cbaoi_aug.items) == 1
for item_obs, item_exp in zip(cbaoi_aug.items, [item1]):
assert item_obs.coords_almost_equals(item_exp)
def test_bounding_boxes(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
item1 = ia.BoundingBox(y1=1, x1=5, y2=6, x2=9)
item2 = ia.BoundingBox(y1=1, x1=5, y2=6, x2=15)
item3 = ia.BoundingBox(y1=1, x1=15, y2=6, x2=25)
cbaoi = ia.BoundingBoxesOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_aug = aug(bounding_boxes=cbaoi)
assert len(cbaoi_aug.items) == 2
for item_obs, item_exp in zip(cbaoi_aug.items, [item1, item2]):
assert item_obs.coords_almost_equals(item_exp)
def test_polygons(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
item1 = ia.Polygon([(5, 1), (9, 1), (9, 2), (5, 2)])
item2 = ia.Polygon([(5, 1), (15, 1), (15, 2), (5, 2)])
item3 = ia.Polygon([(15, 1), (25, 1), (25, 2), (15, 2)])
cbaoi = ia.PolygonsOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_aug = aug(polygons=cbaoi)
assert len(cbaoi_aug.items) == 2
for item_obs, item_exp in zip(cbaoi_aug.items, [item1, item2]):
assert item_obs.coords_almost_equals(item_exp)
def test_line_strings(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
item1 = ia.LineString([(5, 1), (9, 1)])
item2 = ia.LineString([(5, 1), (15, 1)])
item3 = ia.LineString([(15, 1), (25, 1)])
cbaoi = ia.LineStringsOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_aug = aug(line_strings=cbaoi)
assert len(cbaoi_aug.items) == 2
for item_obs, item_exp in zip(cbaoi_aug.items, [item1, item2]):
assert item_obs.coords_almost_equals(item_exp)
def test_get_parameters(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
params = aug.get_parameters()
assert len(params) == 1
assert np.isclose(params[0], 0.51)
def test_pickleable(self):
item1 = ia.Keypoint(x=5, y=1)
item2 = ia.Keypoint(x=15, y=1)
cbaoi = ia.KeypointsOnImage([item1, item2], shape=(10, 10, 3))
augmenter = iaa.RemoveCBAsByOutOfImageFraction(0.51)
augmenter_pkl = pickle.loads(pickle.dumps(augmenter, protocol=-1))
for _ in np.arange(3):
cbaoi_aug = augmenter(keypoints=cbaoi)
cbaoi_aug_pkl = augmenter_pkl(keypoints=cbaoi)
assert np.allclose(cbaoi_aug.to_xy_array(), cbaoi_aug_pkl.to_xy_array())
class TestClipCBAsToImagePlanes(unittest.TestCase):
def setUp(self):
reseed()
def test_no_cbas_in_batch(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
try:
f128 = [np.dtype("float128")]
except TypeError:
f128 = [] # float128 not known by user system
dtypes = [
"uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64",
"float16", "float32", "float64",
"bool"
] + f128
for dt in dtypes:
arr = np.ones((5, 10, 3), dtype=dt)
image_aug = aug(image=arr)
assert image_aug.dtype.name == dt
assert image_aug.shape == (5, 10, 3)
if arr.dtype.kind == "f":
assert np.allclose(image_aug, 1.0)
else:
assert np.all(image_aug == 1)
def test_keypoints(self):
aug = iaa.RemoveCBAsByOutOfImageFraction(0.51)
item1 = ia.Keypoint(x=5, y=1)
item2 = ia.Keypoint(x=15, y=1)
cbaoi = ia.KeypointsOnImage([item1, item2], shape=(10, 10, 3))
cbaoi_aug = aug(keypoints=cbaoi)
assert len(cbaoi_aug.items) == 1
for item_obs, item_exp in zip(cbaoi_aug.items, [item1]):
assert item_obs.coords_almost_equals(item_exp)
def test_bounding_boxes(self):
aug = iaa.ClipCBAsToImagePlanes()
item1 = ia.BoundingBox(y1=1, x1=5, y2=6, x2=9)
item2 = ia.BoundingBox(y1=1, x1=5, y2=6, x2=15)
item3 = ia.BoundingBox(y1=1, x1=15, y2=6, x2=25)
cbaoi = ia.BoundingBoxesOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_aug = aug(bounding_boxes=cbaoi)
expected = [
ia.BoundingBox(y1=1, x1=5, y2=6, x2=9),
ia.BoundingBox(y1=1, x1=5, y2=6, x2=10)
]
assert len(cbaoi_aug.items) == len(expected)
for item_obs, item_exp in zip(cbaoi_aug.items, expected):
assert item_obs.coords_almost_equals(item_exp)
def test_polygons(self):
aug = iaa.ClipCBAsToImagePlanes()
item1 = ia.Polygon([(5, 1), (9, 1), (9, 2), (5, 2)])
item2 = ia.Polygon([(5, 1), (15, 1), (15, 2), (5, 2)])
item3 = ia.Polygon([(15, 1), (25, 1), (25, 2), (15, 2)])
cbaoi = ia.PolygonsOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_aug = aug(polygons=cbaoi)
expected = [
ia.Polygon([(5, 1), (9, 1), (9, 2), (5, 2)]),
ia.Polygon([(5, 1), (10, 1), (10, 2), (5, 2)])
]
assert len(cbaoi_aug.items) == len(expected)
for item_obs, item_exp in zip(cbaoi_aug.items, expected):
assert item_obs.coords_almost_equals(item_exp)
def test_line_strings(self):
aug = iaa.ClipCBAsToImagePlanes()
item1 = ia.LineString([(5, 1), (9, 1)])
item2 = ia.LineString([(5, 1), (15, 1)])
item3 = ia.LineString([(15, 1), (25, 1)])
cbaoi = ia.LineStringsOnImage([item1, item2, item3],
shape=(10, 10, 3))
cbaoi_aug = aug(line_strings=cbaoi)
expected = [
ia.LineString([(5, 1), (9, 1)]),
ia.LineString([(5, 1), (10, 1)])
]
assert len(cbaoi_aug.items) == len(expected)
for item_obs, item_exp in zip(cbaoi_aug.items, expected):
assert item_obs.coords_almost_equals(item_exp, max_distance=1e-2)
def test_get_parameters(self):
aug = iaa.ClipCBAsToImagePlanes()
params = aug.get_parameters()
assert len(params) == 0
def test_pickleable(self):
item1 = ia.Keypoint(x=5, y=1)
item2 = ia.Keypoint(x=15, y=1)
cbaoi = ia.KeypointsOnImage([item1, item2], shape=(10, 10, 3))
augmenter = iaa.ClipCBAsToImagePlanes()
augmenter_pkl = pickle.loads(pickle.dumps(augmenter, protocol=-1))
for _ in np.arange(3):
cbaoi_aug = augmenter(keypoints=cbaoi)
cbaoi_aug_pkl = augmenter_pkl(keypoints=cbaoi)
assert np.allclose(cbaoi_aug.to_xy_array(), cbaoi_aug_pkl.to_xy_array())
================================================
FILE: test/augmenters/test_mixed_files.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import skimage
import skimage.data
import six.moves as sm
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug.testutils import (create_random_images, array_equal_lists,
keypoints_equal, reseed, assertWarns)
# TODO this should probably be tested just once for Augmenter
def test_determinism():
reseed()
images = [
ia.data.quokka(size=(128, 128)),
ia.data.quokka(size=(64, 64)),
ia.data.quokka((128, 256))
]
images.extend([ia.data.quokka(size=(16, 16))] * 20)
keypoints = [
ia.KeypointsOnImage([
ia.Keypoint(x=20, y=10), ia.Keypoint(x=5, y=5),
ia.Keypoint(x=10, y=43)], shape=(50, 60, 3))
] * 20
augs = [
iaa.Sequential([iaa.Fliplr(0.5), iaa.Flipud(0.5)]),
iaa.SomeOf(1, [iaa.Fliplr(0.5), iaa.Flipud(0.5)]),
iaa.OneOf([iaa.Fliplr(0.5), iaa.Flipud(0.5)]),
iaa.Sometimes(0.5, iaa.Fliplr(1.0)),
iaa.WithColorspace("HSV", children=iaa.Add((-50, 50))),
iaa.Resize((0.5, 0.9)),
iaa.CropAndPad(px=(-50, 50)),
iaa.Pad(px=(1, 50)),
iaa.Crop(px=(1, 50)),
iaa.Fliplr(0.5),
iaa.Flipud(0.5),
iaa.Superpixels(p_replace=(0.25, 1.0), n_segments=(16, 128)),
iaa.Grayscale(alpha=(0.1, 1.0)),
iaa.GaussianBlur((0.1, 3.0)),
iaa.AverageBlur((3, 11)),
iaa.MedianBlur((3, 11)),
iaa.Sharpen(alpha=(0.1, 1.0), lightness=(0.8, 1.2)),
iaa.Emboss(alpha=(0.1, 1.0), strength=(0.8, 1.2)),
iaa.EdgeDetect(alpha=(0.1, 1.0)),
iaa.DirectedEdgeDetect(alpha=(0.1, 1.0), direction=(0.0, 1.0)),
iaa.Add((-50, 50)),
iaa.AddElementwise((-50, 50)),
iaa.AdditiveGaussianNoise(scale=(0.1, 1.0)),
iaa.Multiply((0.6, 1.4)),
iaa.MultiplyElementwise((0.6, 1.4)),
iaa.Dropout((0.3, 0.5)),
iaa.CoarseDropout((0.3, 0.5), size_percent=(0.05, 0.2)),
iaa.Invert(0.5),
iaa.Affine(scale=(0.7, 1.3), translate_percent=(-0.1, 0.1),
rotate=(-20, 20), shear=(-20, 20), order=ia.ALL,
mode=ia.ALL, cval=(0, 255)),
iaa.PiecewiseAffine(scale=(0.1, 0.3)),
iaa.ElasticTransformation(alpha=10.0)
]
augs_affect_geometry = [
iaa.Sequential([iaa.Fliplr(0.5), iaa.Flipud(0.5)]),
iaa.SomeOf(1, [iaa.Fliplr(0.5), iaa.Flipud(0.5)]),
iaa.OneOf([iaa.Fliplr(0.5), iaa.Flipud(0.5)]),
iaa.Sometimes(0.5, iaa.Fliplr(1.0)),
iaa.Resize((0.5, 0.9)),
iaa.CropAndPad(px=(-50, 50)),
iaa.Pad(px=(1, 50)),
iaa.Crop(px=(1, 50)),
iaa.Fliplr(0.5),
iaa.Flipud(0.5),
iaa.Affine(scale=(0.7, 1.3), translate_percent=(-0.1, 0.1),
rotate=(-20, 20), shear=(-20, 20), order=ia.ALL,
mode=ia.ALL, cval=(0, 255)),
iaa.PiecewiseAffine(scale=(0.1, 0.3)),
iaa.ElasticTransformation(alpha=(5, 100), sigma=(3, 5))
]
for aug in augs:
aug_det = aug.to_deterministic()
images_aug1 = aug_det.augment_images(images)
images_aug2 = aug_det.augment_images(images)
aug_det = aug.to_deterministic()
images_aug3 = aug_det.augment_images(images)
images_aug4 = aug_det.augment_images(images)
assert array_equal_lists(images_aug1, images_aug2), \
"Images (1, 2) expected to be identical for %s" % (aug.name,)
assert array_equal_lists(images_aug3, images_aug4), \
"Images (3, 4) expected to be identical for %s" % (aug.name,)
assert not array_equal_lists(images_aug1, images_aug3), \
"Images (1, 3) expected to be different for %s" % (aug.name,)
for aug in augs_affect_geometry:
aug_det = aug.to_deterministic()
kps_aug1 = aug_det.augment_keypoints(keypoints)
kps_aug2 = aug_det.augment_keypoints(keypoints)
aug_det = aug.to_deterministic()
kps_aug3 = aug_det.augment_keypoints(keypoints)
kps_aug4 = aug_det.augment_keypoints(keypoints)
assert keypoints_equal(kps_aug1, kps_aug2), \
"Keypoints (1, 2) expected to be identical for %s" % (aug.name,)
assert keypoints_equal(kps_aug3, kps_aug4), \
"Keypoints (3, 4) expected to be identical for %s" % (aug.name,)
assert not keypoints_equal(kps_aug1, kps_aug3), \
"Keypoints (1, 3) expected to be different for %s" % (aug.name,)
class TestKeypointAugmentation(unittest.TestCase):
def setUp(self):
reseed()
def test_many_augmenters(self):
keypoints = []
for y in sm.xrange(40//5):
for x in sm.xrange(60//5):
keypoints.append(ia.Keypoint(y=y*5, x=x*5))
keypoints_oi = ia.KeypointsOnImage(keypoints, shape=(40, 60, 3))
keypoints_oi_empty = ia.KeypointsOnImage([], shape=(40, 60, 3))
augs = [
iaa.Add((-5, 5), name="Add"),
iaa.AddElementwise((-5, 5), name="AddElementwise"),
iaa.AdditiveGaussianNoise(0.01*255, name="AdditiveGaussianNoise"),
iaa.Multiply((0.95, 1.05), name="Multiply"),
iaa.Dropout(0.01, name="Dropout"),
iaa.CoarseDropout(0.01, size_px=6, name="CoarseDropout"),
iaa.Invert(0.01, per_channel=True, name="Invert"),
iaa.GaussianBlur(sigma=(0.95, 1.05), name="GaussianBlur"),
iaa.AverageBlur((3, 5), name="AverageBlur"),
iaa.MedianBlur((3, 5), name="MedianBlur"),
iaa.Sharpen((0.0, 0.1), lightness=(1.0, 1.2), name="Sharpen"),
iaa.Emboss(alpha=(0.0, 0.1), strength=(0.5, 1.5), name="Emboss"),
iaa.EdgeDetect(alpha=(0.0, 0.1), name="EdgeDetect"),
iaa.DirectedEdgeDetect(alpha=(0.0, 0.1), direction=0,
name="DirectedEdgeDetect"),
iaa.Fliplr(0.5, name="Fliplr"),
iaa.Flipud(0.5, name="Flipud"),
iaa.Affine(translate_px=(-5, 5), name="Affine-translate-px"),
iaa.Affine(translate_percent=(-0.05, 0.05),
name="Affine-translate-percent"),
iaa.Affine(rotate=(-20, 20), name="Affine-rotate"),
iaa.Affine(shear=(-20, 20), name="Affine-shear"),
iaa.Affine(scale=(0.9, 1.1), name="Affine-scale"),
iaa.PiecewiseAffine(scale=(0.001, 0.005), name="PiecewiseAffine"),
iaa.ElasticTransformation(alpha=(0.1, 0.2), sigma=(0.1, 0.2),
name="ElasticTransformation"),
iaa.BlendAlpha((0.0, 0.1), iaa.Add(10), name="BlendAlpha"),
iaa.BlendAlphaElementwise((0.0, 0.1), iaa.Add(10),
name="BlendAlphaElementwise"),
iaa.BlendAlphaSimplexNoise(iaa.Add(10), name="BlendAlphaSimplexNoise"),
iaa.BlendAlphaFrequencyNoise(exponent=(-2, 2), foreground=iaa.Add(10),
name="BlendAlphaSimplexNoise"),
iaa.Superpixels(p_replace=0.01, n_segments=64),
iaa.Resize(0.5, name="Resize"),
iaa.CropAndPad(px=(-10, 10), name="CropAndPad"),
iaa.Pad(px=(0, 10), name="Pad"),
iaa.Crop(px=(0, 10), name="Crop")
]
for aug in augs:
dss = []
for i in sm.xrange(10):
aug_det = aug.to_deterministic()
kp_fully_empty_aug = aug_det.augment_keypoints([])
assert kp_fully_empty_aug == []
kp_first_empty_aug = aug_det.augment_keypoints(keypoints_oi_empty)
assert len(kp_first_empty_aug.keypoints) == 0
kp_image = keypoints_oi.to_keypoint_image(size=5)
with assertWarns(self, iaa.SuspiciousSingleImageShapeWarning):
kp_image_aug = aug_det.augment_image(kp_image)
kp_image_aug_rev = ia.KeypointsOnImage.from_keypoint_image(
kp_image_aug,
if_not_found_coords={"x": -9999, "y": -9999},
nb_channels=1
)
kp_aug = aug_det.augment_keypoints([keypoints_oi])[0]
ds = []
assert len(kp_image_aug_rev.keypoints) == len(kp_aug.keypoints), (
"Lost keypoints for '%s' (%d vs expected %d)" % (
aug.name,
len(kp_aug.keypoints),
len(kp_image_aug_rev.keypoints))
)
gen = zip(kp_aug.keypoints, kp_image_aug_rev.keypoints)
for kp_pred, kp_pred_img in gen:
kp_pred_lost = (kp_pred.x == -9999 and kp_pred.y == -9999)
kp_pred_img_lost = (kp_pred_img.x == -9999
and kp_pred_img.y == -9999)
if not kp_pred_lost and not kp_pred_img_lost:
d = np.sqrt((kp_pred.x - kp_pred_img.x) ** 2
+ (kp_pred.y - kp_pred_img.y) ** 2)
ds.append(d)
dss.extend(ds)
if len(ds) == 0:
print("[INFO] No valid keypoints found for '%s' "
"in test_keypoint_augmentation()" % (str(aug),))
assert np.average(dss) < 5.0, \
"Average distance too high (%.2f, with ds: %s)" \
% (np.average(dss), str(dss))
# TODO move these tests to the individual augmenters?
def test_unusual_channel_numbers():
reseed()
images = [
(0, create_random_images((4, 16, 16))),
(1, create_random_images((4, 16, 16, 1))),
(2, create_random_images((4, 16, 16, 2))),
(4, create_random_images((4, 16, 16, 4))),
(5, create_random_images((4, 16, 16, 5))),
(10, create_random_images((4, 16, 16, 10))),
(20, create_random_images((4, 16, 16, 20)))
]
augs = [
iaa.Add((-5, 5), name="Add"),
iaa.AddElementwise((-5, 5), name="AddElementwise"),
iaa.AdditiveGaussianNoise(0.01*255, name="AdditiveGaussianNoise"),
iaa.Multiply((0.95, 1.05), name="Multiply"),
iaa.Dropout(0.01, name="Dropout"),
iaa.CoarseDropout(0.01, size_px=6, name="CoarseDropout"),
iaa.Invert(0.01, per_channel=True, name="Invert"),
iaa.GaussianBlur(sigma=(0.95, 1.05), name="GaussianBlur"),
iaa.AverageBlur((3, 5), name="AverageBlur"),
iaa.MedianBlur((3, 5), name="MedianBlur"),
iaa.Sharpen((0.0, 0.1), lightness=(1.0, 1.2), name="Sharpen"),
iaa.Emboss(alpha=(0.0, 0.1), strength=(0.5, 1.5), name="Emboss"),
iaa.EdgeDetect(alpha=(0.0, 0.1), name="EdgeDetect"),
iaa.DirectedEdgeDetect(alpha=(0.0, 0.1), direction=0,
name="DirectedEdgeDetect"),
iaa.Fliplr(0.5, name="Fliplr"),
iaa.Flipud(0.5, name="Flipud"),
iaa.Affine(translate_px=(-5, 5), name="Affine-translate-px"),
iaa.Affine(translate_percent=(-0.05, 0.05),
name="Affine-translate-percent"),
iaa.Affine(rotate=(-20, 20), name="Affine-rotate"),
iaa.Affine(shear=(-20, 20), name="Affine-shear"),
iaa.Affine(scale=(0.9, 1.1), name="Affine-scale"),
iaa.PiecewiseAffine(scale=(0.001, 0.005), name="PiecewiseAffine"),
iaa.PerspectiveTransform(scale=(0.01, 0.10),
name="PerspectiveTransform"),
iaa.ElasticTransformation(alpha=(0.1, 0.2), sigma=(0.1, 0.2),
name="ElasticTransformation"),
iaa.Sequential([iaa.Add((-5, 5)), iaa.AddElementwise((-5, 5))]),
iaa.SomeOf(1, [iaa.Add((-5, 5)), iaa.AddElementwise((-5, 5))]),
iaa.OneOf([iaa.Add((-5, 5)), iaa.AddElementwise((-5, 5))]),
iaa.Sometimes(0.5, iaa.Add((-5, 5)), name="Sometimes"),
iaa.Identity(name="Noop"),
iaa.BlendAlpha((0.0, 0.1), iaa.Add(10), name="BlendAlpha"),
iaa.BlendAlphaElementwise((0.0, 0.1), iaa.Add(10),
name="BlendAlphaElementwise"),
iaa.BlendAlphaSimplexNoise(iaa.Add(10), name="BlendAlphaSimplexNoise"),
iaa.BlendAlphaFrequencyNoise(exponent=(-2, 2),
foreground=iaa.Add(10),
name="BlendAlphaSimplexNoise"),
iaa.Superpixels(p_replace=0.01, n_segments=64),
iaa.Resize({"height": 4, "width": 4}, name="Resize"),
iaa.CropAndPad(px=(-10, 10), name="CropAndPad"),
iaa.Pad(px=(0, 10), name="Pad"),
iaa.Crop(px=(0, 10), name="Crop")
]
for aug in augs:
for (nb_channels, images_c) in images:
if aug.name != "Resize":
images_aug = aug.augment_images(images_c)
assert images_aug.shape == images_c.shape
image_aug = aug.augment_image(images_c[0])
assert image_aug.shape == images_c[0].shape
else:
images_aug = aug.augment_images(images_c)
image_aug = aug.augment_image(images_c[0])
if images_c.ndim == 3:
assert images_aug.shape == (4, 4, 4)
assert image_aug.shape == (4, 4)
else:
assert images_aug.shape == (4, 4, 4, images_c.shape[3])
assert image_aug.shape == (4, 4, images_c.shape[3])
# TODO move these tests to the individual augmenters?
def test_dtype_preservation():
reseed()
size = (4, 16, 16, 3)
images = [
np.random.uniform(0, 255, size).astype(np.uint8),
np.random.uniform(0, 65535, size).astype(np.uint16),
np.random.uniform(0, 4294967295, size).astype(np.uint32),
np.random.uniform(-128, 127, size).astype(np.int16),
np.random.uniform(-32768, 32767, size).astype(np.int32),
np.random.uniform(0.0, 1.0, size).astype(np.float32),
np.random.uniform(-1000.0, 1000.0, size).astype(np.float16),
np.random.uniform(-1000.0, 1000.0, size).astype(np.float32),
np.random.uniform(-1000.0, 1000.0, size).astype(np.float64)
]
default_dtypes = set([arr.dtype for arr in images])
# Some dtypes are here removed per augmenter, because the respective
# augmenter does not support them. This test currently only checks whether
# dtypes are preserved from in- to output for all dtypes that are supported
# per augmenter.
# dtypes are here removed via list comprehension instead of
# `default_dtypes - set([dtype])`, because the latter one simply never
# removed the dtype(s) for some reason
def _not_dts(dts):
return [dt for dt in default_dtypes if dt not in dts]
augs = [
(iaa.Add((-5, 5), name="Add"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.AddElementwise((-5, 5), name="AddElementwise"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.AdditiveGaussianNoise(0.01*255, name="AdditiveGaussianNoise"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.Multiply((0.95, 1.05), name="Multiply"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.Dropout(0.01, name="Dropout"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.CoarseDropout(0.01, size_px=6, name="CoarseDropout"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.Invert(0.01, per_channel=True, name="Invert"),
default_dtypes),
(iaa.GaussianBlur(sigma=(0.95, 1.05), name="GaussianBlur"),
_not_dts([np.float16])),
(iaa.AverageBlur((3, 5), name="AverageBlur"),
_not_dts([np.uint32, np.int32, np.float16])),
(iaa.MedianBlur((3, 5), name="MedianBlur"),
_not_dts([np.uint32, np.int32, np.float16, np.float64])),
(iaa.BilateralBlur((3, 5), name="BilateralBlur"),
_not_dts([np.uint16, np.uint32, np.int16, np.int32, np.float16,
np.float64])),
(iaa.Sharpen((0.0, 0.1), lightness=(1.0, 1.2), name="Sharpen"),
_not_dts([np.uint32, np.int32, np.float16, np.uint32])),
(iaa.Emboss(alpha=(0.0, 0.1), strength=(0.5, 1.5), name="Emboss"),
_not_dts([np.uint32, np.int32, np.float16, np.uint32])),
(iaa.EdgeDetect(alpha=(0.0, 0.1), name="EdgeDetect"),
_not_dts([np.uint32, np.int32, np.float16, np.uint32])),
(iaa.DirectedEdgeDetect(alpha=(0.0, 0.1), direction=0,
name="DirectedEdgeDetect"),
_not_dts([np.uint32, np.int32, np.float16, np.uint32])),
(iaa.Fliplr(0.5, name="Fliplr"), default_dtypes),
(iaa.Flipud(0.5, name="Flipud"), default_dtypes),
(iaa.Affine(translate_px=(-5, 5), name="Affine-translate-px"),
_not_dts([np.uint32, np.int32])),
(iaa.Affine(translate_percent=(-0.05, 0.05),
name="Affine-translate-percent"),
_not_dts([np.uint32, np.int32])),
(iaa.Affine(rotate=(-20, 20), name="Affine-rotate"),
_not_dts([np.uint32, np.int32])),
(iaa.Affine(shear=(-20, 20), name="Affine-shear"),
_not_dts([np.uint32, np.int32])),
(iaa.Affine(scale=(0.9, 1.1), name="Affine-scale"),
_not_dts([np.uint32, np.int32])),
(iaa.PiecewiseAffine(scale=(0.001, 0.005), name="PiecewiseAffine"),
default_dtypes),
(iaa.ElasticTransformation(alpha=(0.1, 0.2), sigma=(0.1, 0.2),
name="ElasticTransformation"),
_not_dts([np.float16])),
(iaa.Sequential([iaa.Identity(), iaa.Identity()],
name="SequentialNoop"),
default_dtypes),
(iaa.SomeOf(1, [iaa.Identity(), iaa.Identity()], name="SomeOfNoop"),
default_dtypes),
(iaa.OneOf([iaa.Identity(), iaa.Identity()], name="OneOfNoop"),
default_dtypes),
(iaa.Sometimes(0.5, iaa.Identity(), name="SometimesNoop"),
default_dtypes),
(iaa.Sequential([iaa.Add((-5, 5)), iaa.AddElementwise((-5, 5))],
name="Sequential"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.SomeOf(1,
[iaa.Add((-5, 5)), iaa.AddElementwise((-5, 5))],
name="SomeOf"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.OneOf([iaa.Add((-5, 5)), iaa.AddElementwise((-5, 5))],
name="OneOf"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.Sometimes(0.5, iaa.Add((-5, 5)), name="Sometimes"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.Identity(name="Identity"), default_dtypes),
(iaa.BlendAlpha((0.0, 0.1), iaa.Identity(), name="BlendAlphaIdentity"),
_not_dts([np.float64])), # float64 requires float128 support
(iaa.BlendAlphaElementwise((0.0, 0.1), iaa.Identity(),
name="BlendAlphaElementwiseIdentity"),
_not_dts([np.float64])), # float64 requires float128 support
(iaa.BlendAlphaSimplexNoise(iaa.Identity(),
name="BlendAlphaSimplexNoiseIdentity"),
_not_dts([np.float64])), # float64 requires float128 support
(iaa.BlendAlphaFrequencyNoise(exponent=(-2, 2),
foreground=iaa.Identity(),
name="BlendAlphaFrequencyNoiseIdentity"),
_not_dts([np.float64])),
(iaa.BlendAlpha((0.0, 0.1), iaa.Add(10), name="BlendAlpha"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.BlendAlphaElementwise((0.0, 0.1), iaa.Add(10),
name="BlendAlphaElementwise"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.BlendAlphaSimplexNoise(iaa.Add(10), name="BlendAlphaSimplexNoise"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.BlendAlphaFrequencyNoise(exponent=(-2, 2),
foreground=iaa.Add(10),
name="BlendAlphaFrequencyNoise"),
_not_dts([np.uint32, np.int32, np.float64])),
(iaa.Superpixels(p_replace=0.01, n_segments=64),
_not_dts([np.float16, np.float32, np.float64])),
(iaa.Resize({"height": 4, "width": 4}, name="Resize"),
_not_dts([np.uint16, np.uint32, np.int16, np.int32, np.float32,
np.float16, np.float64])),
(iaa.CropAndPad(px=(-10, 10), name="CropAndPad"),
_not_dts([np.uint16, np.uint32, np.int16, np.int32, np.float32,
np.float16, np.float64])),
(iaa.Pad(px=(0, 10), name="Pad"),
_not_dts([np.uint16, np.uint32, np.int16, np.int32, np.float32,
np.float16, np.float64])),
(iaa.Crop(px=(0, 10), name="Crop"),
_not_dts([np.uint16, np.uint32, np.int16, np.int32, np.float32,
np.float16, np.float64]))
]
for (aug, allowed_dtypes) in augs:
for images_i in images:
if images_i.dtype in allowed_dtypes:
images_aug = aug.augment_images(images_i)
assert images_aug.dtype == images_i.dtype
================================================
FILE: test/augmenters/test_overlay.py
================================================
from __future__ import print_function, division, absolute_import
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import imgaug.augmenters as iaa
import imgaug.augmenters.overlay as overlay
class Test_blend_alpha(unittest.TestCase):
def test_warns_that_it_is_deprecated(self):
image_fg = np.zeros((1, 1, 3), dtype=np.uint8)
image_bg = np.copy(image_fg)
alpha = 1
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = overlay.blend_alpha(image_fg, image_bg, alpha)
assert len(caught_warnings) == 1
assert (
"imgaug.augmenters.blend.blend_alpha"
in str(caught_warnings[-1].message)
)
class TestAlpha(unittest.TestCase):
def test_warns_that_it_is_deprecated(self):
children_fg = iaa.Identity()
factor = 1
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = overlay.Alpha(factor, children_fg)
assert len(caught_warnings) == 2
assert (
"imgaug.augmenters.blend.BlendAlpha"
in str(caught_warnings[0].message)
)
class TestAlphaElementwise(unittest.TestCase):
def test_warns_that_it_is_deprecated(self):
children_fg = iaa.Identity()
factor = 1
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = overlay.AlphaElementwise(factor, children_fg)
assert len(caught_warnings) == 2
assert (
"imgaug.augmenters.blend.BlendAlphaElementwise"
in str(caught_warnings[0].message)
)
class TestSimplexNoiseAlpha(unittest.TestCase):
def test_warns_that_it_is_deprecated(self):
children_fg = iaa.Identity()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = overlay.SimplexNoiseAlpha(children_fg)
assert len(caught_warnings) == 2
assert (
"imgaug.augmenters.blend.BlendAlphaSimplexNoise"
in str(caught_warnings[0].message)
)
class TestFrequencyNoiseAlpha(unittest.TestCase):
def test_warns_that_it_is_deprecated(self):
children_fg = iaa.Identity()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = overlay.FrequencyNoiseAlpha(first=children_fg)
assert len(caught_warnings) == 2
assert (
"imgaug.augmenters.blend.BlendAlphaFrequencyNoise"
in str(caught_warnings[0].message)
)
================================================
FILE: test/augmenters/test_pillike.py
================================================
from __future__ import print_function, division, absolute_import
import functools
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import PIL.Image
import PIL.ImageOps
import PIL.ImageEnhance
import PIL.ImageFilter
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import random as iarandom
from imgaug.testutils import reseed, runtest_pickleable_uint8_img, assertWarns
def _test_shape_hw(func):
img = np.arange(20*10).reshape((20, 10)).astype(np.uint8)
observed = func(np.copy(img))
expected = func(
np.tile(np.copy(img)[:, :, np.newaxis], (1, 1, 3)),
)[:, :, 0]
assert observed.dtype.name == "uint8"
assert observed.shape == (20, 10)
assert np.array_equal(observed, expected)
def _test_shape_hw1(func):
img = np.arange(20*10*1).reshape((20, 10, 1)).astype(np.uint8)
observed = func(np.copy(img))
expected = func(
np.tile(np.copy(img), (1, 1, 3)),
)[:, :, 0:1]
assert observed.dtype.name == "uint8"
assert observed.shape == (20, 10, 1)
assert np.array_equal(observed, expected)
class Test_solarize_(unittest.TestCase):
@mock.patch("imgaug.augmenters.arithmetic.invert_")
def test_mocked_defaults(self, mock_sol):
arr = np.zeros((1,), dtype=np.uint8)
mock_sol.return_value = "foo"
observed = iaa.pillike.solarize_(arr)
args = mock_sol.call_args_list[0][0]
kwargs = mock_sol.call_args_list[0][1]
assert args[0] is arr
assert kwargs["threshold"] == 128
assert observed == "foo"
@mock.patch("imgaug.augmenters.arithmetic.invert_")
def test_mocked(self, mock_sol):
arr = np.zeros((1,), dtype=np.uint8)
mock_sol.return_value = "foo"
observed = iaa.pillike.solarize_(arr, threshold=5)
args = mock_sol.call_args_list[0][0]
kwargs = mock_sol.call_args_list[0][1]
assert args[0] is arr
assert kwargs["threshold"] == 5
assert observed == "foo"
def test_image_shape_hw(self):
func = functools.partial(iaa.pillike.solarize_, threshold=5)
_test_shape_hw(func)
def test_image_shape_hw1(self):
func = functools.partial(iaa.pillike.solarize_, threshold=5)
_test_shape_hw1(func)
class Test_solarize(unittest.TestCase):
def test_compare_with_pil(self):
def _solarize_pil(image, threshold):
img = PIL.Image.fromarray(image)
return np.asarray(PIL.ImageOps.solarize(img, threshold))
images = [
np.mod(np.arange(20*20*3), 255).astype(np.uint8)\
.reshape((20, 20, 3)),
iarandom.RNG(0).integers(0, 256, size=(1, 1, 3), dtype="uint8"),
iarandom.RNG(1).integers(0, 256, size=(20, 20, 3), dtype="uint8"),
iarandom.RNG(2).integers(0, 256, size=(40, 40, 3), dtype="uint8"),
iarandom.RNG(0).integers(0, 256, size=(20, 20), dtype="uint8")
]
for image_idx, image in enumerate(images):
for threshold in np.arange(256):
with self.subTest(image_idx=image_idx, threshold=threshold):
image_pil = _solarize_pil(image, threshold)
image_iaa = iaa.pillike.solarize(image, threshold)
assert np.array_equal(image_pil, image_iaa)
def test_image_shape_hw(self):
func = functools.partial(iaa.pillike.solarize, threshold=5)
_test_shape_hw(func)
def test_image_shape_hw1(self):
func = functools.partial(iaa.pillike.solarize, threshold=5)
_test_shape_hw1(func)
class Test_posterize(unittest.TestCase):
def test_by_comparison_with_pil(self):
image = np.arange(64*64*3).reshape((64, 64, 3))
image = np.mod(image, 255).astype(np.uint8)
for nb_bits in [1, 2, 3, 4, 5, 6, 7, 8]:
image_iaa = iaa.pillike.posterize(np.copy(image), nb_bits)
image_pil = np.asarray(
PIL.ImageOps.posterize(
PIL.Image.fromarray(image),
nb_bits
)
)
assert np.array_equal(image_iaa, image_pil)
def test_image_shape_hw(self):
func = functools.partial(iaa.pillike.posterize, bits=2)
_test_shape_hw(func)
def test_image_shape_hw1(self):
func = functools.partial(iaa.pillike.posterize, bits=2)
_test_shape_hw1(func)
class Test_equalize(unittest.TestCase):
def test_by_comparison_with_pil(self):
shapes = [
(1, 1),
(2, 1),
(1, 2),
(2, 2),
(5, 5),
(10, 5),
(5, 10),
(10, 10),
(20, 20),
(100, 100),
(100, 200),
(200, 100),
(200, 200)
]
shapes = shapes + [shape + (3,) for shape in shapes]
rng = iarandom.RNG(0)
images = [rng.integers(0, 255, size=shape).astype(np.uint8)
for shape in shapes]
images = images + [
np.full((10, 10), 0, dtype=np.uint8),
np.full((10, 10), 128, dtype=np.uint8),
np.full((10, 10), 255, dtype=np.uint8)
]
for i, image in enumerate(images):
mask_vals = [False, True] if image.size >= (100*100) else [False]
for use_mask in mask_vals:
with self.subTest(image_idx=i, shape=image.shape,
use_mask=use_mask):
mask_np = None
mask_pil = None
if use_mask:
mask_np = np.zeros(image.shape[0:2], dtype=np.uint8)
mask_np[25:75, 25:75] = 1
mask_pil = PIL.Image.fromarray(mask_np).convert("L")
image_iaa = iaa.pillike.equalize(image, mask=mask_np)
image_pil = np.asarray(
PIL.ImageOps.equalize(
PIL.Image.fromarray(image),
mask=mask_pil
)
)
assert np.array_equal(image_iaa, image_pil)
def test_unusual_channel_numbers(self):
nb_channels_lst = [1, 2, 4, 5, 512, 513]
for nb_channels in nb_channels_lst:
for size in [20, 100]:
with self.subTest(nb_channels=nb_channels,
size=size):
shape = (size, size, nb_channels)
image = iarandom.RNG(0).integers(50, 150, size=shape)
image = image.astype(np.uint8)
image_aug = iaa.pillike.equalize(image)
if size > 1:
channelwise_sums = np.sum(image_aug, axis=(0, 1))
assert np.all(channelwise_sums > 0)
assert np.min(image_aug) < 50
assert np.max(image_aug) > 150
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.pillike.equalize(image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_image_shape_hw(self):
func = functools.partial(iaa.pillike.equalize)
_test_shape_hw(func)
# already covered by unusal channel numbers test, but we run this one here
# anyways for consistency with other tests and because it works a bit
# different
def test_image_shape_hw1(self):
func = functools.partial(iaa.pillike.equalize)
_test_shape_hw1(func)
class Test_autocontrast(unittest.TestCase):
def test_by_comparison_with_pil(self):
rng = iarandom.RNG(0)
shapes = [
(1, 1),
(10, 10),
(1, 1, 3),
(1, 2, 3),
(2, 1, 3),
(2, 2, 3),
(5, 3, 3),
(10, 5, 3),
(5, 10, 3),
(10, 10, 3),
(20, 10, 3),
(20, 40, 3),
(50, 60, 3),
(100, 100, 3),
(200, 100, 3)
]
images = [
rng.integers(0, 255, size=shape).astype(np.uint8)
for shape in shapes
]
images = (
images
+ [
np.full((1, 1, 3), 0, dtype=np.uint8),
np.full((1, 1, 3), 255, dtype=np.uint8),
np.full((20, 20, 3), 0, dtype=np.uint8),
np.full((20, 20, 3), 255, dtype=np.uint8)
]
)
cutoffs = [0, 1, 2, 10, 50, 90, 99, 100]
ignores = [None, 0, 1, 100, 255, [0, 1], [5, 10, 50], [99, 100]]
for cutoff in cutoffs:
for ignore in ignores:
for i, image in enumerate(images):
with self.subTest(cutoff=cutoff, ignore=ignore,
image_idx=i, image_shape=image.shape):
result_pil = np.asarray(
PIL.ImageOps.autocontrast(
PIL.Image.fromarray(image),
cutoff=cutoff,
ignore=ignore
)
)
result_iaa = iaa.pillike.autocontrast(image,
cutoff=cutoff,
ignore=ignore)
assert np.array_equal(result_pil, result_iaa)
def test_unusual_channel_numbers(self):
nb_channels_lst = [1, 2, 4, 5, 512, 513]
for nb_channels in nb_channels_lst:
for size in [20]:
for cutoff in [0, 1, 10]:
with self.subTest(nb_channels=nb_channels,
size=size,
cutoff=cutoff):
shape = (size, size, nb_channels)
image = iarandom.RNG(0).integers(50, 150, size=shape)
image = image.astype(np.uint8)
image_aug = iaa.pillike.autocontrast(image,
cutoff=cutoff)
if size > 1:
channelwise_sums = np.sum(image_aug, axis=(0, 1))
assert np.all(channelwise_sums > 0)
assert np.min(image_aug) < 50
assert np.max(image_aug) > 150
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
for cutoff in [0, 1, 10]:
for ignore in [None, 0, 1, [0, 1, 10]]:
with self.subTest(shape=shape, cutoff=cutoff,
ignore=ignore):
image = np.zeros(shape, dtype=np.uint8)
image_aug = iaa.pillike.autocontrast(image,
cutoff=cutoff,
ignore=ignore)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_image_shape_hw(self):
func = functools.partial(iaa.pillike.autocontrast)
_test_shape_hw(func)
# already covered by unusal channel numbers test, but we run this one here
# anyways for consistency with other tests and because it works a bit
# different
def test_image_shape_hw1(self):
func = functools.partial(iaa.pillike.autocontrast)
_test_shape_hw1(func)
# TODO add test for unusual channel numbers
class _TestEnhanceFunc(unittest.TestCase):
def _test_by_comparison_with_pil(
self, func, cls,
factors=(0.0, 0.01, 0.1, 0.5, 0.95, 0.99, 1.0, 1.05, 1.5, 2.0,
3.0)):
shapes = [(224, 224, 3), (32, 32, 3), (16, 8, 3), (1, 1, 3),
(32, 32, 4)]
seeds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for seed in seeds:
for shape in shapes:
for factor in factors:
with self.subTest(shape=shape, seed=seed, factor=factor):
image = iarandom.RNG(seed).integers(
0, 256, size=shape, dtype="uint8")
image_iaa = func(image, factor)
image_pil = np.asarray(
cls(
PIL.Image.fromarray(image)
).enhance(factor)
)
assert np.array_equal(image_iaa, image_pil)
def _test_zero_sized_axes(self, func,
factors=(0.0, 0.4, 1.0)):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
for factor in factors:
with self.subTest(shape=shape, factor=factor):
image = np.zeros(shape, dtype=np.uint8)
image_aug = func(image, factor=factor)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
@classmethod
def _test_image_shape_hw(self, func):
func = functools.partial(func, factor=0.2)
_test_shape_hw(func)
@classmethod
def _test_image_shape_hw1(self, func):
func = functools.partial(func, factor=0.2)
_test_shape_hw1(func)
class Test_enhance_color(_TestEnhanceFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.enhance_color,
PIL.ImageEnhance.Color)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.enhance_color)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.enhance_color)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.enhance_color)
class Test_enhance_contrast(_TestEnhanceFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.enhance_contrast,
PIL.ImageEnhance.Contrast)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.enhance_contrast)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.enhance_contrast)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.enhance_contrast)
class Test_enhance_brightness(_TestEnhanceFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.enhance_brightness,
PIL.ImageEnhance.Brightness)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.enhance_brightness)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.enhance_brightness)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.enhance_brightness)
class Test_enhance_sharpness(_TestEnhanceFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.enhance_sharpness,
PIL.ImageEnhance.Sharpness)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.enhance_brightness,
factors=[0.0, 0.4, 1.0, 1.5, 2.0])
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.enhance_sharpness)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.enhance_sharpness)
class _TestFilterFunc(unittest.TestCase):
def _test_by_comparison_with_pil(self, func, pil_kernel):
shapes = [(224, 224, 3), (32, 32, 3), (16, 8, 3), (1, 1, 3),
(32, 32, 4)]
seeds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
for seed in seeds:
for shape in shapes:
with self.subTest(shape=shape, seed=seed):
image = iarandom.RNG(seed).integers(
0, 256, size=shape, dtype="uint8")
image_iaa = func(image)
image_pil = np.asarray(
PIL.Image.fromarray(image).filter(pil_kernel)
)
assert np.array_equal(image_iaa, image_pil)
def _test_zero_sized_axes(self, func):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = func(image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
@classmethod
def _test_image_shape_hw(self, func):
_test_shape_hw(func)
@classmethod
def _test_image_shape_hw1(self, func):
_test_shape_hw1(func)
class Test_filter_blur(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_blur,
PIL.ImageFilter.BLUR)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_blur)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_blur)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_blur)
class Test_filter_smooth(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_smooth,
PIL.ImageFilter.SMOOTH)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_smooth)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_smooth)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_smooth)
class Test_filter_smooth_more(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_smooth_more,
PIL.ImageFilter.SMOOTH_MORE)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_smooth_more)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_smooth_more)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_smooth_more)
class Test_filter_edge_enhance(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_edge_enhance,
PIL.ImageFilter.EDGE_ENHANCE)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_edge_enhance)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_edge_enhance)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_edge_enhance)
class Test_filter_edge_enhance_more(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_edge_enhance_more,
PIL.ImageFilter.EDGE_ENHANCE_MORE)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_edge_enhance_more)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_edge_enhance_more)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_edge_enhance_more)
class Test_filter_find_edges(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_find_edges,
PIL.ImageFilter.FIND_EDGES)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_find_edges)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_find_edges)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_find_edges)
class Test_filter_contour(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_contour,
PIL.ImageFilter.CONTOUR)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_contour)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_contour)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_contour)
class Test_filter_emboss(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_emboss,
PIL.ImageFilter.EMBOSS)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_emboss)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_emboss)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_emboss)
class Test_filter_sharpen(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_sharpen,
PIL.ImageFilter.SHARPEN)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_sharpen)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_sharpen)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_sharpen)
class Test_filter_detail(_TestFilterFunc):
def test_by_comparison_with_pil(self):
self._test_by_comparison_with_pil(iaa.pillike.filter_detail,
PIL.ImageFilter.DETAIL)
def test_zero_sized_axes(self):
self._test_zero_sized_axes(iaa.pillike.filter_detail)
def test_image_shape_hw(self):
self._test_image_shape_hw(iaa.pillike.filter_detail)
def test_image_shape_hw1(self):
self._test_image_shape_hw1(iaa.pillike.filter_detail)
class Test_warp_affine(unittest.TestCase):
def _test_aff_by_comparison_with_pil(self, arg_name, arg_values,
matrix_gen):
shapes = [(64, 64, 3), (32, 32, 3), (16, 8, 3), (1, 1, 3),
(32, 32, 4)]
seeds = [1, 2, 3]
fillcolors = [None, 0, 128, (0, 255, 0)]
for shape in shapes:
for seed in seeds:
for fillcolor in fillcolors:
for arg_value in arg_values:
with self.subTest(shape=shape, seed=seed,
fillcolor=fillcolor,
**{arg_name: arg_value}):
image = iarandom.RNG(seed).integers(
0, 256, size=shape, dtype="uint8")
matrix = matrix_gen(arg_value)
image_warped = iaa.pillike.warp_affine(
image,
fillcolor=fillcolor,
center=(0.0, 0.0),
**{arg_name: arg_value})
image_warped_exp = np.asarray(
PIL.Image.fromarray(
image
).transform(shape[0:2][::-1],
PIL.Image.AFFINE,
matrix[:2, :].flat,
fillcolor=fillcolor)
)
assert np.array_equal(image_warped,
image_warped_exp)
def test_scale_x_by_comparison_with_pil(self):
def _matrix_gen(scale):
return np.float32([
[1/scale, 0, 0],
[0, 1, 0],
[0, 0, 1]
])
self._test_aff_by_comparison_with_pil(
"scale_x",
[0.01, 0.1, 0.9, 1.0, 1.5, 3.0],
_matrix_gen
)
def test_scale_y_by_comparison_with_pil(self):
def _matrix_gen(scale):
return np.float32([
[1, 0, 0],
[0, 1/scale, 0],
[0, 0, 1]
])
self._test_aff_by_comparison_with_pil(
"scale_y",
[0.01, 0.1, 0.9, 1.0, 1.5, 3.0],
_matrix_gen
)
def test_translate_x_by_comparison_with_pil(self):
def _matrix_gen(translate):
return np.float32([
[1, 0, -translate],
[0, 1, 0],
[0, 0, 1]
])
self._test_aff_by_comparison_with_pil(
"translate_x_px",
[-50, -10, -1, 0, 1, 10, 50],
_matrix_gen
)
def test_translate_y_by_comparison_with_pil(self):
def _matrix_gen(translate):
return np.float32([
[1, 0, 0],
[0, 1, -translate],
[0, 0, 1]
])
self._test_aff_by_comparison_with_pil(
"translate_y_px",
[-50, -10, -1, 0, 1, 10, 50],
_matrix_gen
)
def test_rotate_by_comparison_with_pil(self):
def _matrix_gen(rotate):
r = np.deg2rad(rotate)
return np.float32([
[np.cos(r), np.sin(r), 0],
[-np.sin(r), np.cos(r), 0],
[0, 0, 1]
])
self._test_aff_by_comparison_with_pil(
"rotate_deg",
[-50, -10, -1, 0, 1, 10, 50],
_matrix_gen
)
def test_shear_x_by_comparison_with_pil(self):
def _matrix_gen(shear):
s = (-1) * np.deg2rad(shear)
return np.float32([
[1, np.tanh(s), 0],
[0, 1, 0],
[0, 0, 1]
])
self._test_aff_by_comparison_with_pil(
"shear_x_deg",
[-50, -10, -1, 0, 1, 10, 50],
_matrix_gen
)
def test_shear_y_by_comparison_with_pil(self):
def _matrix_gen(shear):
s = (-1) * np.deg2rad(shear)
return np.float32([
[1, 0, 0],
[np.tanh(s), 1, 0],
[0, 0, 1]
])
self._test_aff_by_comparison_with_pil(
"shear_y_deg",
[-50, -10, -1, 0, 1, 10, 50],
_matrix_gen
)
def test_scale_x(self):
image = np.zeros((100, 100, 3), dtype=np.uint8)
image[50, 60] = 255
image_aug = iaa.pillike.warp_affine(image, scale_x=1.5)
y, x = np.unravel_index(np.argmax(image_aug[..., 0]),
image_aug.shape[0:2])
assert 50 - 1 <= y <= 50 + 1
assert x > 60
def test_scale_y(self):
image = np.zeros((100, 100, 3), dtype=np.uint8)
image[60, 50] = 255
image_aug = iaa.pillike.warp_affine(image, scale_y=1.5)
y, x = np.unravel_index(np.argmax(image_aug[..., 0]),
image_aug.shape[0:2])
assert 50 - 1 <= x <= 50 + 1
assert y > 60
def test_translate_x_px(self):
image = np.zeros((20, 20, 3), dtype=np.uint8)
image[10, 15] = 255
image_aug = iaa.pillike.warp_affine(image, translate_x_px=1)
assert image_aug[10, 15, 0] == 0
assert image_aug[10, 16, 0] == 255
assert np.all(image_aug[0, :] == 0)
def test_translate_y_px(self):
image = np.zeros((20, 20, 3), dtype=np.uint8)
image[15, 10] = 255
image_aug = iaa.pillike.warp_affine(image, translate_y_px=1)
assert image_aug[15, 10, 0] == 0
assert image_aug[16, 10, 0] == 255
assert np.all(image_aug[:, 0] == 0)
def test_rotate(self):
image = np.zeros((20, 20, 3), dtype=np.uint8)
image[0, 10] = 255
image_aug = iaa.pillike.warp_affine(image,
rotate_deg=45,
center=(0.0, 0.0))
assert image_aug[7, 7, 0] == 255
def test_shear_x(self):
image = np.zeros((20, 20, 3), dtype=np.uint8)
image[5, 10] = 255
image_aug = iaa.pillike.warp_affine(image,
shear_x_deg=20,
center=(0.0, 0.0))
y, x = np.unravel_index(np.argmax(image_aug[..., 0]),
image_aug.shape[0:2])
assert y == 5
assert x > 10
def test_shear_y(self):
image = np.zeros((20, 20, 3), dtype=np.uint8)
image[10, 15] = 255
image_aug = iaa.pillike.warp_affine(image,
shear_y_deg=20,
center=(0.0, 0.0))
y, x = np.unravel_index(np.argmax(image_aug[..., 0]),
image_aug.shape[0:2])
assert y > 10
assert x == 15
def test_fillcolor_is_none(self):
image = np.ones((20, 20, 3), dtype=np.uint8)
image_aug = iaa.pillike.warp_affine(image,
translate_x_px=1,
fillcolor=None)
assert np.all(image_aug[:, :1, :] == 0)
assert np.all(image_aug[:, 1:, :] == 1)
def test_fillcolor_is_int(self):
image = np.ones((20, 20, 3), dtype=np.uint8)
image_aug = iaa.pillike.warp_affine(image,
translate_x_px=1,
fillcolor=128)
assert np.all(image_aug[:, :1, 0] == 128)
assert np.all(image_aug[:, :1, 1] == 0)
assert np.all(image_aug[:, :1, 2] == 0)
assert np.all(image_aug[:, 1:, :] == 1)
def test_fillcolor_is_int_grayscale(self):
image = np.ones((20, 20), dtype=np.uint8)
image_aug = iaa.pillike.warp_affine(image,
translate_x_px=1,
fillcolor=128)
assert np.all(image_aug[:, :1] == 128)
assert np.all(image_aug[:, 1:] == 1)
def test_fillcolor_is_tuple(self):
image = np.ones((20, 20, 3), dtype=np.uint8)
image_aug = iaa.pillike.warp_affine(image,
translate_x_px=1,
fillcolor=(2, 3, 4))
assert np.all(image_aug[:, :1, 0] == 2)
assert np.all(image_aug[:, :1, 1] == 3)
assert np.all(image_aug[:, :1, 2] == 4)
assert np.all(image_aug[:, 1:, :] == 1)
def test_fillcolor_is_tuple_more_values_than_channels(self):
image = np.ones((20, 20, 3), dtype=np.uint8)
image_aug = iaa.pillike.warp_affine(image,
translate_x_px=1,
fillcolor=(2, 3, 4, 5))
assert image_aug.shape == (20, 20, 3)
assert np.all(image_aug[:, :1, 0] == 2)
assert np.all(image_aug[:, :1, 1] == 3)
assert np.all(image_aug[:, :1, 2] == 4)
assert np.all(image_aug[:, 1:, :] == 1)
def test_center(self):
image = np.zeros((21, 21, 3), dtype=np.uint8)
image[2, 10] = 255
image_aug = iaa.pillike.warp_affine(image,
rotate_deg=90,
center=(0.5, 0.5))
assert image_aug[10, 18, 0] == 255
def test_image_shape_hw(self):
func = functools.partial(iaa.pillike.warp_affine, rotate_deg=90)
_test_shape_hw(func)
def test_image_shape_hw1(self):
func = functools.partial(iaa.pillike.warp_affine, rotate_deg=90)
_test_shape_hw1(func)
class TestSolarize(unittest.TestCase):
def setUp(self):
reseed()
def test_returns_correct_instance(self):
aug = iaa.pillike.Solarize()
assert isinstance(aug, iaa.Invert)
assert aug.per_channel.value == 0
assert aug.min_value is None
assert aug.max_value is None
assert np.isclose(aug.threshold.value, 128)
assert aug.invert_above_threshold.value == 1
def test_pickleable(self):
aug = iaa.pillike.Solarize()
runtest_pickleable_uint8_img(aug)
class TestPosterize(unittest.TestCase):
def setUp(self):
reseed()
def test_returns_posterize(self):
aug = iaa.pillike.Posterize()
assert isinstance(aug, iaa.Posterize)
def test_pickleable(self):
aug = iaa.pillike.Posterize()
runtest_pickleable_uint8_img(aug)
class TestEqualize(unittest.TestCase):
def setUp(self):
reseed()
@mock.patch("imgaug.augmenters.pillike.equalize_")
def test_mocked(self, mock_eq):
image = np.arange(1*1*3).astype(np.uint8).reshape((1, 1, 3))
mock_eq.return_value = np.copy(image)
aug = iaa.pillike.Equalize()
_image_aug = aug(image=image)
assert mock_eq.call_count == 1
assert np.array_equal(mock_eq.call_args_list[0][0][0], image)
def test_integrationtest(self):
rng = iarandom.RNG(0)
for size in [20, 100]:
shape = (size, size, 3)
image = rng.integers(50, 150, size=shape)
image = image.astype(np.uint8)
aug = iaa.pillike.Equalize()
image_aug = aug(image=image)
if size > 1:
channelwise_sums = np.sum(image_aug, axis=(0, 1))
assert np.all(channelwise_sums > 0)
assert np.min(image_aug) < 50
assert np.max(image_aug) > 150
def test_pickleable(self):
aug = iaa.pillike.Equalize()
runtest_pickleable_uint8_img(aug)
class TestAutocontrast(unittest.TestCase):
def setUp(self):
reseed()
@mock.patch("imgaug.augmenters.pillike.autocontrast")
def test_mocked(self, mock_auto):
image = np.mod(np.arange(10*10*3), 255)
image = image.reshape((10, 10, 3)).astype(np.uint8)
mock_auto.return_value = image
aug = iaa.pillike.Autocontrast(15)
_image_aug = aug(image=image)
assert np.array_equal(mock_auto.call_args_list[0][0][0], image)
assert mock_auto.call_args_list[0][0][1] == 15
@mock.patch("imgaug.augmenters.pillike.autocontrast")
def test_per_channel(self, mock_auto):
image = np.mod(np.arange(10*10*1), 255)
image = image.reshape((10, 10, 1)).astype(np.uint8)
image = np.tile(image, (1, 1, 100))
mock_auto.return_value = image[..., 0]
aug = iaa.pillike.Autocontrast((0, 30), per_channel=True)
with assertWarns(self, iaa.SuspiciousSingleImageShapeWarning):
_image_aug = aug(image=image)
assert mock_auto.call_count == 100
cutoffs = []
for i in np.arange(100):
assert np.array_equal(mock_auto.call_args_list[i][0][0],
image[..., i])
cutoffs.append(mock_auto.call_args_list[i][0][1])
assert len(set(cutoffs)) > 10
def test_integrationtest(self):
image = iarandom.RNG(0).integers(50, 150, size=(100, 100, 3))
image = image.astype(np.uint8)
aug = iaa.pillike.Autocontrast(10)
image_aug = aug(image=image)
assert np.min(image_aug) < 50
assert np.max(image_aug) > 150
def test_integrationtest_per_channel(self):
image = iarandom.RNG(0).integers(50, 150, size=(100, 100, 50))
image = image.astype(np.uint8)
aug = iaa.pillike.Autocontrast(10, per_channel=True)
with assertWarns(self, iaa.SuspiciousSingleImageShapeWarning):
image_aug = aug(image=image)
assert np.min(image_aug) < 50
assert np.max(image_aug) > 150
def test_pickleable(self):
aug = iaa.pillike.Autocontrast((0, 30), per_channel=0.5)
runtest_pickleable_uint8_img(aug)
class TestEnhanceColor(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
aug = iaa.pillike.EnhanceColor()
assert np.isclose(aug.factor.a.value, 0.0)
assert np.isclose(aug.factor.b.value, 3.0)
def test___init___custom(self):
aug = iaa.pillike.EnhanceColor(0.75)
assert np.isclose(aug.factor.value, 0.75)
@mock.patch("imgaug.augmenters.pillike.enhance_color")
def test_mocked(self, mock_pilcol):
aug = iaa.pillike.EnhanceColor(0.75)
image = np.zeros((1, 1, 3), dtype=np.uint8)
mock_pilcol.return_value = np.full((1, 1, 3), 128, dtype=np.uint8)
image_aug = aug(image=image)
assert mock_pilcol.call_count == 1
assert ia.is_np_array(mock_pilcol.call_args_list[0][0][0])
assert np.isclose(mock_pilcol.call_args_list[0][0][1], 0.75, rtol=0,
atol=1e-4)
assert np.all(image_aug == 128)
def test_simple_image(self):
aug = iaa.pillike.EnhanceColor(0.0)
image = np.zeros((1, 1, 3), dtype=np.uint8)
image[:, :, 0] = 255
image[:, :, 1] = 255
image_aug = aug(image=image)
assert image_aug[:, :, 2] > 200
assert np.all(image_aug[:, :, 0] == image_aug[:, :, 1])
assert np.all(image_aug[:, :, 0] == image_aug[:, :, 2])
def test_batch_contains_no_images(self):
aug = iaa.pillike.EnhanceColor(0.75)
hm_arr = np.ones((3, 3, 1), dtype=np.float32)
hm = ia.HeatmapsOnImage(hm_arr, shape=(3, 3, 3))
hm_aug = aug(heatmaps=hm)
assert np.allclose(hm_aug.get_arr(), hm.get_arr())
def test_get_parameters(self):
aug = iaa.pillike.EnhanceColor(0.75)
params = aug.get_parameters()
assert params[0] is aug.factor
def test_pickleable(self):
aug = iaa.pillike.EnhanceColor((0.1, 2.0))
runtest_pickleable_uint8_img(aug)
# we don't have to test very much here, because some functions of the base
# class are already tested via EnhanceColor
class TestEnhanceContrast(unittest.TestCase):
def setUp(self):
reseed()
@mock.patch("imgaug.augmenters.pillike.enhance_contrast")
def test_mocked(self, mock_pilco):
aug = iaa.pillike.EnhanceContrast(0.75)
image = np.zeros((1, 1, 3), dtype=np.uint8)
mock_pilco.return_value = np.full((1, 1, 3), 128, dtype=np.uint8)
image_aug = aug(image=image)
assert mock_pilco.call_count == 1
assert ia.is_np_array(mock_pilco.call_args_list[0][0][0])
assert np.isclose(mock_pilco.call_args_list[0][0][1], 0.75, rtol=0,
atol=1e-4)
assert np.all(image_aug == 128)
def test_simple_image(self):
aug = iaa.pillike.EnhanceContrast(0.0)
image = np.full((2, 2, 3), 128, dtype=np.uint8)
image[0, :, :] = 200
image_aug = aug(image=image)
diff_before = np.average(np.abs(image.astype(np.int32)
- np.average(image)))
diff_after = np.average(np.abs(image_aug.astype(np.int32)
- np.average(image_aug)))
assert diff_after < diff_before
def test_batch_contains_no_images(self):
aug = iaa.pillike.EnhanceContrast(0.75)
hm_arr = np.ones((3, 3, 1), dtype=np.float32)
hm = ia.HeatmapsOnImage(hm_arr, shape=(3, 3, 3))
hm_aug = aug(heatmaps=hm)
assert np.allclose(hm_aug.get_arr(), hm.get_arr())
def test_pickleable(self):
aug = iaa.pillike.EnhanceContrast((0.1, 2.0))
runtest_pickleable_uint8_img(aug)
# we don't have to test very much here, because some functions of the base
# class are already tested via EnhanceColor
class TestEnhanceBrightness(unittest.TestCase):
def setUp(self):
reseed()
@mock.patch("imgaug.augmenters.pillike.enhance_brightness")
def test_mocked(self, mock_pilbr):
aug = iaa.pillike.EnhanceBrightness(0.75)
image = np.zeros((1, 1, 3), dtype=np.uint8)
mock_pilbr.return_value = np.full((1, 1, 3), 128, dtype=np.uint8)
image_aug = aug(image=image)
assert mock_pilbr.call_count == 1
assert ia.is_np_array(mock_pilbr.call_args_list[0][0][0])
assert np.isclose(mock_pilbr.call_args_list[0][0][1], 0.75, rtol=0,
atol=1e-4)
assert np.all(image_aug == 128)
def test_simple_image(self):
aug = iaa.pillike.EnhanceBrightness(0.0)
image = np.full((2, 2, 3), 255, dtype=np.uint8)
image_aug = aug(image=image)
assert np.all(image_aug < 255)
def test_batch_contains_no_images(self):
aug = iaa.pillike.EnhanceBrightness(0.75)
hm_arr = np.ones((3, 3, 1), dtype=np.float32)
hm = ia.HeatmapsOnImage(hm_arr, shape=(3, 3, 3))
hm_aug = aug(heatmaps=hm)
assert np.allclose(hm_aug.get_arr(), hm.get_arr())
def test_pickleable(self):
aug = iaa.pillike.EnhanceBrightness((0.1, 2.0))
runtest_pickleable_uint8_img(aug)
# we don't have to test very much here, because some functions of the base
# class are already tested via EnhanceColor
class TestEnhanceSharpness(unittest.TestCase):
def setUp(self):
reseed()
@mock.patch("imgaug.augmenters.pillike.enhance_sharpness")
def test_mocked(self, mock_pilsh):
aug = iaa.pillike.EnhanceSharpness(0.75)
image = np.zeros((3, 3, 3), dtype=np.uint8)
mock_pilsh.return_value = np.full((3, 3, 3), 128, dtype=np.uint8)
image_aug = aug(image=image)
assert mock_pilsh.call_count == 1
assert ia.is_np_array(mock_pilsh.call_args_list[0][0][0])
assert np.isclose(mock_pilsh.call_args_list[0][0][1], 0.75, rtol=0,
atol=1e-4)
assert np.all(image_aug == 128)
def test_simple_image(self):
aug = iaa.pillike.EnhanceSharpness(2.0)
image = np.full((3, 3, 3), 64, dtype=np.uint8)
image[1, 1, :] = 128
image_aug = aug(image=image)
assert np.all(image_aug[1, 1, :] > 128)
def test_batch_contains_no_images(self):
aug = iaa.pillike.EnhanceSharpness(0.75)
hm_arr = np.ones((3, 3, 1), dtype=np.float32)
hm = ia.HeatmapsOnImage(hm_arr, shape=(3, 3, 3))
hm_aug = aug(heatmaps=hm)
assert np.allclose(hm_aug.get_arr(), hm.get_arr())
def test_pickleable(self):
aug = iaa.pillike.EnhanceSharpness((0.1, 2.0))
runtest_pickleable_uint8_img(aug)
class _TestFilter(unittest.TestCase):
def _test___init__(self, cls, func):
aug = cls()
assert aug.func is func
def _test_image(self, cls, pil_kernel):
image = ia.data.quokka(0.25)
image_aug = cls()(image=image)
image_aug_pil = PIL.Image.fromarray(image).filter(pil_kernel)
assert np.array_equal(image_aug, image_aug_pil)
def _test_pickleable(self, cls):
aug = cls()
runtest_pickleable_uint8_img(aug)
class FilterBlur(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterBlur,
iaa.pillike.filter_blur)
def test_image(self):
self._test_image(iaa.pillike.FilterBlur,
PIL.ImageFilter.BLUR)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterBlur)
class FilterSmooth(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterSmooth,
iaa.pillike.filter_smooth)
def test_image(self):
self._test_image(iaa.pillike.FilterSmooth,
PIL.ImageFilter.SMOOTH)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterSmooth)
class FilterSmoothMore(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterSmoothMore,
iaa.pillike.filter_smooth_more)
def test_image(self):
self._test_image(iaa.pillike.FilterSmoothMore,
PIL.ImageFilter.SMOOTH_MORE)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterSmoothMore)
class FilterEdgeEnhance(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterEdgeEnhance,
iaa.pillike.filter_edge_enhance)
def test_image(self):
self._test_image(iaa.pillike.FilterEdgeEnhance,
PIL.ImageFilter.EDGE_ENHANCE)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterEdgeEnhance)
class FilterEdgeEnhanceMore(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterEdgeEnhanceMore,
iaa.pillike.filter_edge_enhance_more)
def test_image(self):
self._test_image(iaa.pillike.FilterEdgeEnhanceMore,
PIL.ImageFilter.EDGE_ENHANCE_MORE)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterEdgeEnhanceMore)
class FilterFindEdges(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterFindEdges,
iaa.pillike.filter_find_edges)
def test_image(self):
self._test_image(iaa.pillike.FilterFindEdges,
PIL.ImageFilter.FIND_EDGES)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterFindEdges)
class FilterContour(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterContour,
iaa.pillike.filter_contour)
def test_image(self):
self._test_image(iaa.pillike.FilterContour,
PIL.ImageFilter.CONTOUR)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterContour)
class FilterEmboss(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterEmboss,
iaa.pillike.filter_emboss)
def test_image(self):
self._test_image(iaa.pillike.FilterEmboss,
PIL.ImageFilter.EMBOSS)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterEmboss)
class FilterSharpen(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterSharpen,
iaa.pillike.filter_sharpen)
def test_image(self):
self._test_image(iaa.pillike.FilterSharpen,
PIL.ImageFilter.SHARPEN)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterSharpen)
class FilterDetail(_TestFilter):
def test___init__(self):
self._test___init__(iaa.pillike.FilterDetail,
iaa.pillike.filter_detail)
def test_image(self):
self._test_image(iaa.pillike.FilterDetail,
PIL.ImageFilter.DETAIL)
def test_pickleable(self):
self._test_pickleable(iaa.pillike.FilterDetail)
class TestAffine(unittest.TestCase):
def setUp(self):
reseed()
@mock.patch("imgaug.augmenters.pillike.warp_affine")
def test_mocked(self, mock_pilaff):
aug = iaa.pillike.Affine(
scale={"x": 1.25, "y": 1.5},
translate_px={"x": 10, "y": 20},
rotate=30,
shear={"x": 40, "y": 50},
fillcolor=100,
center=(0.1, 0.2)
)
image = np.zeros((3, 3, 3), dtype=np.uint8)
mock_pilaff.return_value = np.full((3, 3, 3), 128, dtype=np.uint8)
image_aug = aug(image=image)
assert mock_pilaff.call_count == 1
args = mock_pilaff.call_args_list[0][0]
assert np.all(args[0] == 128) # due to in-place change
kwargs = mock_pilaff.call_args_list[0][1]
assert np.isclose(kwargs["scale_x"], 1.25)
assert np.isclose(kwargs["scale_y"], 1.5)
assert np.isclose(kwargs["translate_x_px"], 10)
assert np.isclose(kwargs["translate_y_px"], 20)
assert np.isclose(kwargs["rotate_deg"], 30)
assert np.isclose(kwargs["shear_x_deg"], 40)
assert np.isclose(kwargs["shear_y_deg"], 50)
assert np.isclose(kwargs["fillcolor"][0], 100)
assert np.isclose(kwargs["fillcolor"][1], 100)
assert np.isclose(kwargs["fillcolor"][2], 100)
assert np.isclose(kwargs["center"][0], 0.1)
assert np.isclose(kwargs["center"][1], 0.2)
assert np.all(image_aug == 128)
@mock.patch("imgaug.augmenters.pillike.warp_affine")
def test_mocked_translate_percent(self, mock_pilaff):
aug = iaa.pillike.Affine(
translate_percent={"x": 1.2, "y": 1.5}
)
image = np.zeros((20, 50, 3), dtype=np.uint8)
mock_pilaff.return_value = np.full((20, 50, 3), 128, dtype=np.uint8)
image_aug = aug(image=image)
assert mock_pilaff.call_count == 1
args = mock_pilaff.call_args_list[0][0]
assert np.all(args[0] == 128) # due to in-place change
kwargs = mock_pilaff.call_args_list[0][1]
assert np.isclose(kwargs["scale_x"], 1.0)
assert np.isclose(kwargs["scale_y"], 1.0)
assert np.isclose(kwargs["translate_x_px"], 50*1.2)
assert np.isclose(kwargs["translate_y_px"], 20*1.5)
assert np.isclose(kwargs["rotate_deg"], 0)
assert np.isclose(kwargs["shear_x_deg"], 0)
assert np.isclose(kwargs["shear_y_deg"], 0)
assert np.isclose(kwargs["fillcolor"][0], 0)
assert np.isclose(kwargs["fillcolor"][1], 0)
assert np.isclose(kwargs["fillcolor"][2], 0)
assert np.isclose(kwargs["center"][0], 0.5)
assert np.isclose(kwargs["center"][1], 0.5)
assert np.all(image_aug == 128)
def test_parameters_affect_images(self):
params = [
("scale", {"x": 1.3}),
("scale", {"y": 1.5}),
("translate_px", {"x": 5}),
("translate_px", {"y": 10}),
("translate_percent", {"x": 0.3}),
("translate_percent", {"y": 0.4}),
("rotate", 10),
("shear", {"x": 20}),
("shear", {"y": 20})
]
image = ia.data.quokka_square((64, 64))
images_aug = []
for param_name, param_val in params:
kwargs = {param_name: param_val}
aug = iaa.pillike.Affine(**kwargs)
image_aug = aug(image=image)
images_aug.append(image_aug)
for i, image_aug in enumerate(images_aug):
assert not np.array_equal(image_aug, image)
for j, other_image_aug in enumerate(images_aug):
if i != j:
assert not np.array_equal(image_aug, other_image_aug)
def test_batch_contains_no_images(self):
aug = iaa.pillike.Affine(translate_px={"x": 10})
hm_arr = np.ones((3, 3, 1), dtype=np.float32)
hm = ia.HeatmapsOnImage(hm_arr, shape=(3, 3, 3))
with self.assertRaises(AssertionError):
_hm_aug = aug(heatmaps=hm)
def test_get_parameters(self):
aug = iaa.pillike.Affine(
scale={"x": 1.25, "y": 1.5},
translate_px={"x": 10, "y": 20},
rotate=30,
shear={"x": 40, "y": 50},
fillcolor=100,
center=(0.1, 0.2)
)
params = aug.get_parameters()
assert params[0] is aug.scale
assert params[1] is aug.translate
assert params[2] is aug.rotate
assert params[3] is aug.shear
assert params[4] is aug.cval
assert params[5] is aug.center
def test_pickleable(self):
aug = iaa.pillike.Affine(
scale={"x": 1.25, "y": 1.5},
translate_px={"x": 10, "y": 20},
rotate=30,
shear={"x": 40, "y": 50},
fillcolor=(100, 200),
center="uniform"
)
runtest_pickleable_uint8_img(aug)
================================================
FILE: test/augmenters/test_pooling.py
================================================
from __future__ import print_function, division, absolute_import
import itertools
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
import imgaug as ia
import imgaug.random as iarandom
import imgaug.augmenters.pooling as iapooling
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug.testutils import (reseed,
assert_cbaois_equal,
runtest_pickleable_uint8_img,
is_parameter_instance)
class Test_compute_shape_after_pooling(unittest.TestCase):
def test_random_shapes_and_kernel_sizes(self):
shapes = [
(6, 5),
(5, 6),
(6, 6),
(11, 1),
(1, 11),
(0, 1),
(1, 0),
(0, 0)
]
kernel_sizes = [1, 2, 3, 5]
nb_channels_lst = [None, 1, 3, 4]
# 8*(4*4)*4 = 512 subtests
gen = itertools.product(shapes, nb_channels_lst)
for shape_nochan, nb_channels in gen:
shape = shape_nochan
if nb_channels is not None:
shape = tuple(list(shape) + [nb_channels])
image = np.zeros(shape, dtype=np.uint8)
for ksize_h, ksize_w in itertools.product(kernel_sizes,
kernel_sizes):
with self.subTest(shape=shape, ksize_h=ksize_h,
ksize_w=ksize_w):
image_pooled = ia.avg_pool(image, (ksize_h, ksize_w))
shape_expected = image_pooled.shape
shape_observed = iapooling._compute_shape_after_pooling(
shape, ksize_h, ksize_w
)
assert shape_observed == shape_expected
class _TestPoolingAugmentersBase(object):
def setUp(self):
reseed()
@property
def augmenter(self):
raise NotImplementedError()
@mock.patch("imgaug.augmenters.pooling._AbstractPoolingBase."
"_augment_hms_and_segmaps_by_samples")
def test_augment_segmaps(self, mock_aug_segmaps):
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
arr = np.int32([
[1, 2, 3],
[4, 5, 6]
])
segmap = SegmentationMapsOnImage(arr, shape=(6, 6, 3))
mock_aug_segmaps.return_value = [segmap]
rng = iarandom.RNG(0)
aug = self.augmenter(2, keep_size=False, seed=rng)
_ = aug.augment_segmentation_maps(segmap)
assert mock_aug_segmaps.call_count == 1
# call 0, args, arg 0, segmap 0 within segmaps list
assert np.array_equal(
mock_aug_segmaps.call_args_list[0][0][0][0].arr,
segmap.arr)
def _test_augment_cbaoi__kernel_size_is_noop(self, kernel_size, cbaoi,
augf_name):
aug = self.augmenter(kernel_size)
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(cbaoi_aug, cbaoi)
def _test_augment_keypoints__kernel_size_is_noop(self, kernel_size):
from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
kps = [Keypoint(x=1.5, y=5.5), Keypoint(x=5.5, y=1.5)]
kpsoi = KeypointsOnImage(kps, shape=(6, 6, 3))
self._test_augment_cbaoi__kernel_size_is_noop(
kernel_size, kpsoi, "augment_keypoints")
def test_augment_keypoints__kernel_size_is_zero(self):
self._test_augment_keypoints__kernel_size_is_noop(0)
def test_augment_keypoints__kernel_size_is_one(self):
self._test_augment_keypoints__kernel_size_is_noop(1)
def _test_augment_polygons__kernel_size_is_noop(self, kernel_size):
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
ps = [Polygon([(1, 1), (2, 1), (2, 2)])]
psoi = PolygonsOnImage(ps, shape=(6, 6, 3))
self._test_augment_cbaoi__kernel_size_is_noop(
kernel_size, psoi, "augment_polygons")
def test_augment_polygons__kernel_size_is_zero(self):
self._test_augment_polygons__kernel_size_is_noop(0)
def test_augment_polygons__kernel_size_is_one(self):
self._test_augment_polygons__kernel_size_is_noop(1)
def _test_augment_line_strings__kernel_size_is_noop(self, kernel_size):
from imgaug.augmentables.lines import LineString, LineStringsOnImage
ls = [LineString([(1, 1), (2, 1), (2, 2)])]
lsoi = LineStringsOnImage(ls, shape=(6, 6, 3))
self._test_augment_cbaoi__kernel_size_is_noop(
kernel_size, lsoi, "augment_line_strings")
def test_augment_line_strings__kernel_size_is_zero(self):
self._test_augment_line_strings__kernel_size_is_noop(0)
def test_augment_line_strings__kernel_size_is_one(self):
self._test_augment_line_strings__kernel_size_is_noop(1)
def _test_augment_bounding_boxes__kernel_size_is_noop(self, kernel_size):
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
bbs = [BoundingBox(x1=1, y1=2, x2=3, y2=4)]
bbsoi = BoundingBoxesOnImage(bbs, shape=(6, 6, 3))
self._test_augment_cbaoi__kernel_size_is_noop(
kernel_size, bbsoi, "augment_bounding_boxes")
def test_augment_bounding_boxes__kernel_size_is_zero(self):
self._test_augment_bounding_boxes__kernel_size_is_noop(0)
def test_augment_bounding_boxes__kernel_size_is_one(self):
self._test_augment_bounding_boxes__kernel_size_is_noop(1)
def _test_augment_heatmaps__kernel_size_is_noop(self, kernel_size):
from imgaug.augmentables.heatmaps import HeatmapsOnImage
arr = np.float32([
[0.5, 0.6, 0.7],
[0.4, 0.5, 0.6]
])
heatmaps = HeatmapsOnImage(arr, shape=(6, 6, 3))
aug = self.augmenter(kernel_size)
heatmaps_aug = aug.augment_heatmaps(heatmaps)
assert heatmaps_aug.shape == (6, 6, 3)
assert np.allclose(heatmaps_aug.arr_0to1, arr[..., np.newaxis])
def test_augment_heatmaps__kernel_size_is_zero(self):
self._test_augment_heatmaps__kernel_size_is_noop(0)
def test_augment_heatmaps__kernel_size_is_one(self):
self._test_augment_heatmaps__kernel_size_is_noop(1)
def _test_augment_segmaps__kernel_size_is_noop(self, kernel_size):
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
arr = np.int32([
[0, 1, 2],
[1, 2, 3]
])
segmaps = SegmentationMapsOnImage(arr, shape=(6, 6, 3))
aug = self.augmenter(kernel_size)
segmaps_aug = aug.augment_segmentation_maps(segmaps)
assert segmaps_aug.shape == (6, 6, 3)
assert np.allclose(segmaps_aug.arr, arr[..., np.newaxis])
def test_augment_segmaps__kernel_size_is_zero(self):
self._test_augment_segmaps__kernel_size_is_noop(0)
def test_augment_segmaps__kernel_size_is_one(self):
self._test_augment_segmaps__kernel_size_is_noop(1)
def _test_augment_cbaoi__kernel_size_is_two__keep_size(
self, cbaoi, augf_name):
aug = self.augmenter(2, keep_size=True)
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi)
def test_augment_keypoints__kernel_size_is_two__keep_size(self):
from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
kps = [Keypoint(x=1.5, y=5.5), Keypoint(x=5.5, y=1.5)]
kpsoi = KeypointsOnImage(kps, shape=(6, 6, 3))
self._test_augment_cbaoi__kernel_size_is_two__keep_size(
kpsoi, "augment_keypoints")
def test_augment_polygons__kernel_size_is_two__keep_size(self):
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
polys = [Polygon([(0, 0), (2, 0), (2, 2)])]
psoi = PolygonsOnImage(polys, shape=(6, 6, 3))
self._test_augment_cbaoi__kernel_size_is_two__keep_size(
psoi, "augment_polygons")
def test_augment_line_strings__kernel_size_is_two__keep_size(self):
from imgaug.augmentables.lines import LineString, LineStringsOnImage
ls = [LineString([(0, 0), (2, 0), (2, 2)])]
lsoi = LineStringsOnImage(ls, shape=(6, 6, 3))
self._test_augment_cbaoi__kernel_size_is_two__keep_size(
lsoi, "augment_line_strings")
def test_augment_bounding_boxes__kernel_size_is_two__keep_size(self):
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
bbs = [BoundingBox(x1=0, y1=0, x2=2, y2=2)]
bbsoi = BoundingBoxesOnImage(bbs, shape=(6, 6, 3))
self._test_augment_cbaoi__kernel_size_is_two__keep_size(
bbsoi, "augment_bounding_boxes")
def _test_augment_cbaoi__kernel_size_is_two__no_keep_size(
self, cbaoi, expected, augf_name):
aug = self.augmenter(2, keep_size=False)
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, expected)
def test_augment_keypoints__kernel_size_is_two__no_keep_size(self):
from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
kps = [Keypoint(x=1.5, y=5.5), Keypoint(x=5.5, y=1.5)]
kpsoi = KeypointsOnImage(kps, shape=(6, 6, 3))
expected = KeypointsOnImage.from_xy_array(
np.float32([
[1.5/2, 5.5/2],
[5.5/2, 1.5/2]
]),
shape=(3, 3, 3))
self._test_augment_cbaoi__kernel_size_is_two__no_keep_size(
kpsoi, expected, "augment_keypoints")
def test_augment_polygons__kernel_size_is_two__no_keep_size(self):
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
ps = [Polygon([(1.5, 1.5), (5.5, 1.5), (5.5, 5.5)])]
psoi = PolygonsOnImage(ps, shape=(6, 6, 3))
expected = PolygonsOnImage([
Polygon([(1.5/2, 1.5/2), (5.5/2, 1.5/2), (5.5/2, 5.5/2)])
], shape=(3, 3, 3))
self._test_augment_cbaoi__kernel_size_is_two__no_keep_size(
psoi, expected, "augment_polygons")
def test_augment_line_strings__kernel_size_is_two__no_keep_size(self):
from imgaug.augmentables.lines import LineString, LineStringsOnImage
ls = [LineString([(1.5, 1.5), (5.5, 1.5), (5.5, 5.5)])]
lsoi = LineStringsOnImage(ls, shape=(6, 6, 3))
expected = LineStringsOnImage([
LineString([(1.5/2, 1.5/2), (5.5/2, 1.5/2), (5.5/2, 5.5/2)])
], shape=(3, 3, 3))
self._test_augment_cbaoi__kernel_size_is_two__no_keep_size(
lsoi, expected, "augment_line_strings")
def test_augment_bounding_boxes__kernel_size_is_two__no_keep_size(self):
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
bbs = [BoundingBox(x1=1.5, y1=2.5, x2=3.5, y2=4.5)]
bbsoi = BoundingBoxesOnImage(bbs, shape=(6, 6, 3))
expected = BoundingBoxesOnImage([
BoundingBox(x1=1.5/2, y1=2.5/2, x2=3.5/2, y2=4.5/2)
], shape=(3, 3, 3))
self._test_augment_cbaoi__kernel_size_is_two__no_keep_size(
bbsoi, expected, "augment_bounding_boxes")
def test_augment_heatmaps__kernel_size_is_two__keep_size(self):
from imgaug.augmentables.heatmaps import HeatmapsOnImage
arr = np.float32([
[0.5, 0.6, 0.7],
[0.4, 0.5, 0.6]
])
heatmaps = HeatmapsOnImage(arr, shape=(6, 6, 3))
aug = self.augmenter(2, keep_size=True)
heatmaps_aug = aug.augment_heatmaps(heatmaps)
assert heatmaps_aug.shape == (6, 6, 3)
assert np.allclose(heatmaps_aug.arr_0to1, arr[..., np.newaxis])
def test_augment_heatmaps__kernel_size_is_two__no_keep_size(self):
from imgaug.augmentables.heatmaps import HeatmapsOnImage
arr = np.float32([
[0.5, 0.6, 0.7],
[0.4, 0.5, 0.6]
])
heatmaps = HeatmapsOnImage(arr, shape=(6, 6, 3))
aug = self.augmenter(2, keep_size=False)
heatmaps_aug = aug.augment_heatmaps(heatmaps)
expected = heatmaps.resize((1, 2))
assert heatmaps_aug.shape == (3, 3, 3)
assert heatmaps_aug.arr_0to1.shape == (1, 2, 1)
assert np.allclose(heatmaps_aug.arr_0to1, expected.arr_0to1)
def test_augment_segmaps__kernel_size_is_two__keep_size(self):
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
arr = np.int32([
[0, 1, 2],
[1, 2, 3]
])
segmaps = SegmentationMapsOnImage(arr, shape=(6, 6, 3))
aug = self.augmenter(2, keep_size=True)
segmaps_aug = aug.augment_segmentation_maps(segmaps)
assert segmaps_aug.shape == (6, 6, 3)
assert np.allclose(segmaps_aug.arr, arr[..., np.newaxis])
def test_augment_segmaps__kernel_size_is_two__no_keep_size(self):
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
arr = np.int32([
[0, 1, 2],
[1, 2, 3]
])
segmaps = SegmentationMapsOnImage(arr, shape=(6, 6, 3))
aug = self.augmenter(2, keep_size=False)
segmaps_aug = aug.augment_segmentation_maps(segmaps)
expected = segmaps.resize((1, 2))
assert segmaps_aug.shape == (3, 3, 3)
assert segmaps_aug.arr.shape == (1, 2, 1)
assert np.allclose(segmaps_aug.arr, expected.arr)
def _test_augment_keypoints__kernel_size_differs(self, shape,
shape_exp):
from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
kps = [Keypoint(x=1.5, y=5.5), Keypoint(x=5.5, y=1.5)]
kpsoi = KeypointsOnImage(kps, shape=shape)
aug = self.augmenter(
(iap.Deterministic(3), iap.Deterministic(2)),
keep_size=False)
kpsoi_aug = aug.augment_keypoints(kpsoi)
expected = KeypointsOnImage.from_xy_array(
np.float32([
[(1.5/shape[1])*shape_exp[1], (5.5/shape[0])*shape_exp[0]],
[(5.5/shape[1])*shape_exp[1], (1.5/shape[0])*shape_exp[0]]
]),
shape=shape_exp)
assert_cbaois_equal(kpsoi_aug, expected)
def test_augment_keypoints__kernel_size_differs(self):
self._test_augment_keypoints__kernel_size_differs((6, 6, 3), (2, 3, 3))
def test_augment_keypoints__kernel_size_differs__requires_padding(self):
self._test_augment_keypoints__kernel_size_differs((5, 6, 3), (2, 3, 3))
def _test_augment_polygons__kernel_size_differs(self, shape, shape_exp):
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
polys = [Polygon([(1.5, 5.5), (5.5, 1.5), (5.5, 5.5)])]
psoi = PolygonsOnImage(polys, shape=shape)
aug = self.augmenter(
(iap.Deterministic(3), iap.Deterministic(2)),
keep_size=False)
psoi_aug = aug.augment_polygons(psoi)
expected = PolygonsOnImage(
[Polygon([
((1.5/shape[1])*shape_exp[1], (5.5/shape[0])*shape_exp[0]),
((5.5/shape[1])*shape_exp[1], (1.5/shape[0])*shape_exp[0]),
((5.5/shape[1])*shape_exp[1], (5.5/shape[0])*shape_exp[0])
])],
shape=shape_exp)
assert_cbaois_equal(psoi_aug, expected)
def test_augment_polygons__kernel_size_differs(self):
self._test_augment_polygons__kernel_size_differs((6, 6, 3), (2, 3, 3))
def test_augment_polygons__kernel_size_differs__requires_padding(self):
self._test_augment_polygons__kernel_size_differs((5, 6, 3), (2, 3, 3))
def _test_augment_line_strings__kernel_size_differs(self, shape, shape_exp):
from imgaug.augmentables.lines import LineString, LineStringsOnImage
ls = [LineString([(1.5, 5.5), (5.5, 1.5), (5.5, 5.5)])]
lsoi = LineStringsOnImage(ls, shape=shape)
aug = self.augmenter(
(iap.Deterministic(3), iap.Deterministic(2)),
keep_size=False)
lsoi_aug = aug.augment_line_strings(lsoi)
expected = LineStringsOnImage(
[LineString([
((1.5/shape[1])*shape_exp[1], (5.5/shape[0])*shape_exp[0]),
((5.5/shape[1])*shape_exp[1], (1.5/shape[0])*shape_exp[0]),
((5.5/shape[1])*shape_exp[1], (5.5/shape[0])*shape_exp[0])
])],
shape=shape_exp)
assert_cbaois_equal(lsoi_aug, expected)
def test_augment_line_strings__kernel_size_differs(self):
self._test_augment_line_strings__kernel_size_differs((6, 6, 3),
(2, 3, 3))
def test_augment_line_strings__kernel_size_differs__requires_padding(self):
self._test_augment_line_strings__kernel_size_differs((5, 6, 3),
(2, 3, 3))
def _test_augment_bounding_boxes__kernel_size_differs(self, shape,
shape_exp):
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
bbs = [BoundingBox(x1=1.5, y1=2.5, x2=5.5, y2=6.5)]
bbsoi = BoundingBoxesOnImage(bbs, shape=shape)
aug = self.augmenter(
(iap.Deterministic(3), iap.Deterministic(2)),
keep_size=False)
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
expected = BoundingBoxesOnImage(
[BoundingBox(
x1=(1.5/shape[1])*shape_exp[1],
y1=(2.5/shape[0])*shape_exp[0],
x2=(5.5/shape[1])*shape_exp[1],
y2=(6.5/shape[0])*shape_exp[0],
)],
shape=shape_exp)
assert_cbaois_equal(bbsoi_aug, expected)
def test_augment_bounding_boxes__kernel_size_differs(self):
self._test_augment_bounding_boxes__kernel_size_differs((6, 6, 3),
(2, 3, 3))
def test_augment_bounding_boxes__kernel_size_differs__requires_pad(self):
self._test_augment_bounding_boxes__kernel_size_differs((5, 6, 3),
(2, 3, 3))
def _test_cbaoi_alignment(self, cbaoi, cbaoi_empty,
coords_expected_pooled, coords_expected_nopool,
augf_name):
def _same_coords(cbaoi1, coords):
assert len(cbaoi1.items) == len(coords)
for item, coords_i in zip(cbaoi1.items, coords):
if not np.allclose(item.coords, coords_i, atol=1e-4, rtol=0):
return False
return True
aug = self.augmenter((1, 2), keep_size=False)
image = np.zeros((40, 40, 1), dtype=np.uint8)
images_batch = [image, image, image, image]
cbaoi_batch = [cbaoi, cbaoi, cbaoi_empty, cbaoi]
nb_iterations = 10
for _ in sm.xrange(nb_iterations):
aug_det = aug.to_deterministic()
images_aug = aug_det.augment_images(images_batch)
cbaois_aug = getattr(aug_det, augf_name)(cbaoi_batch)
for index in [0, 1, 3]:
image_aug = images_aug[index]
cbaoi_aug = cbaois_aug[index]
assert image_aug.shape == cbaoi_aug.shape
if image_aug.shape == (20, 20, 1):
assert _same_coords(
cbaoi_aug,
coords_expected_pooled)
else:
assert _same_coords(
cbaoi_aug,
coords_expected_nopool)
for index in [2]:
image_aug = images_aug[index]
cbaoi_aug = cbaois_aug[index]
assert cbaoi_aug.shape == image_aug.shape
assert len(cbaoi_aug.items) == 0
def test_keypoint_alignment(self):
from imgaug.augmentables.kps import Keypoint, KeypointsOnImage
kps = [Keypoint(x=10, y=10), Keypoint(x=30, y=30)]
kpsoi = KeypointsOnImage(kps, shape=(40, 40, 1))
kpsoi_empty = KeypointsOnImage([], shape=(40, 40, 1))
self._test_cbaoi_alignment(
kpsoi, kpsoi_empty,
[[(5, 5)], [(15, 15)]],
[[(10, 10)], [(30, 30)]],
"augment_keypoints")
def test_polygon_alignment(self):
from imgaug.augmentables.polys import Polygon, PolygonsOnImage
polys = [Polygon([(10, 10), (30, 10), (30, 30)])]
psoi = PolygonsOnImage(polys, shape=(40, 40, 1))
psoi_empty = PolygonsOnImage([], shape=(40, 40, 1))
self._test_cbaoi_alignment(
psoi, psoi_empty,
[[(10/2, 10/2), (30/2, 10/2), (30/2, 30/2)]],
[[(10, 10), (30, 10), (30, 30)]],
"augment_polygons")
def test_line_strings_alignment(self):
from imgaug.augmentables.lines import LineString, LineStringsOnImage
lss = [LineString([(10, 10), (30, 10), (30, 30)])]
lsoi = LineStringsOnImage(lss, shape=(40, 40, 1))
lsoi_empty = LineStringsOnImage([], shape=(40, 40, 1))
self._test_cbaoi_alignment(
lsoi, lsoi_empty,
[[(10/2, 10/2), (30/2, 10/2), (30/2, 30/2)]],
[[(10, 10), (30, 10), (30, 30)]],
"augment_line_strings")
def test_bounding_boxes_alignment(self):
from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage
bbs = [BoundingBox(x1=10, y1=10, x2=30, y2=30)]
bbsoi = BoundingBoxesOnImage(bbs, shape=(40, 40, 1))
bbsoi_empty = BoundingBoxesOnImage([], shape=(40, 40, 1))
self._test_cbaoi_alignment(
bbsoi, bbsoi_empty,
[[(10/2, 10/2), (30/2, 30/2)]],
[[(10, 10), (30, 30)]],
"augment_bounding_boxes")
def _test_empty_cbaoi(self, cbaoi, augf_name):
aug = self.augmenter(3, keep_size=False)
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
expected = cbaoi.deepcopy()
expected.shape = (2, 2, 3)
assert_cbaois_equal(cbaoi_aug, expected)
def test_empty_keypoints(self):
from imgaug.augmentables.kps import KeypointsOnImage
cbaoi = KeypointsOnImage([], shape=(5, 6, 3))
self._test_empty_cbaoi(cbaoi, "augment_keypoints")
def test_empty_polygons(self):
from imgaug.augmentables.polys import PolygonsOnImage
cbaoi = PolygonsOnImage([], shape=(5, 6, 3))
self._test_empty_cbaoi(cbaoi, "augment_polygons")
def test_empty_line_strings(self):
from imgaug.augmentables.lines import LineStringsOnImage
cbaoi = LineStringsOnImage([], shape=(5, 6, 3))
self._test_empty_cbaoi(cbaoi, "augment_line_strings")
def test_empty_bounding_boxes(self):
from imgaug.augmentables.bbs import BoundingBoxesOnImage
cbaoi = BoundingBoxesOnImage([], shape=(5, 6, 3))
self._test_empty_cbaoi(cbaoi, "augment_bounding_boxes")
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = self.augmenter(3)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = self.augmenter(3)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
aug = self.augmenter(2)
params = aug.get_parameters()
assert len(params) == 2
assert len(params[0]) == 2
assert is_parameter_instance(params[0][0], iap.Deterministic)
assert params[0][0].value == 2
assert params[0][1] is None
def test_pickleable(self):
aug = self.augmenter((1, 7), random_state=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(25, 25, 1))
def subTest(self, *args, **kwargs):
raise NotImplementedError
# TODO add test that checks the padding behaviour
class TestAveragePooling(unittest.TestCase, _TestPoolingAugmentersBase):
@property
def augmenter(self):
return iaa.AveragePooling
def test___init___default_settings(self):
aug = iaa.AveragePooling(2)
assert len(aug.kernel_size) == 2
assert is_parameter_instance(aug.kernel_size[0], iap.Deterministic)
assert aug.kernel_size[0].value == 2
assert aug.kernel_size[1] is None
assert aug.keep_size is True
def test___init___custom_settings(self):
aug = iaa.AveragePooling(((2, 4), (5, 6)), keep_size=False)
assert len(aug.kernel_size) == 2
assert is_parameter_instance(aug.kernel_size[0], iap.DiscreteUniform)
assert is_parameter_instance(aug.kernel_size[1], iap.DiscreteUniform)
assert aug.kernel_size[0].a.value == 2
assert aug.kernel_size[0].b.value == 4
assert aug.kernel_size[1].a.value == 5
assert aug.kernel_size[1].b.value == 6
assert aug.keep_size is False
def test_augment_images__kernel_size_is_zero(self):
aug = iaa.AveragePooling(0)
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
assert np.array_equal(aug.augment_image(image), image)
def test_augment_images__kernel_size_is_one(self):
aug = iaa.AveragePooling(1)
image = np.arange(6*6*3).astype(np.uint8).reshape((6, 6, 3))
assert np.array_equal(aug.augment_image(image), image)
def test_augment_images__kernel_size_is_two__array_of_100s(self):
aug = iaa.AveragePooling(2, keep_size=False)
image = np.full((6, 6, 3), 100, dtype=np.uint8)
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - 100)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (3, 3, 3)
assert np.all(diff <= 1)
def test_augment_images__kernel_size_is_two__custom_array(self):
aug = iaa.AveragePooling(2, keep_size=False)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+1, 120-1]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
expected = np.uint8([
[50, 120]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 3))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (1, 2, 3)
assert np.all(diff <= 1)
def test_augment_images__kernel_size_is_two__view(self):
aug = iaa.AveragePooling(2, keep_size=False)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+1, 120-1],
[0, 0, 0, 0]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
image = image[:2, :, :]
assert not image.flags["OWNDATA"]
assert image.flags["C_CONTIGUOUS"]
expected = np.uint8([
[50, 120]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 3))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (1, 2, 3)
assert np.all(diff <= 1)
def test_augment_images__kernel_size_is_two__non_contiguous(self):
aug = iaa.AveragePooling(2, keep_size=False)
image = np.array([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+1, 120-1]
], dtype=np.uint8, order="F")
assert image.flags["OWNDATA"]
assert not image.flags["C_CONTIGUOUS"]
expected = np.uint8([
[50, 120]
])
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (1, 2)
assert np.all(diff <= 1)
def test_augment_images__kernel_size_is_two__four_channels(self):
aug = iaa.AveragePooling(2, keep_size=False)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+1, 120-1]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 4))
expected = np.uint8([
[50, 120]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 4))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (1, 2, 4)
assert np.all(diff <= 1)
def test_augment_images__kernel_size_differs(self):
aug = iaa.AveragePooling(
(iap.Deterministic(3), iap.Deterministic(2)),
keep_size=False)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+2, 120-1],
[50-5, 50+5, 120-2, 120+1],
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
expected = np.uint8([
[50, 120]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 3))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (1, 2, 3)
assert np.all(diff <= 1)
def test_augment_images__kernel_size_differs__requires_padding(self):
aug = iaa.AveragePooling(
(iap.Deterministic(3), iap.Deterministic(1)),
keep_size=False)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+2, 120-1]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
expected = np.uint8([
[(50-2 + 50+1 + 50-2)/3,
(50-1 + 50+2 + 50-1)/3,
(120-4 + 120+2 + 120-4)/3,
(120+4 + 120-1 + 120+4)/3]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 3))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (1, 4, 3)
assert np.all(diff <= 1)
def test_augment_images__kernel_size_is_two__keep_size(self):
aug = iaa.AveragePooling(2, keep_size=True)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+1, 120-1]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
expected = np.uint8([
[50, 50, 120, 120],
[50, 50, 120, 120]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 3))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (2, 4, 3)
assert np.all(diff <= 1)
def test_augment_images__kernel_size_is_two__single_channel(self):
aug = iaa.AveragePooling(2, keep_size=False)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+1, 120-1]
])
image = image[:, :, np.newaxis]
expected = np.uint8([
[50, 120]
])
expected = expected[:, :, np.newaxis]
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == (1, 2, 1)
assert np.all(diff <= 1)
# TODO add test that checks the padding behaviour
# We don't have many tests here, because MaxPooling and AveragePooling derive
# from the same base class, i.e. they share most of the methods, which are then
# tested via TestAveragePooling.
class TestMaxPooling(unittest.TestCase, _TestPoolingAugmentersBase):
@property
def augmenter(self):
return iaa.MaxPooling
def test_augment_images(self):
aug = iaa.MaxPooling(2, keep_size=False)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+1, 120-1]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
expected = np.uint8([
[50+2, 120+4]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 3))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.shape == (1, 2, 3)
assert np.all(diff <= 1)
def test_augment_images__different_channels(self):
aug = iaa.MaxPooling((iap.Deterministic(1), iap.Deterministic(4)),
keep_size=False)
c1 = np.arange(start=1, stop=8+1).reshape((1, 8, 1))
c2 = (100 + np.arange(start=1, stop=8+1)).reshape((1, 8, 1))
image = np.dstack([c1, c2]).astype(np.uint8)
c1_expected = np.uint8([4, 8]).reshape((1, 2, 1))
c2_expected = np.uint8([100+4, 100+8]).reshape((1, 2, 1))
image_expected = np.dstack([c1_expected, c2_expected])
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - image_expected)
assert image_aug.shape == (1, 2, 2)
assert np.all(diff <= 1)
# TODO add test that checks the padding behaviour
# We don't have many tests here, because MinPooling and AveragePooling derive
# from the same base class, i.e. they share most of the methods, which are then
# tested via TestAveragePooling.
class TestMinPooling(unittest.TestCase, _TestPoolingAugmentersBase):
@property
def augmenter(self):
return iaa.MinPooling
def test_augment_images(self):
aug = iaa.MinPooling(2, keep_size=False)
image = np.uint8([
[50-2, 50-1, 120-4, 120+4],
[50+1, 50+2, 120+1, 120-1]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
expected = np.uint8([
[50-2, 120-4]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 3))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.shape == (1, 2, 3)
assert np.all(diff <= 1)
def test_augment_images__different_channels(self):
aug = iaa.MinPooling((iap.Deterministic(1), iap.Deterministic(4)),
keep_size=False)
c1 = np.arange(start=1, stop=8+1).reshape((1, 8, 1))
c2 = (100 + np.arange(start=1, stop=8+1)).reshape((1, 8, 1))
image = np.dstack([c1, c2]).astype(np.uint8)
c1_expected = np.uint8([1, 5]).reshape((1, 2, 1))
c2_expected = np.uint8([100+1, 100+4]).reshape((1, 2, 1))
image_expected = np.dstack([c1_expected, c2_expected])
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - image_expected)
assert image_aug.shape == (1, 2, 2)
assert np.all(diff <= 1)
# TODO add test that checks the padding behaviour
# We don't have many tests here, because MedianPooling and AveragePooling
# derive from the same base class, i.e. they share most of the methods, which
# are then tested via TestAveragePooling.
class TestMedianPool(unittest.TestCase, _TestPoolingAugmentersBase):
@property
def augmenter(self):
return iaa.MedianPooling
def test_augment_images(self):
aug = iaa.MedianPooling(3, keep_size=False)
image = np.uint8([
[50-9, 50-8, 50-7, 120-5, 120-5, 120-5],
[50-5, 50+0, 50+3, 120-3, 120+0, 120+1],
[50+8, 50+9, 50+9, 120+2, 120+3, 120+4]
])
image = np.tile(image[:, :, np.newaxis], (1, 1, 3))
expected = np.uint8([
[50, 120]
])
expected = np.tile(expected[:, :, np.newaxis], (1, 1, 3))
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - expected)
assert image_aug.shape == (1, 2, 3)
assert np.all(diff <= 1)
def test_augment_images__different_channels(self):
aug = iaa.MinPooling((iap.Deterministic(1), iap.Deterministic(3)),
keep_size=False)
c1 = np.arange(start=1, stop=9+1).reshape((1, 9, 1))
c2 = (100 + np.arange(start=1, stop=9+1)).reshape((1, 9, 1))
image = np.dstack([c1, c2]).astype(np.uint8)
c1_expected = np.uint8([2, 5, 8]).reshape((1, 3, 1))
c2_expected = np.uint8([100+2, 100+5, 100+8]).reshape((1, 3, 1))
image_expected = np.dstack([c1_expected, c2_expected])
image_aug = aug.augment_image(image)
diff = np.abs(image_aug.astype(np.int32) - image_expected)
assert image_aug.shape == (1, 3, 2)
assert np.all(diff <= 1)
================================================
FILE: test/augmenters/test_segmentation.py
================================================
from __future__ import print_function, division, absolute_import
import sys
import warnings
import itertools
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug import random as iarandom
from imgaug.testutils import (
reseed,
runtest_pickleable_uint8_img,
temporary_constants,
is_parameter_instance
)
from imgaug.imgaug import _NUMBA_INSTALLED
# On systems without numba we are forced to use numpy-based segment
# replacement. We can thus only on numba systems test both.
_NP_REPLACE = [True, False] if _NUMBA_INSTALLED else [True]
def _create_replace_np_context(use_np_replace):
cnames = ["imgaug.augmenters.segmentation._NUMBA_INSTALLED"]
values = [not use_np_replace]
return temporary_constants(cnames, values)
class TestSuperpixels(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _array_equals_tolerant(cls, a, b, tolerance):
# TODO isnt this just np.allclose(a, b, rtol=0, atol=tolerance) ?!
diff = np.abs(a.astype(np.int32) - b.astype(np.int32))
return np.all(diff <= tolerance)
@property
def base_img(self):
base_img = [
[255, 255, 255, 0, 0, 0],
[255, 235, 255, 0, 20, 0],
[250, 250, 250, 5, 5, 5]
]
base_img = np.tile(
np.array(base_img, dtype=np.uint8)[..., np.newaxis],
(1, 1, 3))
return base_img
@property
def base_img_superpixels(self):
base_img_superpixels = [
[251, 251, 251, 4, 4, 4],
[251, 251, 251, 4, 4, 4],
[251, 251, 251, 4, 4, 4]
]
base_img_superpixels = np.tile(
np.array(base_img_superpixels, dtype=np.uint8)[..., np.newaxis],
(1, 1, 3))
return base_img_superpixels
@property
def base_img_superpixels_left(self):
base_img_superpixels_left = self.base_img_superpixels
base_img_superpixels_left[:, 3:, :] = self.base_img[:, 3:, :]
return base_img_superpixels_left
@property
def base_img_superpixels_right(self):
base_img_superpixels_right = self.base_img_superpixels
base_img_superpixels_right[:, :3, :] = self.base_img[:, :3, :]
return base_img_superpixels_right
def test_p_replace_0_n_segments_2(self):
for use_np_replace in _NP_REPLACE:
with self.subTest(use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
aug = iaa.Superpixels(p_replace=0, n_segments=2)
observed = aug.augment_image(self.base_img)
expected = self.base_img
assert np.allclose(observed, expected)
def test_p_replace_1_n_segments_2(self):
for use_np_replace in _NP_REPLACE:
with self.subTest(use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
aug = iaa.Superpixels(p_replace=1.0, n_segments=2)
observed = aug.augment_image(self.base_img)
expected = self.base_img_superpixels
assert self._array_equals_tolerant(observed, expected, 2)
def test_p_replace_1_n_segments_stochastic_parameter(self):
for use_np_replace in _NP_REPLACE:
with self.subTest(use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
aug = iaa.Superpixels(
p_replace=1.0, n_segments=iap.Deterministic(2)
)
observed = aug.augment_image(self.base_img)
expected = self.base_img_superpixels
assert self._array_equals_tolerant(observed, expected, 2)
def test_p_replace_stochastic_parameter_n_segments_2(self):
for use_np_replace in _NP_REPLACE:
with self.subTest(use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
aug = iaa.Superpixels(
p_replace=iap.Binomial(iap.Choice([0.0, 1.0])),
n_segments=2
)
observed = aug.augment_image(self.base_img)
assert (
np.allclose(observed, self.base_img)
or self._array_equals_tolerant(
observed, self.base_img_superpixels, 2)
)
def test_p_replace_050_n_segments_2(self):
_eq = self._array_equals_tolerant
for use_np_replace in _NP_REPLACE:
with self.subTest(use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
aug = iaa.Superpixels(p_replace=0.5, n_segments=2)
seen = {"none": False, "left": False, "right": False,
"both": False}
for _ in sm.xrange(100):
observed = aug.augment_image(self.base_img)
if _eq(observed, self.base_img, 2):
seen["none"] = True
elif _eq(observed, self.base_img_superpixels_left, 2):
seen["left"] = True
elif _eq(observed, self.base_img_superpixels_right, 2):
seen["right"] = True
elif _eq(observed, self.base_img_superpixels, 2):
seen["both"] = True
else:
raise Exception(
"Generated superpixels image does not match "
"any expected image."
)
if np.all(seen.values()):
break
assert np.all(seen.values())
def test_failure_on_invalid_datatype_for_p_replace(self):
# note that assertRaisesRegex does not exist in 2.7
got_exception = False
try:
_ = iaa.Superpixels(p_replace="test", n_segments=100)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_failure_on_invalid_datatype_for_n_segments(self):
# note that assertRaisesRegex does not exist in 2.7
got_exception = False
try:
_ = iaa.Superpixels(p_replace=1, n_segments="test")
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape, use_np_replace in itertools.product(shapes, _NP_REPLACE):
with self.subTest(shape=shape, use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.Superpixels(p_replace=1.0, n_segments=10)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape, use_np_replace in itertools.product(shapes, _NP_REPLACE):
with self.subTest(shape=shape, use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.Superpixels(p_replace=1.0, n_segments=10)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
aug = iaa.Superpixels(
p_replace=0.5, n_segments=2, max_size=100, interpolation="nearest")
params = aug.get_parameters()
assert params[0] is aug.p_replace
assert is_parameter_instance(params[0].p, iap.Deterministic)
assert params[1] is aug.n_segments
assert 0.5 - 1e-4 < params[0].p.value < 0.5 + 1e-4
assert params[1].value == 2
assert params[2] == 100
assert params[3] == "nearest"
def test_other_dtypes_bool(self):
for use_np_replace in _NP_REPLACE:
with self.subTest(use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
aug = iaa.Superpixels(p_replace=1.0, n_segments=2)
img = np.array([
[False, False, True, True],
[False, False, True, True]
], dtype=bool)
img_aug = aug.augment_image(img)
assert img_aug.dtype == img.dtype
assert np.all(img_aug == img)
aug = iaa.Superpixels(p_replace=1.0, n_segments=1)
img = np.array([
[True, True, True, True],
[False, True, True, True]
], dtype=bool)
img_aug = aug.augment_image(img)
assert img_aug.dtype == img.dtype
assert np.all(img_aug)
def test_other_dtypes_uint_int(self):
dtypes = ["uint8", "uint16", "uint32",
"int8", "int16", "int32"]
for dtype in dtypes:
for use_np_replace in _NP_REPLACE:
with self.subTest(dtype=dtype, use_np_replace=use_np_replace):
with _create_replace_np_context(use_np_replace):
dtype = np.dtype(dtype)
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [
int(center_value), int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value-100
]
values = [((-1)*value, value) for value in values]
else:
values = [(0, int(center_value)),
(10, int(0.1 * max_value)),
(10, int(0.2 * max_value)),
(10, int(0.5 * max_value)),
(0, max_value),
(int(center_value),
max_value)]
for v1, v2 in values:
aug = iaa.Superpixels(p_replace=1.0, n_segments=2)
img = np.array([
[v1, v1, v2, v2],
[v1, v1, v2, v2]
], dtype=dtype)
img_aug = aug.augment_image(img)
assert img_aug.dtype.name == dtype.name
assert np.array_equal(img_aug, img)
aug = iaa.Superpixels(p_replace=1.0, n_segments=1)
img = np.array([
[v2, v2, v2, v2],
[v1, v2, v2, v2]
], dtype=dtype)
img_aug = aug.augment_image(img)
assert img_aug.dtype.name == dtype.name
assert np.all(img_aug == int((7/8)*v2 + (1/8)*v1))
def test_other_dtypes_float(self):
# currently, no float dtype is actually accepted
for dtype in []:
def _allclose(a, b):
atol = 1e-4 if dtype == np.float16 else 1e-8
return np.allclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
for value in [0, 1.0, 10.0, 1000 ** (isize - 1)]:
v1 = (-1) * value
v2 = value
aug = iaa.Superpixels(p_replace=1.0, n_segments=2)
img = np.array([
[v1, v1, v2, v2],
[v1, v1, v2, v2]
], dtype=dtype)
img_aug = aug.augment_image(img)
assert img_aug.dtype == np.dtype(dtype)
assert _allclose(img_aug, img)
aug = iaa.Superpixels(p_replace=1.0, n_segments=1)
img = np.array([
[v2, v2, v2, v2],
[v1, v2, v2, v2]
], dtype=dtype)
img_aug = aug.augment_image(img)
assert img_aug.dtype == np.dtype(dtype)
assert _allclose(img_aug, (7/8)*v2 + (1/8)*v1)
def test_pickleable(self):
aug = iaa.Superpixels(p_replace=0.5, seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(25, 25, 1))
class Test_segment_voronoi(unittest.TestCase):
def setUp(self):
reseed()
def test_cell_coordinates_is_empty_integrationtest(self):
image = np.arange(2*2*3).astype(np.uint8).reshape((2, 2, 3))
cell_coordinates = np.zeros((0, 2), dtype=np.float32)
replace_mask = np.zeros((0,), dtype=bool)
image_seg = iaa.segment_voronoi(image, cell_coordinates, replace_mask)
assert np.array_equal(image, image_seg)
@classmethod
def _test_image_n_channels_integrationtest(cls, nb_channels):
image = np.uint8([
[0, 1, 200, 201],
[2, 3, 202, 203]
])
if nb_channels is not None:
image = np.tile(image[:, :, np.newaxis], (1, 1, nb_channels))
for c in sm.xrange(nb_channels):
image[..., c] += c
cell_coordinates = np.float32([
[1.0, 1.0],
[3.0, 1.0]
])
replace_mask = np.array([True, True], dtype=bool)
image_seg = iaa.segment_voronoi(image, cell_coordinates, replace_mask)
pixels1 = image[0:2, 0:2]
pixels2 = image[0:2, 2:4]
avg_color1 = np.average(pixels1.astype(np.float32), axis=(0, 1))
avg_color2 = np.average(pixels2.astype(np.float32), axis=(0, 1))
image_expected = np.uint8([
[avg_color1, avg_color1, avg_color2, avg_color2],
[avg_color1, avg_color1, avg_color2, avg_color2],
])
assert np.array_equal(image_seg, image_expected)
def test_image_has_no_channels_integrationtest(self):
self._test_image_n_channels_integrationtest(None)
def test_image_has_one_channel_integrationtest(self):
self._test_image_n_channels_integrationtest(1)
def test_image_has_three_channels_integrationtest(self):
self._test_image_n_channels_integrationtest(3)
def test_replace_mask_is_all_false_integrationtest(self):
image = np.uint8([
[0, 1, 200, 201],
[2, 3, 202, 203]
])
cell_coordinates = np.float32([
[1.0, 1.0],
[3.0, 1.0]
])
replace_mask = np.array([False, False], dtype=bool)
image_seg = iaa.segment_voronoi(image, cell_coordinates, replace_mask)
assert np.array_equal(image_seg, image)
def test_replace_mask_is_mixed_integrationtest(self):
image = np.uint8([
[0, 1, 200, 201],
[2, 3, 202, 203]
])
cell_coordinates = np.float32([
[1.0, 1.0],
[3.0, 1.0]
])
replace_mask = np.array([False, True], dtype=bool)
image_seg = iaa.segment_voronoi(image, cell_coordinates, replace_mask)
pixels2 = image[0:2, 2:4]
avg_color2 = np.sum(pixels2).astype(np.float32) / pixels2.size
image_expected = np.uint8([
[0, 1, avg_color2, avg_color2],
[2, 3, avg_color2, avg_color2],
])
assert np.array_equal(image_seg, image_expected)
def test_replace_mask_is_none_integrationtest(self):
image = np.uint8([
[0, 1, 200, 201],
[2, 3, 202, 203]
])
cell_coordinates = np.float32([
[1.0, 1.0],
[3.0, 1.0]
])
replace_mask = None
image_seg = iaa.segment_voronoi(image, cell_coordinates, replace_mask)
pixels1 = image[0:2, 0:2]
pixels2 = image[0:2, 2:4]
avg_color1 = np.sum(pixels1).astype(np.float32) / pixels1.size
avg_color2 = np.sum(pixels2).astype(np.float32) / pixels2.size
image_expected = np.uint8([
[avg_color1, avg_color1, avg_color2, avg_color2],
[avg_color1, avg_color1, avg_color2, avg_color2],
])
assert np.array_equal(image_seg, image_expected)
def test_no_cell_coordinates_provided_and_no_channel_integrationtest(self):
image = np.uint8([
[0, 1, 200, 201],
[2, 3, 202, 203]
])
cell_coordinates = np.zeros((0, 2), dtype=np.float32)
replace_mask = np.zeros((0,), dtype=bool)
image_seg = iaa.segment_voronoi(image, cell_coordinates, replace_mask)
assert np.array_equal(image_seg, image)
def test_no_cell_coordinates_provided_and_3_channels_integrationtest(self):
image = np.uint8([
[0, 1, 200, 201],
[2, 3, 202, 203]
])
image = np.tile(image[..., np.newaxis], (1, 1, 3))
cell_coordinates = np.zeros((0, 2), dtype=np.float32)
replace_mask = np.zeros((0,), dtype=bool)
image_seg = iaa.segment_voronoi(image, cell_coordinates, replace_mask)
assert np.array_equal(image_seg, image)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
cell_coordinates = np.float32([
[1.0, 1.0],
[3.0, 1.0]
])
replace_mask = np.array([True, True], dtype=bool)
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
image_aug = iaa.segment_voronoi(image, cell_coordinates,
replace_mask)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
cell_coordinates = np.float32([
[1.0, 1.0],
[3.0, 1.0]
])
replace_mask = np.array([True, True], dtype=bool)
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
image_aug = iaa.segment_voronoi(image, cell_coordinates,
replace_mask)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
class TestVoronoi(unittest.TestCase):
def setUp(self):
reseed()
def test___init___defaults(self):
sampler = iaa.RegularGridPointsSampler(1, 1)
aug = iaa.Voronoi(sampler)
assert aug.points_sampler is sampler
assert is_parameter_instance(aug.p_replace, iap.Deterministic)
assert aug.p_replace.value == 1
assert aug.max_size == 128
assert aug.interpolation == "linear"
def test___init___custom_arguments(self):
sampler = iaa.RegularGridPointsSampler(1, 1)
aug = iaa.Voronoi(sampler, p_replace=0.5, max_size=None,
interpolation="cubic")
assert aug.points_sampler is sampler
assert is_parameter_instance(aug.p_replace, iap.Binomial)
assert np.isclose(aug.p_replace.p.value, 0.5)
assert aug.max_size is None
assert aug.interpolation == "cubic"
def test_max_size_is_none(self):
image = np.zeros((10, 20, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, 1)
aug = iaa.Voronoi(sampler, max_size=None)
mock_imresize = mock.MagicMock()
mock_imresize.return_value = image
fname = "imgaug.imresize_single_image"
with mock.patch(fname, mock_imresize):
_image_aug = aug(image=image)
assert mock_imresize.call_count == 0
def test_max_size_is_int_image_not_too_large(self):
image = np.zeros((10, 20, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, 1)
aug = iaa.Voronoi(sampler, max_size=100)
mock_imresize = mock.MagicMock()
mock_imresize.return_value = image
fname = "imgaug.imresize_single_image"
with mock.patch(fname, mock_imresize):
_image_aug = aug(image=image)
assert mock_imresize.call_count == 0
def test_max_size_is_int_image_too_large(self):
image = np.zeros((10, 20, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, 1)
aug = iaa.Voronoi(sampler, max_size=10)
mock_imresize = mock.MagicMock()
mock_imresize.return_value = image
fname = "imgaug.imresize_single_image"
with mock.patch(fname, mock_imresize):
_image_aug = aug(image=image)
assert mock_imresize.call_count == 1
assert mock_imresize.call_args_list[0][0][1] == (5, 10)
def test_interpolation(self):
image = np.zeros((10, 20, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, 1)
aug = iaa.Voronoi(sampler, max_size=10, interpolation="cubic")
mock_imresize = mock.MagicMock()
mock_imresize.return_value = image
fname = "imgaug.imresize_single_image"
with mock.patch(fname, mock_imresize):
_image_aug = aug(image=image)
assert mock_imresize.call_count == 1
assert mock_imresize.call_args_list[0][1]["interpolation"] == "cubic"
def test_point_sampler_called(self):
class LoggedPointSampler(iaa.IPointsSampler):
def __init__(self, other):
self.other = other
self.call_count = 0
def sample_points(self, images, random_state):
self.call_count += 1
return self.other.sample_points(images, random_state)
image = np.zeros((10, 20, 3), dtype=np.uint8)
sampler = LoggedPointSampler(iaa.RegularGridPointsSampler(1, 1))
aug = iaa.Voronoi(sampler)
_image_aug = aug(image=image)
assert sampler.call_count == 1
def test_point_sampler_returns_no_points_integrationtest(self):
class NoPointsPointSampler(iaa.IPointsSampler):
def sample_points(self, images, random_state):
return [np.zeros((0, 2), dtype=np.float32)]
image = np.zeros((10, 20, 3), dtype=np.uint8)
sampler = NoPointsPointSampler()
aug = iaa.Voronoi(sampler)
image_aug = aug(image=image)
assert np.array_equal(image_aug, image)
@classmethod
def _test_image_with_n_channels(cls, nb_channels):
image = np.zeros((10, 20), dtype=np.uint8)
if nb_channels is not None:
image = image[..., np.newaxis]
image = np.tile(image, (1, 1, nb_channels))
sampler = iaa.RegularGridPointsSampler(1, 1)
aug = iaa.Voronoi(sampler)
mock_segment_voronoi = mock.MagicMock()
if nb_channels is None:
mock_segment_voronoi.return_value = image[..., np.newaxis]
else:
mock_segment_voronoi.return_value = image
fname = "imgaug.augmenters.segmentation.segment_voronoi"
with mock.patch(fname, mock_segment_voronoi):
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_image_with_no_channels(self):
self._test_image_with_n_channels(None)
def test_image_with_one_channel(self):
self._test_image_with_n_channels(1)
def test_image_with_three_channels(self):
self._test_image_with_n_channels(3)
def test_p_replace_is_zero(self):
image = np.zeros((50, 50), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(50, 50)
aug = iaa.Voronoi(sampler, p_replace=0.0)
mock_segment_voronoi = mock.MagicMock()
mock_segment_voronoi.return_value = image[..., np.newaxis]
fname = "imgaug.augmenters.segmentation.segment_voronoi"
with mock.patch(fname, mock_segment_voronoi):
_image_aug = aug(image=image)
replace_mask = mock_segment_voronoi.call_args_list[0][0][2]
assert not np.any(replace_mask)
def test_p_replace_is_one(self):
image = np.zeros((50, 50), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(50, 50)
aug = iaa.Voronoi(sampler, p_replace=1.0)
mock_segment_voronoi = mock.MagicMock()
mock_segment_voronoi.return_value = image[..., np.newaxis]
fname = "imgaug.augmenters.segmentation.segment_voronoi"
with mock.patch(fname, mock_segment_voronoi):
_image_aug = aug(image=image)
replace_mask = mock_segment_voronoi.call_args_list[0][0][2]
assert np.all(replace_mask)
def test_p_replace_is_50_percent(self):
image = np.zeros((200, 200), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(200, 200)
aug = iaa.Voronoi(sampler, p_replace=0.5)
mock_segment_voronoi = mock.MagicMock()
mock_segment_voronoi.return_value = image[..., np.newaxis]
fname = "imgaug.augmenters.segmentation.segment_voronoi"
with mock.patch(fname, mock_segment_voronoi):
_image_aug = aug(image=image)
replace_mask = mock_segment_voronoi.call_args_list[0][0][2]
replace_fraction = np.average(replace_mask.astype(np.float32))
assert 0.4 <= replace_fraction <= 0.6
def test_determinism_integrationtest(self):
image = np.arange(10*20).astype(np.uint8).reshape((10, 20, 1))
image = np.tile(image, (1, 1, 3))
image[:, :, 1] += 5
image[:, :, 2] += 10
sampler = iaa.DropoutPointsSampler(
iaa.RegularGridPointsSampler((1, 10), (1, 20)),
0.5
)
aug = iaa.Voronoi(sampler, p_replace=(0.0, 1.0))
aug_det = aug.to_deterministic()
images_aug_a1 = aug(images=[image] * 50)
images_aug_a2 = aug(images=[image] * 50)
images_aug_b1 = aug_det(images=[image] * 50)
images_aug_b2 = aug_det(images=[image] * 50)
same_within_a1 = _all_arrays_identical(images_aug_a1)
same_within_a2 = _all_arrays_identical(images_aug_a2)
same_within_b1 = _all_arrays_identical(images_aug_b1)
same_within_b2 = _all_arrays_identical(images_aug_b2)
same_between_a1_a2 = _array_lists_elementwise_identical(images_aug_a1,
images_aug_a2)
same_between_b1_b2 = _array_lists_elementwise_identical(images_aug_b1,
images_aug_b2)
assert not same_within_a1
assert not same_within_a2
assert not same_within_b1
assert not same_within_b2
assert not same_between_a1_a2
assert same_between_b1_b2
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
sampler = iaa.RegularGridPointsSampler(50, 50)
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.Voronoi(sampler, p_replace=1)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
sampler = iaa.RegularGridPointsSampler(50, 50)
for shape in shapes:
with self.subTest(shape=shape):
image = np.full(shape, 128, dtype=np.uint8)
aug = iaa.Voronoi(sampler, p_replace=1)
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_get_parameters(self):
sampler = iaa.RegularGridPointsSampler(1, 1)
aug = iaa.Voronoi(sampler, p_replace=0.5, max_size=None,
interpolation="cubic")
params = aug.get_parameters()
assert params[0] is sampler
assert is_parameter_instance(params[1], iap.Binomial)
assert np.isclose(params[1].p.value, 0.5)
assert params[2] is None
assert params[3] == "cubic"
def test_pickleable(self):
sampler = iaa.RegularGridPointsSampler(5, 5)
aug = iaa.Voronoi(sampler, p_replace=0.5, seed=1)
runtest_pickleable_uint8_img(aug, iterations=10, shape=(25, 25, 1))
def _all_arrays_identical(arrs):
if len(arrs) == 1:
return True
return np.all([np.array_equal(arrs[0], arr_other)
for arr_other in arrs[1:]])
def _array_lists_elementwise_identical(arrs1, arrs2):
return np.all([np.array_equal(arr1, arr2)
for arr1, arr2 in zip(arrs1, arrs2)])
class TestUniformVoronoi(unittest.TestCase):
def test___init___(self):
rs = iarandom.RNG(10)
mock_voronoi = mock.MagicMock()
mock_voronoi.return_value = mock_voronoi
fname = "imgaug.augmenters.segmentation.Voronoi.__init__"
with mock.patch(fname, mock_voronoi):
_ = iaa.UniformVoronoi(
100,
p_replace=0.5,
max_size=5,
interpolation="cubic",
seed=rs,
name="foo"
)
assert mock_voronoi.call_count == 1
assert isinstance(mock_voronoi.call_args_list[0][1]["points_sampler"],
iaa.UniformPointsSampler)
assert np.isclose(mock_voronoi.call_args_list[0][1]["p_replace"],
0.5)
assert mock_voronoi.call_args_list[0][1]["max_size"] == 5
assert mock_voronoi.call_args_list[0][1]["interpolation"] == "cubic"
assert mock_voronoi.call_args_list[0][1]["name"] == "foo"
assert mock_voronoi.call_args_list[0][1]["seed"] is rs
def test___init___integrationtest(self):
rs = iarandom.RNG(10)
aug = iaa.UniformVoronoi(
100,
p_replace=0.5,
max_size=5,
interpolation="cubic",
seed=rs,
name=None
)
assert aug.points_sampler.n_points.value == 100
assert np.isclose(aug.p_replace.p.value, 0.5)
assert aug.max_size == 5
assert aug.interpolation == "cubic"
assert aug.name == "UnnamedUniformVoronoi"
assert aug.random_state.equals(rs)
def test_pickleable(self):
aug = iaa.UniformVoronoi((10, 50), p_replace=0.5, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(50, 50, 1))
class TestRegularGridVoronoi(unittest.TestCase):
def test___init___(self):
rs = iarandom.RNG(10)
mock_voronoi = mock.MagicMock()
mock_voronoi.return_value = mock_voronoi
fname = "imgaug.augmenters.segmentation.Voronoi.__init__"
with mock.patch(fname, mock_voronoi):
_ = iaa.RegularGridVoronoi(
10,
20,
p_drop_points=0.6,
p_replace=0.5,
max_size=5,
interpolation="cubic",
seed=rs,
name="foo"
)
assert mock_voronoi.call_count == 1
ps = mock_voronoi.call_args_list[0][1]["points_sampler"]
assert isinstance(ps, iaa.DropoutPointsSampler)
assert isinstance(ps.other_points_sampler,
iaa.RegularGridPointsSampler)
assert np.isclose(ps.p_drop.p.value, 1-0.6)
assert ps.other_points_sampler.n_rows.value == 10
assert ps.other_points_sampler.n_cols.value == 20
assert np.isclose(mock_voronoi.call_args_list[0][1]["p_replace"],
0.5)
assert mock_voronoi.call_args_list[0][1]["max_size"] == 5
assert mock_voronoi.call_args_list[0][1]["interpolation"] == "cubic"
assert mock_voronoi.call_args_list[0][1]["name"] == "foo"
assert mock_voronoi.call_args_list[0][1]["seed"] is rs
def test___init___integrationtest(self):
rs = iarandom.RNG(10)
aug = iaa.RegularGridVoronoi(
10,
(10, 30),
p_replace=0.5,
max_size=5,
interpolation="cubic",
seed=rs,
name=None
)
assert aug.points_sampler.other_points_sampler.n_rows.value == 10
assert is_parameter_instance(
aug.points_sampler.other_points_sampler.n_cols,
iap.DiscreteUniform
)
assert aug.points_sampler.other_points_sampler.n_cols.a.value == 10
assert aug.points_sampler.other_points_sampler.n_cols.b.value == 30
assert np.isclose(aug.p_replace.p.value, 0.5)
assert aug.max_size == 5
assert aug.interpolation == "cubic"
assert aug.name == "UnnamedRegularGridVoronoi"
assert aug.random_state.equals(rs)
def test_pickleable(self):
aug = iaa.RegularGridVoronoi((5, 10), (5, 10), p_replace=0.5,
seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(50, 50, 1))
class TestRelativeRegularGridVoronoi(unittest.TestCase):
def test___init___(self):
rs = iarandom.RNG(10)
mock_voronoi = mock.MagicMock()
mock_voronoi.return_value = mock_voronoi
fname = "imgaug.augmenters.segmentation.Voronoi.__init__"
with mock.patch(fname, mock_voronoi):
_ = iaa.RelativeRegularGridVoronoi(
0.1,
0.2,
p_drop_points=0.6,
p_replace=0.5,
max_size=5,
interpolation="cubic",
seed=rs,
name="foo"
)
assert mock_voronoi.call_count == 1
ps = mock_voronoi.call_args_list[0][1]["points_sampler"]
assert isinstance(ps, iaa.DropoutPointsSampler)
assert isinstance(ps.other_points_sampler,
iaa.RelativeRegularGridPointsSampler)
assert np.isclose(ps.p_drop.p.value, 1-0.6)
assert np.isclose(ps.other_points_sampler.n_rows_frac.value, 0.1)
assert np.isclose(ps.other_points_sampler.n_cols_frac.value, 0.2)
assert np.isclose(mock_voronoi.call_args_list[0][1]["p_replace"],
0.5)
assert mock_voronoi.call_args_list[0][1]["max_size"] == 5
assert mock_voronoi.call_args_list[0][1]["interpolation"] == "cubic"
assert mock_voronoi.call_args_list[0][1]["name"] == "foo"
assert mock_voronoi.call_args_list[0][1]["seed"] is rs
def test___init___integrationtest(self):
rs = iarandom.RNG(10)
aug = iaa.RelativeRegularGridVoronoi(
0.1,
(0.1, 0.3),
p_replace=0.5,
max_size=5,
interpolation="cubic",
seed=rs,
name=None
)
ps = aug.points_sampler
assert np.isclose(ps.other_points_sampler.n_rows_frac.value, 0.1)
assert is_parameter_instance(ps.other_points_sampler.n_cols_frac,
iap.Uniform)
assert np.isclose(ps.other_points_sampler.n_cols_frac.a.value, 0.1)
assert np.isclose(ps.other_points_sampler.n_cols_frac.b.value, 0.3)
assert np.isclose(aug.p_replace.p.value, 0.5)
assert aug.max_size == 5
assert aug.interpolation == "cubic"
assert aug.name == "UnnamedRelativeRegularGridVoronoi"
assert aug.random_state.equals(rs)
def test_pickleable(self):
aug = iaa.RelativeRegularGridVoronoi((0.01, 0.2), (0.01, 0.2),
p_replace=0.5, seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(50, 50, 1))
# TODO verify behaviours when image height/width is zero
class TestRegularGridPointsSampler(unittest.TestCase):
def setUp(self):
reseed()
def test___init___(self):
sampler = iaa.RegularGridPointsSampler((1, 10), 20)
assert is_parameter_instance(sampler.n_rows, iap.DiscreteUniform)
assert sampler.n_rows.a.value == 1
assert sampler.n_rows.b.value == 10
assert sampler.n_cols.value == 20
def test_sample_single_point(self):
image = np.zeros((10, 20, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, 1)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 1
assert np.allclose(points[0], [10.0, 5.0])
def test_sample_points(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(2, 2)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 4
assert np.allclose(points, [
[2.5, 2.5],
[7.5, 2.5],
[2.5, 7.5],
[7.5, 7.5]
])
def test_sample_points_stochastic(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, iap.Choice([1, 2]))
points = sampler.sample_points([image], iarandom.RNG(1))[0]
matches_single_point = np.allclose(points, [
[5.0, 5.0]
])
matches_two_points = np.allclose(points, [
[2.5, 5.0],
[7.5, 5.0]
])
assert len(points) in [1, 2]
assert matches_single_point or matches_two_points
def test_sample_points_cols_is_zero(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(iap.Deterministic(0), 1)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
matches_single_point = np.allclose(points, [
[5.0, 5.0]
])
assert len(points) == 1
assert matches_single_point
def test_sample_points_rows_is_zero(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, iap.Deterministic(0))
points = sampler.sample_points([image], iarandom.RNG(1))[0]
matches_single_point = np.allclose(points, [
[5.0, 5.0]
])
assert len(points) == 1
assert matches_single_point
def test_sample_points_rows_is_more_than_image_height(self):
image = np.zeros((1, 1, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(2, 1)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
matches_single_point = np.allclose(points, [
[0.5, 0.5]
])
assert len(points) == 1
assert matches_single_point
def test_sample_points_cols_is_more_than_image_width(self):
image = np.zeros((1, 1, 3), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, 2)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
matches_single_point = np.allclose(points, [
[0.5, 0.5]
])
assert len(points) == 1
assert matches_single_point
def test_zero_sized_axes(self):
shapes = [
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, 1)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 1
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler(1, 1)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 1
def test_determinism(self):
image = np.zeros((500, 500, 1), dtype=np.uint8)
sampler = iaa.RegularGridPointsSampler((1, 500), (1, 500))
points_seed1_1 = sampler.sample_points([image], 1)[0]
points_seed1_2 = sampler.sample_points([image], 1)[0]
points_seed2_1 = sampler.sample_points([image], 2)[0]
assert points_seed1_1.shape == points_seed1_2.shape
assert points_seed1_1.shape != points_seed2_1.shape
def test_conversion_to_string(self):
sampler = iaa.RegularGridPointsSampler(10, (10, 30))
expected = (
"RegularGridPointsSampler("
"%s, "
"%s"
")" % (
str(sampler.n_rows),
str(sampler.n_cols)
)
)
assert sampler.__str__() == sampler.__repr__() == expected
class TestRelativeRegularGridPointsSampler(unittest.TestCase):
def setUp(self):
reseed()
def test___init___(self):
sampler = iaa.RelativeRegularGridPointsSampler((0.1, 0.2), 0.1)
assert is_parameter_instance(sampler.n_rows_frac, iap.Uniform)
assert np.isclose(sampler.n_rows_frac.a.value, 0.1)
assert np.isclose(sampler.n_rows_frac.b.value, 0.2)
assert np.isclose(sampler.n_cols_frac.value, 0.1)
def test_sample_single_point(self):
image = np.zeros((10, 20, 3), dtype=np.uint8)
sampler = iaa.RelativeRegularGridPointsSampler(0.001, 0.001)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 1
assert np.allclose(points[0], [10.0, 5.0])
def test_sample_points(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.RelativeRegularGridPointsSampler(0.2, 0.2)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 4
assert np.allclose(points, [
[2.5, 2.5],
[7.5, 2.5],
[2.5, 7.5],
[7.5, 7.5]
])
def test_sample_points_stochastic(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.RelativeRegularGridPointsSampler(0.1,
iap.Choice([0.1, 0.2]))
points = sampler.sample_points([image], iarandom.RNG(1))[0]
matches_single_point = np.allclose(points, [
[5.0, 5.0]
])
matches_two_points = np.allclose(points, [
[2.5, 5.0],
[7.5, 5.0]
])
assert len(points) in [1, 2]
assert matches_single_point or matches_two_points
def test_sample_points_cols_is_zero(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.RelativeRegularGridPointsSampler(iap.Deterministic(0.001),
0.1)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
matches_single_point = np.allclose(points, [
[5.0, 5.0]
])
assert len(points) == 1
assert matches_single_point
def test_sample_points_rows_is_zero(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.RelativeRegularGridPointsSampler(0.1,
iap.Deterministic(0.001))
points = sampler.sample_points([image], iarandom.RNG(1))[0]
matches_single_point = np.allclose(points, [
[5.0, 5.0]
])
assert len(points) == 1
assert matches_single_point
def test_determinism(self):
image = np.zeros((500, 500, 1), dtype=np.uint8)
sampler = iaa.RelativeRegularGridPointsSampler((0.01, 1.0), (0.1, 1.0))
points_seed1_1 = sampler.sample_points([image], 1)[0]
points_seed1_2 = sampler.sample_points([image], 1)[0]
points_seed2_1 = sampler.sample_points([image], 2)[0]
assert points_seed1_1.shape == points_seed1_2.shape
assert points_seed1_1.shape != points_seed2_1.shape
def test_zero_sized_axes(self):
shapes = [
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
sampler = iaa.RelativeRegularGridPointsSampler(0.01, 0.01)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 1
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
sampler = iaa.RelativeRegularGridPointsSampler(0.01, 0.01)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 1
def test_conversion_to_string(self):
sampler = iaa.RelativeRegularGridPointsSampler(0.01, (0.01, 0.05))
expected = (
"RelativeRegularGridPointsSampler("
"%s, "
"%s"
")" % (
str(sampler.n_rows_frac),
str(sampler.n_cols_frac)
)
)
assert sampler.__str__() == sampler.__repr__() == expected
class _FixedPointsSampler(iaa.IPointsSampler):
def __init__(self, points):
self.points = np.float32(np.copy(points))
self.last_random_state = None
def sample_points(self, images, random_state):
self.last_random_state = random_state
return np.tile(self.points[np.newaxis, ...], (len(images), 1))
class TestDropoutPointsSampler(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
other = iaa.RegularGridPointsSampler(1, 1)
sampler = iaa.DropoutPointsSampler(other, 0.5)
assert sampler.other_points_sampler is other
assert isinstance(sampler.p_drop, iap.Binomial)
assert np.isclose(sampler.p_drop.p.value, 0.5)
def test_p_drop_is_0_percent(self):
image = np.zeros((1, 1, 3), dtype=np.uint8)
points = np.linspace(0.0, 1000.0, num=100000)
points = np.stack([points, points], axis=-1)
other = _FixedPointsSampler(points)
sampler = iaa.DropoutPointsSampler(other, 0.0)
observed = sampler.sample_points([image], 1)[0]
assert np.allclose(observed, points)
def test_p_drop_is_100_percent(self):
image = np.zeros((1, 1, 3), dtype=np.uint8)
points = np.linspace(0.0+0.9, 1000.0-0.9, num=100000)
points = np.stack([points, points], axis=-1)
other = _FixedPointsSampler(points)
sampler = iaa.DropoutPointsSampler(other, 1.0)
observed = sampler.sample_points([image], 1)[0]
eps = 1e-4
assert len(observed) == 1
assert 0.0 + 0.9 - eps <= observed[0][0] <= 1000.0 - 0.9 + eps
assert 0.0 + 0.9 - eps <= observed[0][1] <= 1000.0 - 0.9 + eps
def test_p_drop_is_50_percent(self):
image = np.zeros((1, 1, 3), dtype=np.uint8)
points = np.linspace(0.0+0.9, 1000.0-0.9, num=100000)
points = np.stack([points, points], axis=-1)
other = _FixedPointsSampler(points)
sampler = iaa.DropoutPointsSampler(other, 0.5)
observed = sampler.sample_points([image], 1)[0]
assert 50000 - 1000 <= len(observed) <= 50000 + 1000
def test_determinism(self):
image = np.zeros((1, 1, 3), dtype=np.uint8)
points = np.linspace(0.0+0.9, 1000.0-0.9, num=100000)
points = np.stack([points, points], axis=-1)
other = _FixedPointsSampler(points)
sampler = iaa.DropoutPointsSampler(other, (0.3, 0.7))
observed_s1_1 = sampler.sample_points([image], 1)[0]
observed_s1_2 = sampler.sample_points([image], 1)[0]
observed_s2_1 = sampler.sample_points([image], 2)[0]
assert np.allclose(observed_s1_1, observed_s1_2)
assert (observed_s1_1.shape != observed_s2_1.shape
or not np.allclose(observed_s1_1, observed_s2_1))
def test_random_state_propagates(self):
image = np.zeros((1, 1, 3), dtype=np.uint8)
points = np.linspace(0.0+0.9, 1000.0-0.9, num=1)
points = np.stack([points, points], axis=-1)
other = _FixedPointsSampler(points)
sampler = iaa.DropoutPointsSampler(other, 0.5)
_ = sampler.sample_points([image], 1)[0]
rs_s1_1 = other.last_random_state
_ = sampler.sample_points([image], 1)[0]
rs_s1_2 = other.last_random_state
_ = sampler.sample_points([image], 2)[0]
rs_s2_1 = other.last_random_state
assert rs_s1_1.equals(rs_s1_2)
assert not rs_s1_1.equals(rs_s2_1)
def test_conversion_to_string(self):
sampler = iaa.DropoutPointsSampler(
iaa.RegularGridPointsSampler(10, 20),
0.2
)
expected = (
"DropoutPointsSampler("
"RegularGridPointsSampler("
"%s, "
"%s"
"), "
"%s"
")" % (
str(sampler.other_points_sampler.n_rows),
str(sampler.other_points_sampler.n_cols),
str(sampler.p_drop)
)
)
assert sampler.__str__() == sampler.__repr__() == expected
class TestUniformPointsSampler(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
sampler = iaa.UniformPointsSampler(100)
assert is_parameter_instance(sampler.n_points, iap.Deterministic)
assert sampler.n_points.value == 100
def test_sampled_points_not_identical(self):
sampler = iaa.UniformPointsSampler(3)
images = [np.zeros((1000, 1000, 3), dtype=np.uint8)]
points = sampler.sample_points(images, 1)[0]
points_tpls = [tuple(point) for point in points]
n_points = len(points)
n_points_uq = len(set(points_tpls))
assert n_points == 3
assert n_points_uq == 3
def test_sampled_points_uniformly_distributed_by_quadrants(self):
# split image into 2x2 quadrants, group all points per quadrant,
# assume that at least around N_points/(2*2) points are in each
# quadrant
sampler = iaa.UniformPointsSampler(10000)
images = [np.zeros((1000, 3000, 1), dtype=np.uint8)]
points = sampler.sample_points(images, 1)[0]
points_rel = points.astype(np.float32)
points_rel[:, 1] /= 1000
points_rel[:, 0] /= 3000
points_quadrants = np.clip(
np.floor(points_rel * 2),
0, 1
).astype(np.int32)
n_points_per_quadrant = np.zeros((2, 2), dtype=np.int32)
np.add.at(
n_points_per_quadrant,
(points_quadrants[:, 1], points_quadrants[:, 0]),
1)
assert np.all(n_points_per_quadrant > 0.8*(10000/4))
def test_sampled_points_uniformly_distributed_by_distance_from_origin(self):
# Sample N points, compute distances from origin each axis,
# split into B bins, assume that each bin contains at least around
# N/B points.
sampler = iaa.UniformPointsSampler(10000)
images = [np.zeros((1000, 3000, 1), dtype=np.uint8)]
points = sampler.sample_points(images, 1)[0]
points_rel = points.astype(np.float32)
points_rel[:, 1] /= 1000
points_rel[:, 0] /= 3000
points_bins = np.clip(
np.floor(points_rel * 10),
0, 1
).astype(np.int32)
# Don't use euclidean (2d) distance here, but instead axis-wise (1d)
# distance. The euclidean distance leads to non-uniform density of
# distances, because points on the same "circle" have the same
# distance, and there are less points close/far away from the origin
# that fall on the same circle.
points_bincounts_x = np.bincount(points_bins[:, 0])
points_bincounts_y = np.bincount(points_bins[:, 1])
assert np.all(points_bincounts_x > 0.8*(10000/10))
assert np.all(points_bincounts_y > 0.8*(10000/10))
def test_many_images(self):
sampler = iaa.UniformPointsSampler(1000)
images = [
np.zeros((100, 500, 3), dtype=np.uint8),
np.zeros((500, 100, 1), dtype=np.uint8)
]
points = sampler.sample_points(images, 1)
assert len(points) == 2
assert len(points[0]) == 1000
assert len(points[1]) == 1000
assert not np.allclose(points[0], points[1])
assert np.any(points[0][:, 1] < 20)
assert np.any(points[0][:, 1] > 0.9*100)
assert np.any(points[0][:, 0] < 20)
assert np.any(points[0][:, 0] > 0.9*500)
assert np.any(points[1][:, 1] < 20)
assert np.any(points[1][:, 1] > 0.9*500)
assert np.any(points[1][:, 0] < 20)
assert np.any(points[1][:, 0] > 0.9*100)
def test_always_at_least_one_point(self):
sampler = iaa.UniformPointsSampler(iap.Deterministic(0))
images = [np.zeros((10, 10, 1), dtype=np.uint8)]
points = sampler.sample_points(images, 1)[0]
assert len(points) == 1
def test_n_points_can_vary_between_calls(self):
sampler = iaa.UniformPointsSampler(iap.Choice([1, 10]))
images = [np.zeros((10, 10, 1), dtype=np.uint8)]
seen = {1: False, 10: False}
for i in sm.xrange(50):
points = sampler.sample_points(images, i)[0]
seen[len(points)] = True
if np.all(seen.values()):
break
assert len(list(seen.keys())) == 2
assert np.all(seen.values())
def test_n_points_can_vary_between_images(self):
sampler = iaa.UniformPointsSampler(iap.Choice([1, 10]))
images = [
np.zeros((10, 10, 1), dtype=np.uint8)
for _ in sm.xrange(50)]
points = sampler.sample_points(images, 1)
point_counts = set([len(points_i) for points_i in points])
assert len(points) == 50
assert len(list(point_counts)) == 2
assert 1 in point_counts
assert 10 in point_counts
def test_determinism(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
sampler = iaa.UniformPointsSampler(100)
observed_s1_1 = sampler.sample_points([image], 1)[0]
observed_s1_2 = sampler.sample_points([image], 1)[0]
observed_s2_1 = sampler.sample_points([image], 2)[0]
assert np.allclose(observed_s1_1, observed_s1_2)
assert not np.allclose(observed_s1_1, observed_s2_1)
def test_zero_sized_axes(self):
shapes = [
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
sampler = iaa.UniformPointsSampler(1)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
# TODO this is not the same as for
# (Relative)RegularGridPointsSampler, which returns in
# this case 0 points
assert len(points) == 1
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
sampler = iaa.UniformPointsSampler(1)
points = sampler.sample_points([image], iarandom.RNG(1))[0]
assert len(points) == 1
def test_conversion_to_string(self):
sampler = iaa.UniformPointsSampler(10)
expected = "UniformPointsSampler(%s)" % (
str(sampler.n_points)
)
assert sampler.__str__() == sampler.__repr__() == expected
class TestSubsamplingPointsSampler(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
other = iaa.RegularGridPointsSampler(1, 1)
sampler = iaa.SubsamplingPointsSampler(other, 100)
assert sampler.other_points_sampler is other
assert sampler.n_points_max == 100
def test_max_is_zero(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
other = iaa.RegularGridPointsSampler(2, 2)
with warnings.catch_warnings(record=True) as caught_warnings:
sampler = iaa.SubsamplingPointsSampler(other, 0)
observed = sampler.sample_points([image], 1)[0]
assert len(observed) == 0
assert len(caught_warnings) == 1
assert "n_points_max=0" in str(caught_warnings[-1].message)
def test_max_is_above_point_count(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
other = iaa.RegularGridPointsSampler(2, 2)
sampler = iaa.SubsamplingPointsSampler(other, 100)
observed = sampler.sample_points([image], 1)[0]
assert len(observed) == 4
assert np.allclose(observed, [
[2.5, 2.5],
[7.5, 2.5],
[2.5, 7.5],
[7.5, 7.5]
])
def test_max_is_below_point_count(self):
image = np.zeros((10, 10, 3), dtype=np.uint8)
other = iaa.RegularGridPointsSampler(5, 5)
sampler = iaa.SubsamplingPointsSampler(other, 1000)
observed = sampler.sample_points([image], 1)[0]
assert len(observed) == 5*5
def test_max_is_sometimes_below_point_count(self):
image = np.zeros((1, 10, 3), dtype=np.uint8)
other = iaa.RegularGridPointsSampler(1, (9, 11))
sampler = iaa.SubsamplingPointsSampler(other, 1000)
observed = sampler.sample_points([image] * 100, 1)
counts = [len(observed_i) for observed_i in observed]
counts_uq = set(counts)
assert 9 in counts_uq
assert 10 in counts_uq
assert 11 not in counts_uq
def test_random_state_propagates(self):
image = np.zeros((1, 1, 3), dtype=np.uint8)
points = np.linspace(0.0+0.9, 1000.0-0.9, num=1)
points = np.stack([points, points], axis=-1)
other = _FixedPointsSampler(points)
sampler = iaa.SubsamplingPointsSampler(other, 100)
_ = sampler.sample_points([image], 1)[0]
rs_s1_1 = other.last_random_state
_ = sampler.sample_points([image], 1)[0]
rs_s1_2 = other.last_random_state
_ = sampler.sample_points([image], 2)[0]
rs_s2_1 = other.last_random_state
assert rs_s1_1.equals(rs_s1_2)
assert not rs_s1_1.equals(rs_s2_1)
def test_conversion_to_string(self):
sampler = iaa.SubsamplingPointsSampler(
iaa.RegularGridPointsSampler(10, 20),
10
)
expected = (
"SubsamplingPointsSampler("
"RegularGridPointsSampler("
"%s, "
"%s"
"), "
"10"
")" % (
str(sampler.other_points_sampler.n_rows),
str(sampler.other_points_sampler.n_cols)
)
)
assert sampler.__str__() == sampler.__repr__() == expected
================================================
FILE: test/augmenters/test_size.py
================================================
from __future__ import print_function, division, absolute_import
import sys
import warnings
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug import dtypes as iadt
from imgaug import random as iarandom
import imgaug.augmenters.size as iaa_size
from imgaug.testutils import (array_equal_lists, keypoints_equal, reseed,
assert_cbaois_equal,
runtest_pickleable_uint8_img,
is_parameter_instance,
remove_prefetching)
from imgaug.augmentables.heatmaps import HeatmapsOnImage
from imgaug.augmentables.segmaps import SegmentationMapsOnImage
from imgaug.augmenters.size import _prevent_zero_sizes_after_crops_
class Test__prevent_zero_sizes_after_crops_(unittest.TestCase):
def test_single_item_arrays_without_crops(self):
# axis_sizes, crops_start, crops_end
axis_si = np.array([10], dtype=np.int32)
crops_s = np.array([0], dtype=np.int32)
crops_e = np.array([0], dtype=np.int32)
cs, ce = _prevent_zero_sizes_after_crops_(
axis_si, np.copy(crops_s), np.copy(crops_e)
)
assert np.all(cs == 0)
assert np.all(ce == 0)
def test_single_item_arrays_with_crops_in_bounds(self):
# axis_sizes, crops_start, crops_end
axis_si = np.array([10], dtype=np.int32)
crops_s = np.array([1], dtype=np.int32)
crops_e = np.array([2], dtype=np.int32)
cs, ce = _prevent_zero_sizes_after_crops_(
axis_si, np.copy(crops_s), np.copy(crops_e)
)
assert np.all(cs == 1)
assert np.all(ce == 2)
def test_single_item_arrays_with_crops_out_of_bounds(self):
# axis_sizes, crops_start, crops_end
axis_si = np.array([10], dtype=np.int32)
crops_s = np.array([5], dtype=np.int32)
crops_e = np.array([20], dtype=np.int32)
cs, ce = _prevent_zero_sizes_after_crops_(
axis_si, np.copy(crops_s), np.copy(crops_e)
)
assert np.all(cs == 0)
assert np.all(ce == 9)
def test_all_crops_zero(self):
# axis_sizes, crops_start, crops_end
axis_si = np.array([10, 11, 12, 13], dtype=np.int32)
crops_s = np.array([0, 0, 0, 0], dtype=np.int32)
crops_e = np.array([0, 0, 0, 0], dtype=np.int32)
cs, ce = _prevent_zero_sizes_after_crops_(
axis_si, np.copy(crops_s), np.copy(crops_e)
)
assert np.all(cs == 0)
assert np.all(ce == 0)
def test_all_crops_above_zero_but_none_reaches_zero_size(self):
# axis_sizes, crops_start, crops_end
axis_si = np.array([10, 11, 12, 13], dtype=np.int32)
crops_s = np.array([1, 2, 3, 4], dtype=np.int32)
crops_e = np.array([5, 6, 7, 8], dtype=np.int32)
cs, ce = _prevent_zero_sizes_after_crops_(
axis_si, np.copy(crops_s), np.copy(crops_e)
)
assert np.array_equal(cs, crops_s)
assert np.array_equal(ce, crops_e)
def test_some_axes_reach_zero_size(self):
axis_si = np.array([10, 11, 12, 13, 14], dtype=np.int32)
crops_s = np.array([1, 0, 13, 10, 7], dtype=np.int32)
crops_e = np.array([5, 12, 0, 10, 7], dtype=np.int32)
cs, ce = _prevent_zero_sizes_after_crops_(
axis_si, np.copy(crops_s), np.copy(crops_e)
)
assert np.array_equal(cs, [1, 0, 11, 6, 6])
assert np.array_equal(ce, [5, 10, 0, 6, 7])
def test_axis_sizes_of_1(self):
# axis_sizes, crops_start, crops_end
axis_si = np.array([9, 1, 1, 1, 1, 1, 1, 1], dtype=np.int32)
crops_s = np.array([1, 0, 1, 0, 1, 2, 0, 2], dtype=np.int32)
crops_e = np.array([5, 0, 0, 1, 1, 0, 2, 2], dtype=np.int32)
cs, ce = _prevent_zero_sizes_after_crops_(
axis_si, np.copy(crops_s), np.copy(crops_e)
)
assert np.array_equal(cs, [1, 0, 0, 0, 0, 0, 0, 0])
assert np.array_equal(ce, [5, 0, 0, 0, 0, 0, 0, 0])
def test_axis_sizes_of_0(self):
# axis_sizes, crops_start, crops_end
axis_si = np.array([9, 0, 0, 0, 0, 0, 0, 0], dtype=np.int32)
crops_s = np.array([1, 0, 1, 0, 1, 2, 0, 2], dtype=np.int32)
crops_e = np.array([5, 0, 0, 1, 1, 0, 2, 2], dtype=np.int32)
cs, ce = _prevent_zero_sizes_after_crops_(
axis_si, np.copy(crops_s), np.copy(crops_e)
)
assert np.array_equal(cs, [1, 0, 0, 0, 0, 0, 0, 0])
assert np.array_equal(ce, [5, 0, 0, 0, 0, 0, 0, 0])
def test_with_random_values(self):
batch_size = 256
for seed in np.arange(100):
with self.subTest(seed=seed):
rs = iarandom.RNG(seed)
axis_sizes = rs.integers(0, 100, size=(batch_size,))
crops_start = rs.integers(0, 100, size=(batch_size,))
crops_end = rs.integers(0, 100, size=(batch_size,))
cs, ce = _prevent_zero_sizes_after_crops_(
axis_sizes, np.copy(crops_start), np.copy(crops_end)
)
expected_start = np.zeros((batch_size,), dtype=np.int32)
expected_end = np.zeros((batch_size,), dtype=np.int32)
gen = enumerate(zip(axis_sizes, crops_start, crops_end))
for i, (axs, csi, cei) in gen:
if axs in [0, 1]:
csi = 0
cei = 0
else:
regain = abs(min(axs - csi - cei - 1, 0))
while regain > 0:
csi = csi - np.ceil(regain / 2)
cei = cei - np.floor(regain / 2)
if csi < 0:
cei = cei - abs(csi)
csi = 0
if cei < 0:
csi = csi - abs(cei)
cei = 0
regain = abs(min(axs - csi - cei, 0))
expected_start[i] = csi
expected_end[i] = cei
assert np.array_equal(cs, expected_start)
assert np.array_equal(ce, expected_end)
mask_zeros = (axis_sizes == 0)
if np.any(mask_zeros):
assert np.all(
axis_sizes[mask_zeros]
- cs[mask_zeros]
- ce[mask_zeros]
== 0
)
if np.any(~mask_zeros):
assert np.all(
axis_sizes[~mask_zeros]
- cs[~mask_zeros]
- ce[~mask_zeros]
>= 1
)
class Test__handle_position_parameter(unittest.TestCase):
def setUp(self):
reseed()
def test_string_uniform(self):
observed = iaa_size._handle_position_parameter("uniform")
assert isinstance(observed, tuple)
assert len(observed) == 2
for i in range(2):
param = remove_prefetching(observed[i])
assert is_parameter_instance(param, iap.Uniform)
assert is_parameter_instance(param.a, iap.Deterministic)
assert is_parameter_instance(param.b, iap.Deterministic)
assert 0.0 - 1e-4 < param.a.value < 0.0 + 1e-4
assert 1.0 - 1e-4 < param.b.value < 1.0 + 1e-4
def test_string_center(self):
observed = iaa_size._handle_position_parameter("center")
assert isinstance(observed, tuple)
assert len(observed) == 2
for i in range(2):
assert is_parameter_instance(observed[i], iap.Deterministic)
assert 0.5 - 1e-4 < observed[i].value < 0.5 + 1e-4
def test_string_normal(self):
observed = iaa_size._handle_position_parameter("normal")
assert isinstance(observed, tuple)
assert len(observed) == 2
for i in range(2):
param = remove_prefetching(observed[i])
assert is_parameter_instance(param, iap.Clip)
assert is_parameter_instance(param.other_param, iap.Normal)
assert is_parameter_instance(param.other_param.loc,
iap.Deterministic)
assert is_parameter_instance(param.other_param.scale,
iap.Deterministic)
assert 0.5 - 1e-4 < param.other_param.loc.value < 0.5 + 1e-4
assert 0.35/2 - 1e-4 < param.other_param.scale.value < 0.35/2 + 1e-4
def test_xy_axis_combined_strings(self):
pos_x = [
("left", 0.0),
("center", 0.5),
("right", 1.0)
]
pos_y = [
("top", 0.0),
("center", 0.5),
("bottom", 1.0)
]
for x_str, x_val in pos_x:
for y_str, y_val in pos_y:
position = "%s-%s" % (x_str, y_str)
with self.subTest(position=position):
observed = iaa_size._handle_position_parameter(position)
assert is_parameter_instance(observed[0], iap.Deterministic)
assert x_val - 1e-4 < observed[0].value < x_val + 1e-4
assert is_parameter_instance(observed[1], iap.Deterministic)
assert y_val - 1e-4 < observed[1].value < y_val + 1e-4
def test_stochastic_parameter(self):
observed = iaa_size._handle_position_parameter(iap.Poisson(2))
assert is_parameter_instance(observed, iap.Poisson)
def test_tuple_of_floats(self):
observed = iaa_size._handle_position_parameter((0.4, 0.6))
assert isinstance(observed, tuple)
assert len(observed) == 2
assert is_parameter_instance(observed[0], iap.Deterministic)
assert 0.4 - 1e-4 < observed[0].value < 0.4 + 1e-4
assert is_parameter_instance(observed[1], iap.Deterministic)
assert 0.6 - 1e-4 < observed[1].value < 0.6 + 1e-4
def test_tuple_of_floats_outside_value_range_leads_to_failure(self):
got_exception = False
try:
_ = iaa_size._handle_position_parameter((1.2, 0.6))
except Exception as e:
assert "must be within the value range" in str(e)
got_exception = True
assert got_exception
def test_tuple_of_stochastic_parameters(self):
observed = iaa_size._handle_position_parameter(
(iap.Poisson(2), iap.Poisson(3)))
assert is_parameter_instance(observed[0], iap.Poisson)
assert is_parameter_instance(observed[0].lam, iap.Deterministic)
assert 2 - 1e-4 < observed[0].lam.value < 2 + 1e-4
assert is_parameter_instance(observed[1], iap.Poisson)
assert is_parameter_instance(observed[1].lam, iap.Deterministic)
assert 3 - 1e-4 < observed[1].lam.value < 3 + 1e-4
def test_tuple_of_float_and_stochastic_parameter(self):
observed = iaa_size._handle_position_parameter((0.4, iap.Poisson(3)))
assert isinstance(observed, tuple)
assert len(observed) == 2
assert is_parameter_instance(observed[0], iap.Deterministic)
assert 0.4 - 1e-4 < observed[0].value < 0.4 + 1e-4
assert is_parameter_instance(observed[1], iap.Poisson)
assert is_parameter_instance(observed[1].lam, iap.Deterministic)
assert 3 - 1e-4 < observed[1].lam.value < 3 + 1e-4
def test_bad_datatype_leads_to_failure(self):
got_exception = False
try:
_ = iaa_size._handle_position_parameter(False)
except Exception as e:
assert "Expected one of the following as position parameter" in str(e)
got_exception = True
assert got_exception
def test_pad():
# -------
# uint, int
# -------
for dtype in [np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32, np.int64]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
arr = np.zeros((3, 3), dtype=dtype) + max_value
arr_pad = iaa.pad(arr)
assert arr_pad.shape == (3, 3)
# For some reason, arr_pad.dtype.type == dtype fails here for int64 but not for the other dtypes,
# even though int64 is the dtype of arr_pad. Also checked .name and .str for them -- all same value.
assert arr_pad.dtype == np.dtype(dtype)
assert np.array_equal(arr_pad, arr)
arr_pad = iaa.pad(arr, top=1)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[0, :] == 0)
arr_pad = iaa.pad(arr, right=1)
assert arr_pad.shape == (3, 4)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[:, -1] == 0)
arr_pad = iaa.pad(arr, bottom=1)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[-1, :] == 0)
arr_pad = iaa.pad(arr, left=1)
assert arr_pad.shape == (3, 4)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[:, 0] == 0)
arr_pad = iaa.pad(arr, top=1, right=2, bottom=3, left=4)
assert arr_pad.shape == (3+(1+3), 3+(2+4))
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[0, :] == 0)
assert np.all(arr_pad[:, -2:] == 0)
assert np.all(arr_pad[-3:, :] == 0)
assert np.all(arr_pad[:, :4] == 0)
arr_pad = iaa.pad(arr, top=1, cval=10)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[0, :] == 10)
arr = np.zeros((3, 3, 3), dtype=dtype) + 127
arr_pad = iaa.pad(arr, top=1)
assert arr_pad.shape == (4, 3, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[0, :, 0] == 0)
assert np.all(arr_pad[0, :, 1] == 0)
assert np.all(arr_pad[0, :, 2] == 0)
v1 = int(center_value + 0.25 * max_value)
v2 = int(center_value + 0.40 * max_value)
arr = np.zeros((3, 3), dtype=dtype) + v1
arr[1, 1] = v2
arr_pad = iaa.pad(arr, top=1, mode="maximum")
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert arr_pad[0, 0] == v1
assert arr_pad[0, 1] == v2
assert arr_pad[0, 2] == v1
v1 = int(center_value + 0.25 * max_value)
arr = np.zeros((3, 3), dtype=dtype)
arr_pad = iaa.pad(arr, top=1, mode="constant", cval=v1)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert arr_pad[0, 0] == v1
assert arr_pad[0, 1] == v1
assert arr_pad[0, 2] == v1
assert arr_pad[1, 0] == 0
for nb_channels in [1, 2, 3, 4, 5]:
v1 = int(center_value + 0.25 * max_value)
arr = np.zeros((3, 3, nb_channels), dtype=dtype)
arr_pad = iaa.pad(arr, top=1, mode="constant", cval=v1)
assert arr_pad.shape == (4, 3, nb_channels)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[0, 0, :] == v1)
assert np.all(arr_pad[0, 1, :] == v1)
assert np.all(arr_pad[0, 2, :] == v1)
assert np.all(arr_pad[1, 0, :] == 0)
# TODO reactivate this block when np 1.17 pad with mode=linear_ramp
# uint and end_value>edge_value is fixed
"""
arr = np.zeros((1, 1), dtype=dtype) + 100
arr_pad = iaa.pad(arr, top=4, mode="linear_ramp", cval=100)
assert arr_pad.shape == (5, 1)
assert arr_pad.dtype == np.dtype(dtype)
assert arr_pad[0, 0] == 100
assert arr_pad[1, 0] == 75
assert arr_pad[2, 0] == 50
assert arr_pad[3, 0] == 25
assert arr_pad[4, 0] == 0
arr = np.zeros((1, 1), dtype=dtype) + 100
arr_pad = iaa.pad(arr, top=4, mode="linear_ramp", cval=0)
assert arr_pad.shape == (5, 1)
assert arr_pad.dtype == np.dtype(dtype)
assert arr_pad[0, 0] == 0
assert arr_pad[1, 0] == 25
assert arr_pad[2, 0] == 50
assert arr_pad[3, 0] == 75
assert arr_pad[4, 0] == 100
"""
# test other channel numbers
value = int(center_value + 0.25 * max_value)
for nb_channels in [None, 1, 2, 3, 4, 5, 7, 11]:
arr = np.full((3, 3), value, dtype=dtype)
if nb_channels is not None:
arr = arr[..., np.newaxis]
arr = np.tile(arr, (1, 1, nb_channels))
for c in sm.xrange(nb_channels):
arr[..., c] += c
arr_pad = iaa.pad(arr, top=1, mode="constant", cval=0)
assert arr_pad.dtype.name == np.dtype(dtype).name
if nb_channels is None:
assert arr_pad.shape == (4, 3)
assert np.all(arr_pad[0, :] == 0)
assert np.all(arr_pad[1:, :] == arr)
else:
assert arr_pad.shape == (4, 3, nb_channels)
assert np.all(arr_pad[0, :, :] == 0)
assert np.all(arr_pad[1:, :, :] == arr)
# multi-channel cval
value = int(center_value + 0.25 * max_value)
arr = np.full((3, 3, 5), value, dtype=dtype)
arr_pad = iaa.pad(arr, top=1, mode="constant", cval=(0, 1, 2, 3, 4))
assert np.all(arr_pad[0, :, 0] == 0)
assert np.all(arr_pad[0, :, 1] == 1)
assert np.all(arr_pad[0, :, 2] == 2)
assert np.all(arr_pad[0, :, 3] == 3)
assert np.all(arr_pad[0, :, 4] == 4)
# -------
# float
# -------
dtypes = [np.float16, np.float32, np.float64]
try:
# without .type here the dtype() statements below fail
dtypes.append(np.dtype("float128").type)
except TypeError:
pass # float128 not known by user system
for dtype in dtypes:
arr = np.zeros((3, 3), dtype=dtype) + 1.0
def _allclose(a, b):
atol = 1e-3 if dtype == np.float16 else 1e-7
return np.allclose(a, b, atol=atol, rtol=0)
arr_pad = iaa.pad(arr)
assert arr_pad.shape == (3, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad, arr)
arr_pad = iaa.pad(arr, top=1)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, :], dtype([0, 0, 0]))
arr_pad = iaa.pad(arr, right=1)
assert arr_pad.shape == (3, 4)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[:, -1], dtype([0, 0, 0]))
arr_pad = iaa.pad(arr, bottom=1)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[-1, :], dtype([0, 0, 0]))
arr_pad = iaa.pad(arr, left=1)
assert arr_pad.shape == (3, 4)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[:, 0], dtype([0, 0, 0]))
arr_pad = iaa.pad(arr, top=1, right=2, bottom=3, left=4)
assert arr_pad.shape == (3+(1+3), 3+(2+4))
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(np.max(arr_pad[0, :]), 0)
assert _allclose(np.max(arr_pad[:, -2:]), 0)
assert _allclose(np.max(arr_pad[-3, :]), 0)
assert _allclose(np.max(arr_pad[:, :4]), 0)
arr_pad = iaa.pad(arr, top=1, cval=0.2)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, :], dtype([0.2, 0.2, 0.2]))
v1 = 1000 ** (np.dtype(dtype).itemsize - 1)
arr_pad = iaa.pad(arr, top=1, cval=v1)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, :], dtype([v1, v1, v1]))
v1 = (-1000) ** (np.dtype(dtype).itemsize - 1)
arr_pad = iaa.pad(arr, top=1, cval=v1)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, :], dtype([v1, v1, v1]))
arr = np.zeros((3, 3, 3), dtype=dtype) + 0.5
arr_pad = iaa.pad(arr, top=1)
assert arr_pad.shape == (4, 3, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, :, 0], dtype([0, 0, 0]))
assert _allclose(arr_pad[0, :, 1], dtype([0, 0, 0]))
assert _allclose(arr_pad[0, :, 2], dtype([0, 0, 0]))
arr = np.zeros((3, 3), dtype=dtype) + 0.5
arr[1, 1] = 0.75
arr_pad = iaa.pad(arr, top=1, mode="maximum")
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, 0], 0.5)
assert _allclose(arr_pad[0, 1], 0.75)
assert _allclose(arr_pad[0, 2], 0.50)
arr = np.zeros((3, 3), dtype=dtype)
arr_pad = iaa.pad(arr, top=1, mode="constant", cval=0.4)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, 0], 0.4)
assert _allclose(arr_pad[0, 1], 0.4)
assert _allclose(arr_pad[0, 2], 0.4)
assert _allclose(arr_pad[1, 0], 0.0)
for nb_channels in [1, 2, 3, 4, 5]:
arr = np.zeros((3, 3, nb_channels), dtype=dtype)
arr_pad = iaa.pad(arr, top=1, mode="constant", cval=0.4)
assert arr_pad.shape == (4, 3, nb_channels)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, 0, :], 0.4)
assert _allclose(arr_pad[0, 1, :], 0.4)
assert _allclose(arr_pad[0, 2, :], 0.4)
assert _allclose(arr_pad[1, 0, :], 0.0)
arr = np.zeros((1, 1), dtype=dtype) + 0.6
arr_pad = iaa.pad(arr, top=4, mode="linear_ramp", cval=1.0)
assert arr_pad.shape == (5, 1)
assert arr_pad.dtype == np.dtype(dtype)
assert _allclose(arr_pad[0, 0], 1.0)
assert _allclose(arr_pad[1, 0], 0.9)
assert _allclose(arr_pad[2, 0], 0.8)
assert _allclose(arr_pad[3, 0], 0.7)
assert _allclose(arr_pad[4, 0], 0.6)
# test other channel numbers
value = 1000 ** (np.dtype(dtype).itemsize - 1)
for nb_channels in [None, 1, 2, 3, 4, 5, 7, 11]:
arr = np.full((3, 3), value, dtype=dtype)
if nb_channels is not None:
arr = arr[..., np.newaxis]
arr = np.tile(arr, (1, 1, nb_channels))
for c in sm.xrange(nb_channels):
arr[..., c] += c
arr_pad = iaa.pad(arr, top=1, mode="constant", cval=0)
assert arr_pad.dtype.name == np.dtype(dtype).name
if nb_channels is None:
assert arr_pad.shape == (4, 3)
assert _allclose(arr_pad[0, :], 0)
assert _allclose(arr_pad[1:, :], arr)
else:
assert arr_pad.shape == (4, 3, nb_channels)
assert _allclose(arr_pad[0, :, :], 0)
assert _allclose(arr_pad[1:, :, :], arr)
# multi-channel cval
value = int(center_value + 0.25 * max_value)
arr = np.full((3, 3, 5), value, dtype=dtype)
arr_pad = iaa.pad(arr, top=1, mode="constant", cval=(0, 1, 2, 3, 4))
assert _allclose(arr_pad[0, :, 0], 0)
assert _allclose(arr_pad[0, :, 1], 1)
assert _allclose(arr_pad[0, :, 2], 2)
assert _allclose(arr_pad[0, :, 3], 3)
assert _allclose(arr_pad[0, :, 4], 4)
# -------
# bool
# -------
dtype = bool
arr = np.zeros((3, 3), dtype=dtype)
arr_pad = iaa.pad(arr)
assert arr_pad.shape == (3, 3)
# For some reason, arr_pad.dtype.type == dtype fails here for int64 but not for the other dtypes,
# even though int64 is the dtype of arr_pad. Also checked .name and .str for them -- all same value.
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad == arr)
arr_pad = iaa.pad(arr, top=1)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[0, :] == 0)
arr_pad = iaa.pad(arr, top=1, cval=True)
assert arr_pad.shape == (4, 3)
assert arr_pad.dtype == np.dtype(dtype)
assert np.all(arr_pad[0, :] == 1)
def test_compute_paddings_for_aspect_ratio():
arr = np.zeros((4, 4), dtype=np.uint8)
top, right, bottom, left = \
iaa.compute_paddings_to_reach_aspect_ratio(arr, 1.0)
assert top == 0
assert right == 0
assert bottom == 0
assert left == 0
arr = np.zeros((1, 4), dtype=np.uint8)
top, right, bottom, left = \
iaa.compute_paddings_to_reach_aspect_ratio(arr, 1.0)
assert top == 1
assert right == 0
assert bottom == 2
assert left == 0
arr = np.zeros((4, 1), dtype=np.uint8)
top, right, bottom, left = \
iaa.compute_paddings_to_reach_aspect_ratio(arr, 1.0)
assert top == 0
assert right == 2
assert bottom == 0
assert left == 1
arr = np.zeros((2, 4), dtype=np.uint8)
top, right, bottom, left = \
iaa.compute_paddings_to_reach_aspect_ratio(arr, 1.0)
assert top == 1
assert right == 0
assert bottom == 1
assert left == 0
arr = np.zeros((4, 2), dtype=np.uint8)
top, right, bottom, left = \
iaa.compute_paddings_to_reach_aspect_ratio(arr, 1.0)
assert top == 0
assert right == 1
assert bottom == 0
assert left == 1
arr = np.zeros((4, 4), dtype=np.uint8)
top, right, bottom, left = \
iaa.compute_paddings_to_reach_aspect_ratio(arr, 0.5)
assert top == 2
assert right == 0
assert bottom == 2
assert left == 0
arr = np.zeros((4, 4), dtype=np.uint8)
top, right, bottom, left = \
iaa.compute_paddings_to_reach_aspect_ratio(arr, 2.0)
assert top == 0
assert right == 2
assert bottom == 0
assert left == 2
def test_pad_to_aspect_ratio():
for dtype in [np.uint8, np.int32, np.float32]:
dtype = np.dtype(dtype)
# aspect_ratio = 1.0
arr = np.zeros((4, 4), dtype=dtype)
arr_pad = iaa.pad_to_aspect_ratio(arr, 1.0)
assert arr_pad.dtype.name == dtype.name
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 4
arr = np.zeros((1, 4), dtype=dtype)
arr_pad = iaa.pad_to_aspect_ratio(arr, 1.0)
assert arr_pad.dtype.name == dtype.name
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 4
arr = np.zeros((4, 1), dtype=dtype)
arr_pad = iaa.pad_to_aspect_ratio(arr, 1.0)
assert arr_pad.dtype.name == dtype.name
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 4
arr = np.zeros((2, 4), dtype=dtype)
arr_pad = iaa.pad_to_aspect_ratio(arr, 1.0)
assert arr_pad.dtype.name == dtype.name
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 4
arr = np.zeros((4, 2), dtype=dtype)
arr_pad = iaa.pad_to_aspect_ratio(arr, 1.0)
assert arr_pad.dtype.name == dtype.name
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 4
# aspect_ratio != 1.0
arr = np.zeros((4, 4), dtype=dtype)
arr_pad = iaa.pad_to_aspect_ratio(arr, 2.0)
assert arr_pad.dtype.name == dtype.name
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 8
arr = np.zeros((4, 4), dtype=dtype)
arr_pad = iaa.pad_to_aspect_ratio(arr, 0.5)
assert arr_pad.dtype.name == dtype.name
assert arr_pad.shape[0] == 8
assert arr_pad.shape[1] == 4
# 3d arr
arr = np.zeros((4, 2, 3), dtype=dtype)
arr_pad = iaa.pad_to_aspect_ratio(arr, 1.0)
assert arr_pad.dtype.name == dtype.name
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 4
assert arr_pad.shape[2] == 3
# cval
arr = np.zeros((4, 4), dtype=np.uint8) + 128
arr_pad = iaa.pad_to_aspect_ratio(arr, 2.0)
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 8
assert np.max(arr_pad[:, 0:2]) == 0
assert np.max(arr_pad[:, -2:]) == 0
assert np.max(arr_pad[:, 2:-2]) == 128
arr = np.zeros((4, 4), dtype=np.uint8) + 128
arr_pad = iaa.pad_to_aspect_ratio(arr, 2.0, cval=10)
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 8
assert np.max(arr_pad[:, 0:2]) == 10
assert np.max(arr_pad[:, -2:]) == 10
assert np.max(arr_pad[:, 2:-2]) == 128
arr = np.zeros((4, 4), dtype=np.float32) + 0.5
arr_pad = iaa.pad_to_aspect_ratio(arr, 2.0, cval=0.0)
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 8
assert 0 - 1e-6 <= np.max(arr_pad[:, 0:2]) <= 0 + 1e-6
assert 0 - 1e-6 <= np.max(arr_pad[:, -2:]) <= 0 + 1e-6
assert 0.5 - 1e-6 <= np.max(arr_pad[:, 2:-2]) <= 0.5 + 1e-6
arr = np.zeros((4, 4), dtype=np.float32) + 0.5
arr_pad = iaa.pad_to_aspect_ratio(arr, 2.0, cval=0.1)
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 8
assert 0.1 - 1e-6 <= np.max(arr_pad[:, 0:2]) <= 0.1 + 1e-6
assert 0.1 - 1e-6 <= np.max(arr_pad[:, -2:]) <= 0.1 + 1e-6
assert 0.5 - 1e-6 <= np.max(arr_pad[:, 2:-2]) <= 0.5 + 1e-6
# mode
arr = np.zeros((4, 4), dtype=np.uint8) + 128
arr[1:3, 1:3] = 200
arr_pad = iaa.pad_to_aspect_ratio(arr, 2.0, mode="maximum")
assert arr_pad.shape[0] == 4
assert arr_pad.shape[1] == 8
assert np.max(arr_pad[0:1, 0:2]) == 128
assert np.max(arr_pad[1:3, 0:2]) == 200
assert np.max(arr_pad[3:, 0:2]) == 128
assert np.max(arr_pad[0:1, -2:]) == 128
assert np.max(arr_pad[1:3, -2:]) == 200
assert np.max(arr_pad[3:, -2:]) == 128
# TODO add tests for return_pad_values=True
class Test_compute_paddings_to_reach_multiples_of(unittest.TestCase):
def test_zero_height_array(self):
arr = np.zeros((0, 2, 3), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(arr, 2, 2)
assert paddings == (1, 0, 1, 0)
def test_zero_width_array(self):
arr = np.zeros((2, 0, 3), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(arr, 2, 2)
assert paddings == (0, 1, 0, 1)
def test_both_none(self):
arr = np.zeros((1, 1, 3), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(arr, None, None)
assert paddings == (0, 0, 0, 0)
def test_height_is_none(self):
arr = np.zeros((1, 1, 3), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(arr, None, 2)
assert paddings == (0, 1, 0, 0)
def test_width_is_none(self):
arr = np.zeros((1, 1, 3), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(arr, 2, None)
assert paddings == (0, 0, 1, 0)
def test_height_is_one(self):
arr = np.zeros((1, 1, 3), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(arr, 1, 2)
assert paddings == (0, 1, 0, 0)
def test_width_is_one(self):
arr = np.zeros((1, 1, 3), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(arr, 2, 1)
assert paddings == (0, 0, 1, 0)
def test_various_widths(self):
nb_channels_lst = [None, 1, 3, 4]
amounts = [2, 3, 4, 5, 6, 7, 8, 9]
expecteds = [
(0, 1, 0, 0),
(0, 1, 0, 0),
(0, 2, 0, 1),
(0, 0, 0, 0),
(0, 1, 0, 0),
(0, 1, 0, 1),
(0, 2, 0, 1),
(0, 2, 0, 2)
]
for amount, expected in zip(amounts, expecteds):
for nb_channels in nb_channels_lst:
with self.subTest(width_multiple=amount,
nb_channels=nb_channels):
if nb_channels is None:
arr = np.zeros((3, 5), dtype=np.uint8)
else:
arr = np.zeros((3, 5, nb_channels), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(
arr, None, amount)
assert paddings == expected
def test_various_heights(self):
nb_channels_lst = [None, 1, 3, 4]
amounts = [2, 3, 4, 5, 6, 7, 8, 9]
expecteds = [
(0, 0, 1, 0),
(0, 0, 1, 0),
(1, 0, 2, 0),
(0, 0, 0, 0),
(0, 0, 1, 0),
(1, 0, 1, 0),
(1, 0, 2, 0),
(2, 0, 2, 0)
]
for amount, expected in zip(amounts, expecteds):
for nb_channels in nb_channels_lst:
with self.subTest(height_multiple=amount,
nb_channels=nb_channels):
if nb_channels is None:
arr = np.zeros((5, 3), dtype=np.uint8)
else:
arr = np.zeros((5, 3, nb_channels), dtype=np.uint8)
paddings = iaa.compute_paddings_to_reach_multiples_of(
arr, amount, None)
assert paddings == expected
class Test_pad_to_multiples_of(unittest.TestCase):
@mock.patch("imgaug.augmenters.size.compute_paddings_to_reach_multiples_of")
@mock.patch("imgaug.augmenters.size.pad")
def test_mocked(self, mock_pad, mock_compute_pads):
mock_compute_pads.return_value = (1, 2, 3, 4)
mock_pad.return_value = "padded_array"
arr = np.ones((3, 5, 1), dtype=np.uint8)
arr_padded = iaa.pad_to_multiples_of(
arr, 10, 20, mode="foo", cval=100)
mock_compute_pads.assert_called_once_with(arr, 10, 20)
mock_pad.assert_called_once_with(arr, top=1, right=2, bottom=3,
left=4, mode="foo", cval=100)
assert arr_padded == "padded_array"
@mock.patch("imgaug.augmenters.size.compute_paddings_to_reach_multiples_of")
@mock.patch("imgaug.augmenters.size.pad")
def test_mocked_return_pad_amounts(self, mock_pad, mock_compute_pads):
mock_compute_pads.return_value = (1, 2, 3, 4)
mock_pad.return_value = "padded_array"
arr = np.ones((3, 5, 1), dtype=np.uint8)
arr_padded, paddings = iaa.pad_to_multiples_of(
arr, 10, 20, mode="foo", cval=100, return_pad_amounts=True)
mock_compute_pads.assert_called_once_with(arr, 10, 20)
mock_pad.assert_called_once_with(arr, top=1, right=2, bottom=3,
left=4, mode="foo", cval=100)
assert arr_padded == "padded_array"
assert paddings == (1, 2, 3, 4)
def test_integrationtest(self):
dtypes = [np.uint8, np.int32, np.float32]
nb_channels_lst = [None, 1, 3, 4]
for dtype in dtypes:
dtype = np.dtype(dtype)
for nb_channels in nb_channels_lst:
with self.subTest(dtype=dtype.name, nb_channels=nb_channels):
if nb_channels is None:
arr = np.ones((3, 5), dtype=dtype)
else:
arr = np.ones((3, 5, nb_channels), dtype=dtype)
arr_padded = iaa.pad_to_multiples_of(arr, 7, 11, cval=2)
if nb_channels is None:
base_area = 3*5
new_area = 7*11 - base_area
assert arr_padded.shape == (7, 11)
assert np.sum(arr_padded) == 1*base_area + 2*new_area
else:
base_area = 3*5*nb_channels
new_area = 7*11*nb_channels - base_area
assert arr_padded.shape == (7, 11, nb_channels)
assert np.sum(arr_padded) == 1*base_area + 2*new_area
class TestResize(unittest.TestCase):
def setUp(self):
reseed()
@property
def image2d(self):
# 4x8
base_img2d = [
[0, 0, 0, 0, 0, 0, 0, 0],
[0, 255, 255, 255, 255, 255, 255, 0],
[0, 255, 255, 255, 255, 255, 255, 0],
[0, 0, 0, 0, 0, 0, 0, 0]
]
base_img2d = np.array(base_img2d, dtype=np.uint8)
return base_img2d
@property
def image3d(self):
base_img3d = np.tile(self.image2d[..., np.newaxis], (1, 1, 3))
return base_img3d
@property
def kpsoi2d(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=4, y=1)]
return ia.KeypointsOnImage(kps, shape=self.image2d.shape)
@property
def kpsoi3d(self):
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=4, y=1)]
return ia.KeypointsOnImage(kps, shape=self.image3d.shape)
@property
def psoi2d(self):
polygons = [
ia.Polygon([(0, 0), (8, 0), (8, 4)]),
ia.Polygon([(1, 1), (7, 1), (7, 3), (1, 3)]),
]
return ia.PolygonsOnImage(polygons, shape=self.image2d.shape)
@property
def psoi3d(self):
polygons = [
ia.Polygon([(0, 0), (8, 0), (8, 4)]),
ia.Polygon([(1, 1), (7, 1), (7, 3), (1, 3)]),
]
return ia.PolygonsOnImage(polygons, shape=self.image3d.shape)
@property
def lsoi2d(self):
lss = [
ia.LineString([(0, 0), (8, 0), (8, 4)]),
ia.LineString([(1, 1), (7, 1), (7, 3), (1, 3)]),
]
return ia.LineStringsOnImage(lss, shape=self.image2d.shape)
@property
def lsoi3d(self):
lss = [
ia.LineString([(0, 0), (8, 0), (8, 4)]),
ia.LineString([(1, 1), (7, 1), (7, 3), (1, 3)]),
]
return ia.LineStringsOnImage(lss, shape=self.image3d.shape)
@property
def bbsoi2d(self):
bbs = [
ia.BoundingBox(x1=0, y1=0, x2=8, y2=4),
ia.BoundingBox(x1=1, y1=2, x2=6, y2=3),
]
return ia.BoundingBoxesOnImage(bbs, shape=self.image2d.shape)
@property
def bbsoi3d(self):
bbs = [
ia.BoundingBox(x1=0, y1=0, x2=8, y2=4),
ia.BoundingBox(x1=1, y1=2, x2=6, y2=3),
]
return ia.BoundingBoxesOnImage(bbs, shape=self.image3d.shape)
@classmethod
def _aspect_ratio(cls, image):
return image.shape[1] / image.shape[0]
def test_resize_to_fixed_int(self):
aug = iaa.Resize(12)
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == (12, 12)
assert observed3d.shape == (12, 12, 3)
assert 50 < np.average(observed2d) < 205
assert 50 < np.average(observed3d) < 205
def test_resize_to_fixed_float(self):
aug = iaa.Resize(0.5)
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == (2, 4)
assert observed3d.shape == (2, 4, 3)
assert 50 < np.average(observed2d) < 205
assert 50 < np.average(observed3d) < 205
def test_heatmaps_with_width_int_and_height_int(self):
aug = iaa.Resize({"height": 8, "width": 12})
heatmaps_arr = (self.image2d / 255.0).astype(np.float32)
heatmaps_aug = aug.augment_heatmaps([
HeatmapsOnImage(heatmaps_arr, shape=self.image3d.shape)])[0]
assert heatmaps_aug.shape == (8, 12, 3)
assert 0 - 1e-6 < heatmaps_aug.min_value < 0 + 1e-6
assert 1 - 1e-6 < heatmaps_aug.max_value < 1 + 1e-6
assert np.average(heatmaps_aug.get_arr()[0, :]) < 0.05
assert np.average(heatmaps_aug.get_arr()[-1, :]) < 0.05
assert np.average(heatmaps_aug.get_arr()[:, 0]) < 0.05
assert 0.8 < np.average(heatmaps_aug.get_arr()[2:6, 2:10]) < 1 + 1e-6
def test_segmaps_with_width_int_and_height_int(self):
for nb_channels in [None, 1, 10]:
aug = iaa.Resize({"height": 8, "width": 12})
segmaps_arr = (self.image2d > 0).astype(np.int32)
if nb_channels is not None:
segmaps_arr = np.tile(
segmaps_arr[..., np.newaxis], (1, 1, nb_channels))
segmaps_aug = aug.augment_segmentation_maps([
SegmentationMapsOnImage(segmaps_arr, shape=self.image3d.shape)])[0]
assert segmaps_aug.shape == (8, 12, 3)
assert segmaps_aug.arr.shape == (8, 12, nb_channels if nb_channels is not None else 1)
assert np.all(segmaps_aug.arr[0, 1:-1, :] == 0)
assert np.all(segmaps_aug.arr[-1, 1:-1, :] == 0)
assert np.all(segmaps_aug.arr[1:-1, 0, :] == 0)
assert np.all(segmaps_aug.arr[1:-1, -1, :] == 0)
assert np.all(segmaps_aug.arr[2:-2, 2:-2, :] == 1)
def test_heatmaps_with_diff_size_than_img_and_width_float_height_int(self):
aug = iaa.Resize({"width": 2.0, "height": 16})
heatmaps_arr = (self.image2d / 255.0).astype(np.float32)
heatmaps = HeatmapsOnImage(
heatmaps_arr,
shape=(2*self.image3d.shape[0], 2*self.image3d.shape[1], 3))
heatmaps_aug = aug.augment_heatmaps([heatmaps])[0]
assert heatmaps_aug.shape == (16, int(self.image3d.shape[1]*2*2), 3)
assert heatmaps_aug.arr_0to1.shape == (8, 16, 1)
assert 0 - 1e-6 < heatmaps_aug.min_value < 0 + 1e-6
assert 1 - 1e-6 < heatmaps_aug.max_value < 1 + 1e-6
assert np.average(heatmaps_aug.get_arr()[0, :]) < 0.05
assert np.average(heatmaps_aug.get_arr()[-1:, :]) < 0.05
assert np.average(heatmaps_aug.get_arr()[:, 0]) < 0.05
assert 0.8 < np.average(heatmaps_aug.get_arr()[2:6, 2:10]) < 1 + 1e-6
def test_segmaps_with_diff_size_than_img_and_width_float_height_int(self):
aug = iaa.Resize({"width": 2.0, "height": 16})
segmaps_arr = (self.image2d > 0).astype(np.int32)
segmaps = SegmentationMapsOnImage(
segmaps_arr,
shape=(2*self.image3d.shape[0], 2*self.image3d.shape[1], 3))
segmaps_aug = aug.augment_segmentation_maps([segmaps])[0]
assert segmaps_aug.shape == (16, int(self.image3d.shape[1]*2*2), 3)
assert segmaps_aug.arr.shape == (8, 16, 1)
assert np.all(segmaps_aug.arr[0, 1:-1, :] == 0)
assert np.all(segmaps_aug.arr[-1, 1:-1, :] == 0)
assert np.all(segmaps_aug.arr[1:-1, 0, :] == 0)
assert np.all(segmaps_aug.arr[1:-1, -1, :] == 0)
assert np.all(segmaps_aug.arr[2:-2, 2:-2, :] == 1)
def test_keypoints_on_3d_img_and_with_width_int_and_height_int(self):
aug = iaa.Resize({"height": 8, "width": 12})
kpsoi_aug = aug.augment_keypoints([self.kpsoi3d])[0]
assert len(kpsoi_aug.keypoints) == 2
assert kpsoi_aug.shape == (8, 12, 3)
assert np.allclose(kpsoi_aug.keypoints[0].x, 1.5)
assert np.allclose(kpsoi_aug.keypoints[0].y, 4)
assert np.allclose(kpsoi_aug.keypoints[1].x, 6)
assert np.allclose(kpsoi_aug.keypoints[1].y, 2)
def test_polygons_on_3d_img_and_with_width_int_and_height_int(self):
aug = iaa.Resize({"width": 12, "height": 8})
cbaoi_aug = aug.augment_polygons(self.psoi3d)
assert len(cbaoi_aug.items) == 2
assert cbaoi_aug.shape == (8, 12, 3)
assert cbaoi_aug.items[0].coords_almost_equals(
[(0, 0), (12, 0), (12, 8)]
)
assert cbaoi_aug.items[1].coords_almost_equals(
[(1.5, 2), (10.5, 2), (10.5, 6), (1.5, 6)]
)
def test_line_strings_on_3d_img_and_with_width_int_and_height_int(self):
aug = iaa.Resize({"width": 12, "height": 8})
cbaoi_aug = aug.augment_line_strings(self.lsoi3d)
assert len(cbaoi_aug.items) == 2
assert cbaoi_aug.shape == (8, 12, 3)
assert cbaoi_aug.items[0].coords_almost_equals(
[(0, 0), (12, 0), (12, 8)]
)
assert cbaoi_aug.items[1].coords_almost_equals(
[(1.5, 2), (10.5, 2), (10.5, 6), (1.5, 6)]
)
def test_bounding_boxes_on_3d_img_and_with_width_int_and_height_int(self):
aug = iaa.Resize({"width": 12, "height": 8})
bbsoi_aug = aug.augment_bounding_boxes(self.bbsoi3d)
assert len(bbsoi_aug.bounding_boxes) == 2
assert bbsoi_aug.shape == (8, 12, 3)
assert bbsoi_aug.bounding_boxes[0].coords_almost_equals(
[(0, 0), (12, 8)]
)
assert bbsoi_aug.bounding_boxes[1].coords_almost_equals(
[((1/8)*12, (2/4)*8), ((6/8)*12, (3/4)*8)]
)
def test_keypoints_on_2d_img_and_with_width_float_and_height_int(self):
aug = iaa.Resize({"width": 3.0, "height": 8})
kpsoi_aug = aug.augment_keypoints([self.kpsoi2d])[0]
assert len(kpsoi_aug.keypoints) == 2
assert kpsoi_aug.shape == (8, 24)
assert np.allclose(kpsoi_aug.keypoints[0].x, 3)
assert np.allclose(kpsoi_aug.keypoints[0].y, 4)
assert np.allclose(kpsoi_aug.keypoints[1].x, 12)
assert np.allclose(kpsoi_aug.keypoints[1].y, 2)
def test_polygons_on_2d_img_and_with_width_float_and_height_int(self):
aug = iaa.Resize({"width": 3.0, "height": 8})
cbaoi_aug = aug.augment_polygons(self.psoi2d)
assert len(cbaoi_aug.items) == 2
assert cbaoi_aug.shape == (8, 24)
assert cbaoi_aug.items[0].coords_almost_equals(
[(3*0, 0), (3*8, 0), (3*8, 8)]
)
assert cbaoi_aug.items[1].coords_almost_equals(
[(3*1, 2), (3*7, 2), (3*7, 6), (3*1, 6)]
)
def test_line_strings_on_2d_img_and_with_width_float_and_height_int(self):
aug = iaa.Resize({"width": 3.0, "height": 8})
cbaoi_aug = aug.augment_line_strings(self.lsoi2d)
assert len(cbaoi_aug.items) == 2
assert cbaoi_aug.shape == (8, 24)
assert cbaoi_aug.items[0].coords_almost_equals(
[(3*0, 0), (3*8, 0), (3*8, 8)]
)
assert cbaoi_aug.items[1].coords_almost_equals(
[(3*1, 2), (3*7, 2), (3*7, 6), (3*1, 6)]
)
def test_bounding_boxes_on_2d_img_and_with_width_float_and_height_int(self):
aug = iaa.Resize({"width": 3.0, "height": 8})
bbsoi_aug = aug.augment_bounding_boxes(self.bbsoi2d)
assert len(bbsoi_aug.bounding_boxes) == 2
assert bbsoi_aug.shape == (8, 24)
assert bbsoi_aug.bounding_boxes[0].coords_almost_equals(
[(3*0, 0), (3*8, 8)]
)
assert bbsoi_aug.bounding_boxes[1].coords_almost_equals(
[(3*1, (2/4)*8), (3*6, (3/4)*8)]
)
def test_empty_keypoints(self):
aug = iaa.Resize({"height": 8, "width": 12})
kpsoi = ia.KeypointsOnImage([], shape=(4, 8, 3))
kpsoi_aug = aug.augment_keypoints(kpsoi)
assert len(kpsoi_aug.keypoints) == 0
assert kpsoi_aug.shape == (8, 12, 3)
def test_empty_polygons(self):
aug = iaa.Resize({"height": 8, "width": 12})
psoi = ia.PolygonsOnImage([], shape=(4, 8, 3))
psoi_aug = aug.augment_polygons(psoi)
assert len(psoi_aug.polygons) == 0
assert psoi_aug.shape == (8, 12, 3)
def test_empty_line_strings(self):
aug = iaa.Resize({"height": 8, "width": 12})
lsoi = ia.LineStringsOnImage([], shape=(4, 8, 3))
lsoi_aug = aug.augment_line_strings(lsoi)
assert len(lsoi_aug.items) == 0
assert lsoi_aug.shape == (8, 12, 3)
def test_empty_bounding_boxes(self):
aug = iaa.Resize({"height": 8, "width": 12})
bbsoi = ia.BoundingBoxesOnImage([], shape=(4, 8, 3))
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
assert len(bbsoi_aug.bounding_boxes) == 0
assert bbsoi_aug.shape == (8, 12, 3)
def test_size_is_list_of_ints(self):
aug = iaa.Resize([12, 14])
seen2d = [False, False]
seen3d = [False, False]
for _ in sm.xrange(100):
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape in [(12, 12), (14, 14)]
assert observed3d.shape in [(12, 12, 3), (14, 14, 3)]
if observed2d.shape == (12, 12):
seen2d[0] = True
else:
seen2d[1] = True
if observed3d.shape == (12, 12, 3):
seen3d[0] = True
else:
seen3d[1] = True
if all(seen2d) and all(seen3d):
break
assert np.all(seen2d)
assert np.all(seen3d)
def test_size_is_tuple_of_ints(self):
aug = iaa.Resize((12, 14))
seen2d = [False, False, False]
seen3d = [False, False, False]
for _ in sm.xrange(100):
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape in [(12, 12), (13, 13), (14, 14)]
assert observed3d.shape in [(12, 12, 3), (13, 13, 3), (14, 14, 3)]
if observed2d.shape == (12, 12):
seen2d[0] = True
elif observed2d.shape == (13, 13):
seen2d[1] = True
else:
seen2d[2] = True
if observed3d.shape == (12, 12, 3):
seen3d[0] = True
elif observed3d.shape == (13, 13, 3):
seen3d[1] = True
else:
seen3d[2] = True
if all(seen2d) and all(seen3d):
break
assert np.all(seen2d)
assert np.all(seen3d)
def test_size_is_string_keep(self):
aug = iaa.Resize("keep")
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == self.image2d.shape
assert observed3d.shape == self.image3d.shape
# TODO shouldn't this be more an error?
def test_size_is_empty_list(self):
aug = iaa.Resize([])
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == self.image2d.shape
assert observed3d.shape == self.image3d.shape
def test_size_is_empty_dict(self):
aug = iaa.Resize({})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == self.image2d.shape
assert observed3d.shape == self.image3d.shape
def test_change_height_to_fixed_int(self):
aug = iaa.Resize({"height": 11})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == (11, self.image2d.shape[1])
assert observed3d.shape == (11, self.image3d.shape[1], 3)
def test_change_width_to_fixed_int(self):
aug = iaa.Resize({"width": 13})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == (self.image2d.shape[0], 13)
assert observed3d.shape == (self.image3d.shape[0], 13, 3)
def test_change_height_and_width_to_fixed_ints(self):
aug = iaa.Resize({"height": 12, "width": 13})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == (12, 13)
assert observed3d.shape == (12, 13, 3)
def test_change_height_to_fixed_int_but_dont_change_width(self):
aug = iaa.Resize({"height": 12, "width": "keep"})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == (12, self.image2d.shape[1])
assert observed3d.shape == (12, self.image3d.shape[1], 3)
def test_dont_change_height_but_width_to_fixed_int(self):
aug = iaa.Resize({"height": "keep", "width": 12})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape == (self.image2d.shape[0], 12)
assert observed3d.shape == (self.image3d.shape[0], 12, 3)
def test_change_height_to_fixed_int_width_keeps_aspect_ratio(self):
aug = iaa.Resize({"height": 12, "width": "keep-aspect-ratio"})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
aspect_ratio2d = self._aspect_ratio(self.image2d)
aspect_ratio3d = self._aspect_ratio(self.image3d)
assert observed2d.shape == (12, int(12 * aspect_ratio2d))
assert observed3d.shape == (12, int(12 * aspect_ratio3d), 3)
def test_height_keeps_aspect_ratio_width_changed_to_fixed_int(self):
aug = iaa.Resize({"height": "keep-aspect-ratio", "width": 12})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
aspect_ratio2d = self._aspect_ratio(self.image2d)
aspect_ratio3d = self._aspect_ratio(self.image3d)
assert observed2d.shape == (int(12 * (1/aspect_ratio2d)), 12)
assert observed3d.shape == (int(12 * (1/aspect_ratio3d)), 12, 3)
# TODO add test for shorter side being tuple, list, stochastic parameter
def test_change_shorter_side_by_fixed_int_longer_keeps_aspect_ratio(self):
aug = iaa.Resize({"shorter-side": 6,
"longer-side": "keep-aspect-ratio"})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
aspect_ratio2d = self._aspect_ratio(self.image2d)
aspect_ratio3d = self._aspect_ratio(self.image3d)
assert observed2d.shape == (6, int(6 * aspect_ratio2d))
assert observed3d.shape == (6, int(6 * aspect_ratio3d), 3)
# TODO add test for longer side being tuple, list, stochastic parameter
def test_change_longer_side_by_fixed_int_shorter_keeps_aspect_ratio(self):
aug = iaa.Resize({"longer-side": 6,
"shorter-side": "keep-aspect-ratio"})
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
aspect_ratio2d = self._aspect_ratio(self.image2d)
aspect_ratio3d = self._aspect_ratio(self.image3d)
assert observed2d.shape == (int(6 * (1/aspect_ratio2d)), 6)
assert observed3d.shape == (int(6 * (1/aspect_ratio3d)), 6, 3)
def test_change_height_by_list_of_ints_width_by_fixed_int(self):
aug = iaa.Resize({"height": [12, 14], "width": 12})
seen2d = [False, False]
seen3d = [False, False]
for _ in sm.xrange(100):
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape in [(12, 12), (14, 12)]
assert observed3d.shape in [(12, 12, 3), (14, 12, 3)]
if observed2d.shape == (12, 12):
seen2d[0] = True
else:
seen2d[1] = True
if observed3d.shape == (12, 12, 3):
seen3d[0] = True
else:
seen3d[1] = True
if np.all(seen2d) and np.all(seen3d):
break
assert np.all(seen2d)
assert np.all(seen3d)
def test_change_height_by_fixed_int_width_by_list_of_ints(self):
aug = iaa.Resize({"height": 12, "width": [12, 14]})
seen2d = [False, False]
seen3d = [False, False]
for _ in sm.xrange(100):
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape in [(12, 12), (12, 14)]
assert observed3d.shape in [(12, 12, 3), (12, 14, 3)]
if observed2d.shape == (12, 12):
seen2d[0] = True
else:
seen2d[1] = True
if observed3d.shape == (12, 12, 3):
seen3d[0] = True
else:
seen3d[1] = True
if np.all(seen2d) and np.all(seen3d):
break
assert np.all(seen2d)
assert np.all(seen3d)
def test_change_height_by_fixed_int_width_by_stochastic_parameter(self):
aug = iaa.Resize({"height": 12, "width": iap.Choice([12, 14])})
seen2d = [False, False]
seen3d = [False, False]
for _ in sm.xrange(100):
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape in [(12, 12), (12, 14)]
assert observed3d.shape in [(12, 12, 3), (12, 14, 3)]
if observed2d.shape == (12, 12):
seen2d[0] = True
else:
seen2d[1] = True
if observed3d.shape == (12, 12, 3):
seen3d[0] = True
else:
seen3d[1] = True
if np.all(seen2d) and np.all(seen3d):
break
assert np.all(seen2d)
assert np.all(seen3d)
def test_change_height_by_tuple_of_ints_width_by_fixed_int(self):
aug = iaa.Resize({"height": (12, 14), "width": 12})
seen2d = [False, False, False]
seen3d = [False, False, False]
for _ in sm.xrange(100):
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape in [(12, 12), (13, 12), (14, 12)]
assert observed3d.shape in [(12, 12, 3), (13, 12, 3), (14, 12, 3)]
if observed2d.shape == (12, 12):
seen2d[0] = True
elif observed2d.shape == (13, 12):
seen2d[1] = True
else:
seen2d[2] = True
if observed3d.shape == (12, 12, 3):
seen3d[0] = True
elif observed3d.shape == (13, 12, 3):
seen3d[1] = True
else:
seen3d[2] = True
if np.all(seen2d) and np.all(seen3d):
break
assert np.all(seen2d)
assert np.all(seen3d)
def test_size_is_float(self):
aug = iaa.Resize(2.0)
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
intensity_avg = np.average(self.image2d)
intensity_low = intensity_avg - 0.2 * np.abs(intensity_avg - 128)
intensity_high = intensity_avg + 0.2 * np.abs(intensity_avg - 128)
assert observed2d.shape == (self.image2d.shape[0]*2,
self.image2d.shape[1]*2)
assert observed3d.shape == (self.image3d.shape[0]*2,
self.image3d.shape[1]*2,
3)
assert intensity_low < np.average(observed2d) < intensity_high
assert intensity_low < np.average(observed3d) < intensity_high
def test_size_is_list(self):
aug = iaa.Resize([2.0, 4.0])
seen2d = [False, False]
seen3d = [False, False]
expected_shapes_2d = [
(self.image2d.shape[0]*2, self.image2d.shape[1]*2),
(self.image2d.shape[0]*4, self.image2d.shape[1]*4)]
expected_shapes_3d = [
(self.image3d.shape[0]*2, self.image3d.shape[1]*2, 3),
(self.image3d.shape[0]*4, self.image3d.shape[1]*4, 3)]
for _ in sm.xrange(100):
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape in expected_shapes_2d
assert observed3d.shape in expected_shapes_3d
if observed2d.shape == expected_shapes_2d[0]:
seen2d[0] = True
else:
seen2d[1] = True
if observed3d.shape == expected_shapes_3d[0]:
seen3d[0] = True
else:
seen3d[1] = True
if np.all(seen2d) and np.all(seen3d):
break
assert np.all(seen2d)
assert np.all(seen3d)
def test_size_is_stochastic_parameter(self):
aug = iaa.Resize(iap.Choice([2.0, 4.0]))
seen2d = [False, False]
seen3d = [False, False]
expected_shapes_2d = [
(self.image2d.shape[0]*2, self.image2d.shape[1]*2),
(self.image2d.shape[0]*4, self.image2d.shape[1]*4)]
expected_shapes_3d = [
(self.image3d.shape[0]*2, self.image3d.shape[1]*2, 3),
(self.image3d.shape[0]*4, self.image3d.shape[1]*4, 3)]
for _ in sm.xrange(100):
observed2d = aug.augment_image(self.image2d)
observed3d = aug.augment_image(self.image3d)
assert observed2d.shape in expected_shapes_2d
assert observed3d.shape in expected_shapes_3d
if observed2d.shape == expected_shapes_2d[0]:
seen2d[0] = True
else:
seen2d[1] = True
if observed3d.shape == expected_shapes_3d[0]:
seen3d[0] = True
else:
seen3d[1] = True
if all(seen2d) and all(seen3d):
break
assert np.all(seen2d)
assert np.all(seen3d)
def test_decrease_size_by_tuple_of_floats__one_for_both_sides(self):
image2d = self.image2d[0:4, 0:4]
image3d = self.image3d[0:4, 0:4, :]
aug = iaa.Resize((0.76, 1.0))
not_seen2d = set()
not_seen3d = set()
for size in sm.xrange(3, 4+1):
not_seen2d.add((size, size))
for size in sm.xrange(3, 4+1):
not_seen3d.add((size, size, 3))
possible2d = set(list(not_seen2d))
possible3d = set(list(not_seen3d))
for _ in sm.xrange(100):
observed2d = aug.augment_image(image2d)
observed3d = aug.augment_image(image3d)
assert observed2d.shape in possible2d
assert observed3d.shape in possible3d
if observed2d.shape in not_seen2d:
not_seen2d.remove(observed2d.shape)
if observed3d.shape in not_seen3d:
not_seen3d.remove(observed3d.shape)
if not not_seen2d and not not_seen3d:
break
assert not not_seen2d
assert not not_seen3d
def test_decrease_size_by_tuples_of_floats__one_per_side(self):
image2d = self.image2d[0:4, 0:4]
image3d = self.image3d[0:4, 0:4, :]
aug = iaa.Resize({"height": (0.76, 1.0), "width": (0.76, 1.0)})
not_seen2d = set()
not_seen3d = set()
for hsize in sm.xrange(3, 4+1):
for wsize in sm.xrange(3, 4+1):
not_seen2d.add((hsize, wsize))
for hsize in sm.xrange(3, 4+1):
for wsize in sm.xrange(3, 4+1):
not_seen3d.add((hsize, wsize, 3))
possible2d = set(list(not_seen2d))
possible3d = set(list(not_seen3d))
for _ in sm.xrange(100):
observed2d = aug.augment_image(image2d)
observed3d = aug.augment_image(image3d)
assert observed2d.shape in possible2d
assert observed3d.shape in possible3d
if observed2d.shape in not_seen2d:
not_seen2d.remove(observed2d.shape)
if observed3d.shape in not_seen3d:
not_seen3d.remove(observed3d.shape)
if not not_seen2d and not not_seen3d:
break
assert not not_seen2d
assert not not_seen3d
def test_bad_datatype_for_size_leads_to_failure(self):
got_exception = False
try:
aug = iaa.Resize("foo")
_ = aug.augment_image(self.image2d)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_get_parameters(self):
aug = iaa.Resize(size=1, interpolation="nearest")
params = aug.get_parameters()
assert is_parameter_instance(params[0], iap.Deterministic)
assert is_parameter_instance(params[1], iap.Deterministic)
assert params[0].value == 1
assert params[1].value == "nearest"
def test_dtypes_roughly(self):
# most of the dtype testing is done for imresize_many_images()
# so we focus here on a rough test that merely checks if the dtype
# does not change
# these dtypes should be kept in sync with imresize_many_images()
dtypes = [
"uint8",
"uint16",
"int8",
"int16",
"float16",
"float32",
"float64",
"bool"
]
for dt in dtypes:
for ip in ["nearest", "cubic"]:
aug = iaa.Resize({"height": 10, "width": 20}, interpolation=ip)
for is_list in [False, True]:
with self.subTest(dtype=dt, interpolation=ip,
is_list=is_list):
image = np.full((9, 19, 3), 1, dtype=dt)
images = [image, image]
if not is_list:
images = np.array(images, dtype=dt)
images_aug = aug(images=images)
if is_list:
assert isinstance(images_aug, list)
else:
assert ia.is_np_array(images_aug)
assert len(images_aug) == 2
for image_aug in images_aug:
assert image_aug.dtype.name == dt
assert image_aug.shape == (10, 20, 3)
assert np.all(image_aug >= 1 - 1e-4)
def test_pickleable(self):
aug = iaa.Resize({"height": (10, 30), "width": (10, 30)},
interpolation=["nearest", "linear"],
seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(50, 50, 1))
class TestPad(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
base_img = np.array([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]], dtype=np.uint8)
return base_img[:, :, np.newaxis]
@property
def images(self):
return np.array([self.image])
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
return ia.KeypointsOnImage(kps, shape=self.image.shape)
@property
def psoi(self):
polys = [ia.Polygon([(1, 1), (2, 1), (2, 2)])]
return ia.PolygonsOnImage(polys, shape=self.image.shape)
@property
def lsoi(self):
ls = [ia.LineString([(1, 1), (2, 1), (2, 2)])]
return ia.LineStringsOnImage(ls, shape=self.image.shape)
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)]
return ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)
@property
def heatmap(self):
heatmaps_arr = np.float32([[0, 0, 0],
[0, 1.0, 0],
[0, 0, 0]])
return ia.HeatmapsOnImage(heatmaps_arr, shape=self.image.shape)
@property
def segmap(self):
segmaps_arr = np.int32([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]])
return ia.SegmentationMapsOnImage(segmaps_arr, shape=self.image.shape)
def test___init___pad_mode_is_all(self):
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode=ia.ALL,
pad_cval=0,
keep_size=False)
expected = ["constant", "edge", "linear_ramp", "maximum", "mean",
"median", "minimum", "reflect", "symmetric", "wrap"]
assert is_parameter_instance(aug.pad_mode, iap.Choice)
assert len(aug.pad_mode.a) == len(expected)
assert np.all([v in aug.pad_mode.a for v in expected])
def test___init___pad_mode_is_list(self):
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode=["constant", "maximum"],
pad_cval=0,
keep_size=False)
expected = ["constant", "maximum"]
assert is_parameter_instance(aug.pad_mode, iap.Choice)
assert len(aug.pad_mode.a) == len(expected)
assert np.all([v in aug.pad_mode.a for v in expected])
def test___init___pad_cval_is_list(self):
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="constant",
pad_cval=[50, 100],
keep_size=False)
expected = [50, 100]
assert is_parameter_instance(aug.pad_cval, iap.Choice)
assert len(aug.pad_cval.a) == len(expected)
assert np.all([v in aug.pad_cval.a for v in expected])
def test_pad_images_by_1px_each_side_on_its_own(self):
# test pad by 1 pixel on each side
pads = [
(1, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 1, 0),
(0, 0, 0, 1),
]
for pad in pads:
with self.subTest(px=pad):
aug = iaa.Pad(px=pad, keep_size=False)
top, right, bottom, left = pad
base_img_padded = np.pad(
self.image,
((top, bottom), (left, right), (0, 0)),
mode="constant",
constant_values=0)
observed = aug.augment_images(self.images)
assert np.array_equal(observed, np.array([base_img_padded]))
observed = aug.augment_images([self.image])
assert array_equal_lists(observed, [base_img_padded])
def _test_pad_cbaoi_by_1px_each_side_on_its_own(self, cbaoi, augf_name):
pads = [
(1, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 1, 0),
(0, 0, 0, 1),
]
for pad in pads:
with self.subTest(px=pad):
aug = iaa.Pad(px=pad, keep_size=False)
top, right, bottom, left = pad
image_padded_shape = list(self.image.shape)
image_padded_shape[0] += top + bottom
image_padded_shape[1] += left + right
observed = getattr(aug, augf_name)(cbaoi)
expected = cbaoi.shift(x=left, y=top)
expected.shape = tuple(image_padded_shape)
assert_cbaois_equal(observed, expected)
def test_pad_keypoints_by_1px_each_side_on_its_own(self):
self._test_pad_cbaoi_by_1px_each_side_on_its_own(
self.kpsoi, "augment_keypoints")
def test_pad_polygons_by_1px_each_side_on_its_own(self):
self._test_pad_cbaoi_by_1px_each_side_on_its_own(
self.psoi, "augment_polygons")
def test_pad_line_strings_by_1px_each_side_on_its_own(self):
self._test_pad_cbaoi_by_1px_each_side_on_its_own(
self.lsoi, "augment_line_strings")
def test_pad_bounding_boxes_by_1px_each_side_on_its_own(self):
self._test_pad_cbaoi_by_1px_each_side_on_its_own(
self.bbsoi, "augment_bounding_boxes")
def test_pad_heatmaps_by_1px_each_side_on_its_own(self):
pads = [
(1, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 1, 0),
(0, 0, 0, 1),
]
for pad in pads:
with self.subTest(px=pad):
aug = iaa.Pad(px=pad, keep_size=False)
top, right, bottom, left = pad
heatmaps_arr = self.heatmap.get_arr()
heatmaps_arr_padded = np.pad(
heatmaps_arr,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
heatmaps = [ia.HeatmapsOnImage(
heatmaps_arr, shape=self.image.shape)]
image_padded_shape = list(self.image.shape)
image_padded_shape[0] += top + bottom
image_padded_shape[1] += left + right
observed = aug.augment_heatmaps(heatmaps)[0]
assert observed.shape == tuple(image_padded_shape)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.array_equal(observed.get_arr(), heatmaps_arr_padded)
def test_pad_segmaps_by_1px_each_side_on_its_own(self):
pads = [
(1, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 1, 0),
(0, 0, 0, 1),
]
for pad in pads:
with self.subTest(px=pad):
aug = iaa.Pad(px=pad, keep_size=False)
top, right, bottom, left = pad
segmaps_arr = self.segmap.get_arr()
segmaps_arr_padded = np.pad(
segmaps_arr,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
segmaps = [SegmentationMapsOnImage(
segmaps_arr, shape=self.image.shape)]
image_padded_shape = list(self.image.shape)
image_padded_shape[0] += top + bottom
image_padded_shape[1] += left + right
observed = aug.augment_segmentation_maps(segmaps)[0]
assert observed.shape == tuple(image_padded_shape)
assert np.array_equal(observed.get_arr(), segmaps_arr_padded)
# TODO split up, add similar tests for polygons/LS/BBs
def test_pad_each_side_on_its_own_by_tuple_of_ints(self):
def _to_range_tuple(val):
return val if isinstance(val, tuple) else (val, val)
pads = [
((0, 2), 0, 0, 0),
(0, (0, 2), 0, 0),
(0, 0, (0, 2), 0),
(0, 0, 0, (0, 2)),
]
for pad in pads:
with self.subTest(px=pad):
aug = iaa.Pad(px=pad, keep_size=False)
aug_det = aug.to_deterministic()
top, right, bottom, left = pad
images_padded = []
keypoints_padded = []
top_range = _to_range_tuple(top)
right_range = _to_range_tuple(right)
bottom_range = _to_range_tuple(bottom)
left_range = _to_range_tuple(left)
top_values = sm.xrange(top_range[0], top_range[1]+1)
right_values = sm.xrange(right_range[0], right_range[1]+1)
bottom_values = sm.xrange(bottom_range[0], bottom_range[1]+1)
left_values = sm.xrange(left_range[0], left_range[1]+1)
for top_val in top_values:
for right_val in right_values:
for bottom_val in bottom_values:
for left_val in left_values:
images_padded.append(
np.pad(
self.image,
((top_val, bottom_val),
(left_val, right_val),
(0, 0)),
mode="constant",
constant_values=0
)
)
keypoints_padded.append(
self.kpsoi.shift(x=left_val, y=top_val))
movements = []
movements_det = []
for i in sm.xrange(100):
observed = aug.augment_images(self.images)
matches = [
(1 if np.array_equal(observed,
np.array([base_img_padded]))
else 0)
for base_img_padded
in images_padded
]
movements.append(np.argmax(np.array(matches)))
assert any([val == 1 for val in matches])
observed = aug_det.augment_images(self.images)
matches = [
(1 if np.array_equal(observed,
np.array([base_img_padded]))
else 0)
for base_img_padded
in images_padded
]
movements_det.append(np.argmax(np.array(matches)))
assert any([val == 1 for val in matches])
observed = aug.augment_images([self.image])
assert any([
array_equal_lists(observed, [base_img_padded])
for base_img_padded
in images_padded])
observed = aug.augment_keypoints(self.kpsoi)
assert any([
keypoints_equal(observed, kp)
for kp
in keypoints_padded])
assert len(set(movements)) == 3
assert len(set(movements_det)) == 1
# TODO split up, add similar tests for polygons/LS/BBs
def test_pad_each_side_on_its_own_by_list_of_ints(self):
# test pad by list of exact pixel values
pads = [
([0, 2], 0, 0, 0),
(0, [0, 2], 0, 0),
(0, 0, [0, 2], 0),
(0, 0, 0, [0, 2]),
]
for pad in pads:
top, right, bottom, left = pad
aug = iaa.Pad(px=pad, keep_size=False)
aug_det = aug.to_deterministic()
images_padded = []
keypoints_padded = []
top_range = top if isinstance(top, list) else [top]
right_range = right if isinstance(right, list) else [right]
bottom_range = bottom if isinstance(bottom, list) else [bottom]
left_range = left if isinstance(left, list) else [left]
for top_val in top_range:
for right_val in right_range:
for bottom_val in bottom_range:
for left_val in left_range:
images_padded.append(
np.pad(
self.image,
((top_val, bottom_val),
(left_val, right_val),
(0, 0)),
mode="constant",
constant_values=0
)
)
keypoints_padded.append(
self.kpsoi.shift(x=left_val, y=top_val))
movements = []
movements_det = []
for i in sm.xrange(100):
observed = aug.augment_images(self.images)
matches = [
(1 if np.array_equal(observed,
np.array([base_img_padded]))
else 0)
for base_img_padded
in images_padded]
movements.append(np.argmax(np.array(matches)))
assert any([val == 1 for val in matches])
observed = aug_det.augment_images(self.images)
matches = [
(1 if np.array_equal(observed,
np.array([base_img_padded]))
else 0)
for base_img_padded
in images_padded]
movements_det.append(np.argmax(np.array(matches)))
assert any([val == 1 for val in matches])
observed = aug.augment_images([self.image])
assert any([
array_equal_lists(observed, [base_img_padded])
for base_img_padded
in images_padded])
observed = aug.augment_keypoints(self.kpsoi)
assert any([
keypoints_equal(observed, kp)
for kp
in keypoints_padded])
assert len(set(movements)) == 2
assert len(set(movements_det)) == 1
def test_pad_heatmaps_smaller_than_img_by_tuple_of_ints_without_ks(self):
# pad smaller heatmaps
# heatmap is (6, 4), image is (6, 16)
# image is padded by (2, 4, 2, 4)
# expected image size: (10, 24)
# expected heatmap size: (10, 6)
aug = iaa.Pad(px=(2, 4, 2, 4), keep_size=False)
heatmaps_arr_small = np.float32([
[0, 0, 0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 0, 0, 0]
])
top, bottom, left, right = 2, 2, 1, 1
heatmaps_arr_small_padded = np.pad(
heatmaps_arr_small,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
heatmaps = [ia.HeatmapsOnImage(heatmaps_arr_small, shape=(6, 16))]
observed = aug.augment_heatmaps(heatmaps)[0]
assert observed.shape == (10, 24)
assert observed.arr_0to1.shape == (10, 6, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.arr_0to1[..., 0], heatmaps_arr_small_padded)
def test_pad_segmaps_smaller_than_img_by_tuple_of_ints_without_ks(self):
# pad smaller segmaps
# same sizes and paddings as above
aug = iaa.Pad(px=(2, 4, 2, 4), keep_size=False)
segmaps_arr_small = np.int32([
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]
])
segmaps = [SegmentationMapsOnImage(segmaps_arr_small, shape=(6, 16))]
top, bottom, left, right = 2, 2, 1, 1
segmaps_arr_small_padded = np.pad(
segmaps_arr_small,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
observed = aug.augment_segmentation_maps(segmaps)[0]
assert observed.shape == (10, 24)
assert observed.arr.shape == (10, 6, 1)
assert np.array_equal(observed.arr[..., 0], segmaps_arr_small_padded)
def test_pad_heatmaps_smaller_than_img_by_tuple_of_ints_with_ks(self):
# pad smaller heatmaps, with keep_size=True
# heatmap is (6, 4), image is (6, 16)
# image is padded by (2, 4, 2, 4)
# expected image size: (10, 24) -> (6, 16) after resize
# expected heatmap size: (10, 6) -> (6, 4) after resize
aug = iaa.Pad(px=(2, 4, 2, 4), keep_size=True)
heatmaps_arr_small = np.float32([
[0, 0, 0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 0, 0, 0]
])
top, bottom, left, right = 2, 2, 1, 1
heatmaps_arr_small_padded = np.pad(
heatmaps_arr_small,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
heatmaps = [ia.HeatmapsOnImage(heatmaps_arr_small, shape=(6, 16))]
observed = aug.augment_heatmaps(heatmaps)[0]
assert observed.shape == (6, 16)
assert observed.arr_0to1.shape == (6, 4, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(
observed.arr_0to1[..., 0],
np.clip(
ia.imresize_single_image(
heatmaps_arr_small_padded,
(6, 4),
interpolation="cubic"),
0, 1.0
)
)
def test_pad_segmaps_smaller_than_img_by_tuple_of_ints_with_keep_size(self):
# pad smaller segmaps, with keep_size=True
# same sizes and paddings as above
aug = iaa.Pad(px=(2, 4, 2, 4), keep_size=True)
segmaps_arr_small = np.int32([
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]
])
top, bottom, left, right = 2, 2, 1, 1
segmaps_arr_small_padded = np.pad(
segmaps_arr_small,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
segmaps = [SegmentationMapsOnImage(segmaps_arr_small, shape=(6, 16))]
observed = aug.augment_segmentation_maps(segmaps)[0]
assert observed.shape == (6, 16)
assert observed.arr.shape == (6, 4, 1)
assert np.array_equal(
observed.arr[..., 0],
ia.imresize_single_image(
segmaps_arr_small_padded,
(6, 4),
interpolation="nearest"
),
)
def test_pad_keypoints_by_tuple_of_fixed_ints_without_keep_size(self):
aug = iaa.Pad((2, 0, 4, 4), keep_size=False)
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=0)]
kpsoi = ia.KeypointsOnImage(kps, shape=(4, 4, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (10, 8, 3)
assert len(kpsoi_aug.keypoints) == 2
assert np.allclose(kpsoi_aug.keypoints[0].x, 4+1)
assert np.allclose(kpsoi_aug.keypoints[0].y, 2+2)
assert np.allclose(kpsoi_aug.keypoints[1].x, 4+3)
assert np.allclose(kpsoi_aug.keypoints[1].y, 2+0)
def test_pad_keypoints_by_tuple_of_fixed_ints_with_keep_size(self):
aug = iaa.Pad((2, 0, 4, 4), keep_size=True)
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=0)]
kpsoi = ia.KeypointsOnImage(kps, shape=(4, 4, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (4, 4, 3)
assert len(kpsoi_aug.keypoints) == 2
assert np.allclose(kpsoi_aug.keypoints[0].x, ((4+1)/8)*4)
assert np.allclose(kpsoi_aug.keypoints[0].y, ((2+2)/10)*4)
assert np.allclose(kpsoi_aug.keypoints[1].x, ((4+3)/8)*4)
assert np.allclose(kpsoi_aug.keypoints[1].y, ((2+0)/10)*4)
def test_pad_polygons_by_tuple_of_fixed_ints_without_keep_size(self):
aug = iaa.Pad((2, 0, 4, 4), keep_size=False)
polygons = [ia.Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)])]
psoi = ia.PolygonsOnImage(polygons, shape=(4, 4, 3))
psoi_aug = aug.augment_polygons([psoi, psoi])
assert len(psoi_aug) == 2
for psoi_aug_i in psoi_aug:
assert psoi_aug_i.shape == (10, 8, 3)
assert len(psoi_aug_i.items) == 2
assert psoi_aug_i.items[0].coords_almost_equals(
[(4, 2), (8, 2), (8, 6), (4, 6)])
assert psoi_aug_i.items[1].coords_almost_equals(
[(5, 3), (9, 3), (9, 7), (5, 7)])
def test_pad_polygons_by_tuple_of_fixed_ints_with_keep_size(self):
aug = iaa.Pad((2, 0, 4, 4), keep_size=True)
polygons = [ia.Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)])]
psoi = ia.PolygonsOnImage(polygons, shape=(4, 4, 3))
psoi_aug = aug.augment_polygons([psoi, psoi])
assert len(psoi_aug) == 2
for psoi_aug_i in psoi_aug:
assert psoi_aug_i.shape == (4, 4, 3)
assert len(psoi_aug_i.items) == 2
assert psoi_aug_i.items[0].coords_almost_equals(
[(4*(4/8), 4*(2/10)),
(4*(8/8), 4*(2/10)),
(4*(8/8), 4*(6/10)),
(4*(4/8), 4*(6/10))]
)
assert psoi_aug_i.items[1].coords_almost_equals(
[(4*(5/8), 4*(3/10)),
(4*(9/8), 4*(3/10)),
(4*(9/8), 4*(7/10)),
(4*(5/8), 4*(7/10))]
)
def test_pad_line_strings_by_tuple_of_fixed_ints_without_keep_size(self):
aug = iaa.Pad((2, 0, 4, 4), keep_size=False)
lss = [ia.LineString([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.LineString([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.LineStringsOnImage(lss, shape=(4, 4, 3))
cbaoi_aug = aug.augment_line_strings([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (10, 8, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(4, 2), (8, 2), (8, 6), (4, 6)])
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(5, 3), (9, 3), (9, 7), (5, 7)])
def test_pad_line_strings_by_tuple_of_fixed_ints_with_keep_size(self):
aug = iaa.Pad((2, 0, 4, 4), keep_size=True)
lss = [ia.LineString([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.LineString([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.LineStringsOnImage(lss, shape=(4, 4, 3))
cbaoi_aug = aug.augment_line_strings([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (4, 4, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(4*(4/8), 4*(2/10)),
(4*(8/8), 4*(2/10)),
(4*(8/8), 4*(6/10)),
(4*(4/8), 4*(6/10))]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(4*(5/8), 4*(3/10)),
(4*(9/8), 4*(3/10)),
(4*(9/8), 4*(7/10)),
(4*(5/8), 4*(7/10))]
)
def test_pad_bounding_boxes_by_tuple_of_fixed_ints_without_keep_size(self):
aug = iaa.Pad((2, 0, 4, 4), keep_size=False)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=4, y2=4),
ia.BoundingBox(x1=1, y1=1, x2=3, y2=4)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(4, 4, 3))
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi])
assert len(bbsoi_aug) == 2
for bbsoi_aug_i in bbsoi_aug:
assert bbsoi_aug_i.shape == (10, 8, 3)
assert len(bbsoi_aug_i.bounding_boxes) == 2
assert bbsoi_aug_i.bounding_boxes[0].coords_almost_equals(
[(4+0, 2+0), (4+4, 2+4)]
)
assert bbsoi_aug_i.bounding_boxes[1].coords_almost_equals(
[(4+1, 2+1), (4+3, 2+4)]
)
def test_pad_bounding_boxes_by_tuple_of_fixed_ints_with_keep_size(self):
aug = iaa.Pad((2, 0, 4, 4), keep_size=True)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=4, y2=4),
ia.BoundingBox(x1=1, y1=1, x2=3, y2=4)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(4, 4, 3))
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi])
assert len(bbsoi_aug) == 2
for bbsoi_aug_i in bbsoi_aug:
assert bbsoi_aug_i.shape == (4, 4, 3)
assert len(bbsoi_aug_i.bounding_boxes) == 2
assert bbsoi_aug_i.bounding_boxes[0].coords_almost_equals(
[(4*((4+0)/8), 4*((2+0)/10)), (4*((4+4)/8), 4*((2+4)/10))]
)
assert bbsoi_aug_i.bounding_boxes[1].coords_almost_equals(
[(4*((4+1)/8), 4*((2+1)/10)), (4*((4+3)/8), 4*((2+4)/10))]
)
def test_pad_mode_is_stochastic_parameter(self):
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode=iap.Choice(["constant", "maximum", "edge"]),
pad_cval=0,
keep_size=False)
image = np.zeros((1, 2), dtype=np.uint8)
image[0, 0] = 100
image[0, 1] = 50
seen = [0, 0, 0]
for _ in sm.xrange(300):
observed = aug.augment_image(image)
if observed[0, 2] == 0:
seen[0] += 1
elif observed[0, 2] == 100:
seen[1] += 1
elif observed[0, 2] == 50:
seen[2] += 1
else:
assert False
assert np.all([100 - 50 < v < 100 + 50 for v in seen])
def test_bad_datatype_for_pad_mode_causes_failure(self):
got_exception = False
try:
_aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode=False,
pad_cval=0,
keep_size=False)
except Exception as exc:
assert "Expected pad_mode to be " in str(exc)
got_exception = True
assert got_exception
def test_pad_heatmaps_with_pad_mode_set(self):
# pad modes, heatmaps (always uses constant padding)
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="edge",
pad_cval=0,
keep_size=False)
heatmaps_arr = np.ones((3, 3, 1), dtype=np.float32)
heatmaps = HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
observed = aug.augment_heatmaps([heatmaps])[0]
assert np.sum(observed.get_arr() <= 1e-4) == 3
def test_pad_segmaps_with_pad_mode_set(self):
# pad modes, segmaps (always uses constant padding)
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="edge",
pad_cval=0,
keep_size=False)
segmaps_arr = np.ones((3, 3, 1), dtype=np.int32)
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
observed = aug.augment_segmentation_maps([segmaps])[0]
assert np.sum(observed.get_arr() == 0) == 3
def test_pad_cval_is_int(self):
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="constant",
pad_cval=100,
keep_size=False)
image = np.zeros((1, 1), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed[0, 0] == 0
assert observed[0, 1] == 100
def test_pad_cval_is_stochastic_parameter(self):
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="constant",
pad_cval=iap.Choice([50, 100]),
keep_size=False)
image = np.zeros((1, 1), dtype=np.uint8)
seen = [0, 0]
for _ in sm.xrange(200):
observed = aug.augment_image(image)
if observed[0, 1] == 50:
seen[0] += 1
elif observed[0, 1] == 100:
seen[1] += 1
else:
assert False
assert np.all([100 - 50 < v < 100 + 50 for v in seen])
def test_pad_cval_is_tuple(self):
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="constant",
pad_cval=(50, 52),
keep_size=False)
image = np.zeros((1, 1), dtype=np.uint8)
seen = [0, 0, 0]
for _ in sm.xrange(300):
observed = aug.augment_image(image)
if observed[0, 1] == 50:
seen[0] += 1
elif observed[0, 1] == 51:
seen[1] += 1
elif observed[0, 1] == 52:
seen[2] += 1
else:
assert False
assert np.all([100 - 50 < v < 100 + 50 for v in seen])
def test_invalid_pad_cval_datatype_leads_to_failure(self):
got_exception = False
try:
_aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="constant",
pad_cval="test",
keep_size=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_pad_heatmaps_with_cval_set(self):
# pad cvals, heatmaps (should always use cval 0)
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="constant",
pad_cval=255,
keep_size=False)
heatmaps_arr = np.zeros((3, 3, 1), dtype=np.float32)
heatmaps = HeatmapsOnImage(heatmaps_arr, shape=(3, 3, 3))
observed = aug.augment_heatmaps([heatmaps])[0]
assert np.sum(observed.get_arr() > 1e-4) == 0
def test_pad_segmaps_with_cval_set(self):
# pad cvals, segmaps (should always use cval 0)
aug = iaa.Pad(px=(0, 1, 0, 0),
pad_mode="constant",
pad_cval=255,
keep_size=False)
segmaps_arr = np.zeros((3, 3, 1), dtype=np.int32)
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(3, 3, 3))
observed = aug.augment_segmentation_maps([segmaps])[0]
assert np.sum(observed.get_arr() > 0) == 0
def test_pad_all_sides_by_100_percent_without_keep_size(self):
aug = iaa.Pad(percent=1.0, keep_size=False)
image = np.zeros((4, 4), dtype=np.uint8) + 1
observed = aug.augment_image(image)
assert observed.shape == (4+4+4, 4+4+4)
assert np.sum(observed[4:-4, 4:-4]) == 4*4
assert np.sum(observed) == 4*4
def test_pad_all_sides_by_stochastic_param_without_keep_size(self):
aug = iaa.Pad(percent=iap.Deterministic(1.0), keep_size=False)
image = np.zeros((4, 4), dtype=np.uint8) + 1
observed = aug.augment_image(image)
assert observed.shape == (4+4+4, 4+4+4)
assert np.sum(observed[4:-4, 4:-4]) == 4*4
assert np.sum(observed) == 4*4
def test_pad_by_tuple_of_two_floats_dont_sample_independently_noks(self):
aug = iaa.Pad(percent=(1.0, 2.0),
sample_independently=False,
keep_size=False)
image = np.zeros((4, 4), dtype=np.uint8) + 1
observed = aug.augment_image(image)
assert np.sum(observed) == 4*4
assert (observed.shape[0] - 4) % 2 == 0
assert (observed.shape[1] - 4) % 2 == 0
def test_bad_datatype_for_percent_leads_to_failure_without_keep_size(self):
got_exception = False
try:
_ = iaa.Pad(percent="test", keep_size=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_pad_each_side_by_100_percent_without_keep_size(self):
image = np.zeros((4, 4), dtype=np.uint8)
image[0, 0] = 255
image[3, 0] = 255
image[0, 3] = 255
image[3, 3] = 255
height, width = image.shape[0:2]
pads = [
(1.0, 0, 0, 0),
(0, 1.0, 0, 0),
(0, 0, 1.0, 0),
(0, 0, 0, 1.0),
]
for pad in pads:
with self.subTest(pad=pad):
top, right, bottom, left = pad
top_px = int(top * height)
right_px = int(right * width)
bottom_px = int(bottom * height)
left_px = int(left * width)
aug = iaa.Pad(percent=pad, keep_size=False)
image_padded = np.pad(
image,
((top_px, bottom_px), (left_px, right_px)),
mode="constant",
constant_values=0)
observed = aug.augment_image(image)
assert np.array_equal(observed, image_padded)
def _test_pad_cba_each_side_by_100_percent_without_keep_size(
self, augf_name, cbaoi):
height, width = cbaoi.shape[0:2]
pads = [
(1.0, 0, 0, 0),
(0, 1.0, 0, 0),
(0, 0, 1.0, 0),
(0, 0, 0, 1.0),
]
for pad in pads:
with self.subTest(pad=pad):
top, right, bottom, left = pad
top_px = int(top * height)
left_px = int(left * width)
aug = iaa.Pad(percent=pad, keep_size=False)
cbaoi_moved = cbaoi.shift(x=left_px, y=top_px)
cbaoi_moved.shape = (
int(height+top*height+bottom*height),
int(width+left*width+right*width)
)
observed = getattr(aug, augf_name)(cbaoi)
assert_cbaois_equal(observed, cbaoi_moved)
def test_pad_keypoints_each_side_by_100_percent_without_keep_size(self):
height, width = (4, 4)
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=3, y=3),
ia.Keypoint(x=3, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(height, width))
self._test_pad_cba_each_side_by_100_percent_without_keep_size(
"augment_keypoints", kpsoi)
def test_pad_polygons_each_side_by_100_percent_without_keep_size(self):
height, width = (4, 4)
polys = [ia.Polygon([(0, 0), (4, 0), (4, 4)]),
ia.Polygon([(1, 2), (2, 3), (0, 4)])]
psoi = ia.PolygonsOnImage(polys, shape=(height, width))
self._test_pad_cba_each_side_by_100_percent_without_keep_size(
"augment_polygons", psoi)
def test_pad_line_strings_each_side_by_100_percent_without_keep_size(self):
height, width = (4, 4)
lss = [ia.LineString([(0, 0), (4, 0), (4, 4)]),
ia.LineString([(1, 2), (2, 3), (0, 4)])]
lsoi = ia.LineStringsOnImage(lss, shape=(height, width))
self._test_pad_cba_each_side_by_100_percent_without_keep_size(
"augment_line_strings", lsoi)
def test_pad_bbs_each_side_by_100_percent_without_keep_size(self):
height, width = (4, 4)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=4, y2=4),
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(height, width))
self._test_pad_cba_each_side_by_100_percent_without_keep_size(
"augment_bounding_boxes", bbsoi)
def test_pad_heatmaps_smaller_than_img_by_floats_without_keep_size(self):
# pad smaller heatmaps
# heatmap is (6, 4), image is (6, 16)
# image is padded by (0.5, 0.25, 0.5, 0.25)
# expected image size: (12, 24)
# expected heatmap size: (12, 6)
aug = iaa.Pad(percent=(0.5, 0.25, 0.5, 0.25), keep_size=False)
heatmaps_arr_small = np.float32([
[0, 0, 0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 0, 0, 0]
])
top, bottom, left, right = 3, 3, 1, 1
heatmaps_arr_small_padded = np.pad(
heatmaps_arr_small,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
heatmaps = [ia.HeatmapsOnImage(heatmaps_arr_small, shape=(6, 16))]
observed = aug.augment_heatmaps(heatmaps)[0]
assert observed.shape == (12, 24)
assert observed.arr_0to1.shape == (12, 6, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.arr_0to1[..., 0], heatmaps_arr_small_padded)
def test_pad_segmaps_smaller_than_img_by_floats_without_keep_size(self):
aug = iaa.Pad(percent=(0.5, 0.25, 0.5, 0.25), keep_size=False)
segmaps_arr_small = np.int32([
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]
])
top, bottom, left, right = 3, 3, 1, 1
segmaps_arr_small_padded = np.pad(
segmaps_arr_small,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
segmaps = [SegmentationMapsOnImage(segmaps_arr_small, shape=(6, 16))]
observed = aug.augment_segmentation_maps(segmaps)[0]
assert observed.shape == (12, 24)
assert observed.arr.shape == (12, 6, 1)
assert np.array_equal(observed.arr[..., 0], segmaps_arr_small_padded)
def test_pad_heatmaps_smaller_than_img_by_floats_with_keep_size(self):
# pad smaller heatmaps, with keep_size=True
# heatmap is (6, 4), image is (6, 16)
# image is padded by (0.5, 0.25, 0.5, 0.25)
# expected image size: (12, 24) -> (6, 16) after resize
# expected heatmap size: (12, 6) -> (6, 4) after resize
aug = iaa.Pad(percent=(0.5, 0.25, 0.5, 0.25), keep_size=True)
heatmaps_arr_small = np.float32([
[0, 0, 0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 1.0, 1.0, 0],
[0, 0, 0, 0]
])
top, bottom, left, right = 3, 3, 1, 1
heatmaps_arr_small_padded = np.pad(
heatmaps_arr_small,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
heatmaps = [ia.HeatmapsOnImage(heatmaps_arr_small, shape=(6, 16))]
observed = aug.augment_heatmaps(heatmaps)[0]
assert observed.shape == (6, 16)
assert observed.arr_0to1.shape == (6, 4, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(
observed.arr_0to1[..., 0],
np.clip(
ia.imresize_single_image(
heatmaps_arr_small_padded, (6, 4), interpolation="cubic"),
0, 1.0
)
)
def test_pad_segmaps_smaller_than_img_by_floats_with_keep_size(self):
aug = iaa.Pad(percent=(0.5, 0.25, 0.5, 0.25), keep_size=True)
segmaps_arr_small = np.int32([
[0, 0, 0, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 1, 1, 0],
[0, 0, 0, 0]
])
top, bottom, left, right = 3, 3, 1, 1
segmaps_arr_small_padded = np.pad(
segmaps_arr_small,
((top, bottom), (left, right)),
mode="constant",
constant_values=0)
segmaps = [SegmentationMapsOnImage(segmaps_arr_small, shape=(6, 16))]
observed = aug.augment_segmentation_maps(segmaps)[0]
assert observed.shape == (6, 16)
assert observed.arr.shape == (6, 4, 1)
assert np.array_equal(
observed.arr[..., 0],
ia.imresize_single_image(
segmaps_arr_small_padded, (6, 4), interpolation="nearest")
)
def test_pad_keypoints_by_floats_without_keep_size(self):
aug = iaa.Pad(percent=(0.5, 0, 1.0, 1.0), keep_size=False)
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=0)]
kpsoi = ia.KeypointsOnImage(kps, shape=(4, 4, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (10, 8, 3)
assert len(kpsoi_aug.keypoints) == 2
assert np.allclose(kpsoi_aug.keypoints[0].x, 4+1)
assert np.allclose(kpsoi_aug.keypoints[0].y, 2+2)
assert np.allclose(kpsoi_aug.keypoints[1].x, 4+3)
assert np.allclose(kpsoi_aug.keypoints[1].y, 2+0)
def test_pad_keypoints_by_floats_with_keep_size(self):
aug = iaa.Pad(percent=(0.5, 0, 1.0, 1.0), keep_size=True)
kps = [ia.Keypoint(x=1, y=2), ia.Keypoint(x=3, y=0)]
kpsoi = ia.KeypointsOnImage(kps, shape=(4, 4, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (4, 4, 3)
assert len(kpsoi_aug.keypoints) == 2
assert np.allclose(kpsoi_aug.keypoints[0].x, ((4+1)/8)*4)
assert np.allclose(kpsoi_aug.keypoints[0].y, ((2+2)/10)*4)
assert np.allclose(kpsoi_aug.keypoints[1].x, ((4+3)/8)*4)
assert np.allclose(kpsoi_aug.keypoints[1].y, ((2+0)/10)*4)
def test_pad_polygons_by_floats_without_keep_size(self):
aug = iaa.Pad(percent=(0.5, 0, 1.0, 1.0), keep_size=False)
cbaoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)])
], shape=(4, 4, 3))
cbaoi_aug = aug.augment_polygons([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (10, 8, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(4, 2), (8, 2), (8, 6), (4, 6)]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(5, 3), (9, 3), (9, 7), (5, 7)]
)
def test_pad_polygons_by_floats_with_keep_size(self):
# polygons, with keep_size=True
aug = iaa.Pad(percent=(0.5, 0, 1.0, 1.0), keep_size=True)
cbaoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)])
], shape=(4, 4, 3))
cbaoi_aug = aug.augment_polygons([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (4, 4, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(4*(4/8), 4*(2/10)),
(4*(8/8), 4*(2/10)),
(4*(8/8), 4*(6/10)),
(4*(4/8), 4*(6/10))]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(4*(5/8), 4*(3/10)),
(4*(9/8), 4*(3/10)),
(4*(9/8), 4*(7/10)),
(4*(5/8), 4*(7/10))]
)
def test_pad_line_strings_by_floats_without_keep_size(self):
aug = iaa.Pad(percent=(0.5, 0, 1.0, 1.0), keep_size=False)
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.LineString([(1, 1), (5, 1), (5, 5), (1, 5)])
], shape=(4, 4, 3))
cbaoi_aug = aug.augment_line_strings([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (10, 8, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(4, 2), (8, 2), (8, 6), (4, 6)]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(5, 3), (9, 3), (9, 7), (5, 7)]
)
def test_pad_line_strings_by_floats_with_keep_size(self):
# polygons, with keep_size=True
aug = iaa.Pad(percent=(0.5, 0, 1.0, 1.0), keep_size=True)
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.LineString([(1, 1), (5, 1), (5, 5), (1, 5)])
], shape=(4, 4, 3))
cbaoi_aug = aug.augment_line_strings([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (4, 4, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(4*(4/8), 4*(2/10)),
(4*(8/8), 4*(2/10)),
(4*(8/8), 4*(6/10)),
(4*(4/8), 4*(6/10))]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(4*(5/8), 4*(3/10)),
(4*(9/8), 4*(3/10)),
(4*(9/8), 4*(7/10)),
(4*(5/8), 4*(7/10))]
)
def test_pad_bounding_boxes_by_floats_without_keep_size(self):
aug = iaa.Pad(percent=(0.5, 0, 1.0, 1.0), keep_size=False)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=4, y2=4),
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(4, 4, 3))
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi])
assert len(bbsoi_aug) == 2
for bbsoi_aug_i in bbsoi_aug:
assert bbsoi_aug_i.shape == (10, 8, 3)
assert len(bbsoi_aug_i.bounding_boxes) == 2
assert bbsoi_aug_i.bounding_boxes[0].coords_almost_equals(
[(int(1.0*4+0), int(0.5*4+0)),
(int(1.0*4+4), int(0.5*4+4))]
)
assert bbsoi_aug_i.bounding_boxes[1].coords_almost_equals(
[(int(1.0*4+1), int(0.5*4+2)),
(int(1.0*4+3), int(0.5*4+4))]
)
def test_pad_bounding_boxes_by_floats_with_keep_size(self):
# BBs, with keep_size=True
aug = iaa.Pad(percent=(0.5, 0, 1.0, 1.0), keep_size=True)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=4, y2=4),
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(4, 4, 3))
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi])
assert len(bbsoi_aug) == 2
for bbsoi_aug_i in bbsoi_aug:
assert bbsoi_aug_i.shape == (4, 4, 3)
assert len(bbsoi_aug_i.bounding_boxes) == 2
assert bbsoi_aug_i.bounding_boxes[0].coords_almost_equals(
[(4*(4/8), 4*(2/10)),
(4*(8/8), 4*(6/10))]
)
assert bbsoi_aug_i.bounding_boxes[1].coords_almost_equals(
[(4*(5/8), 4*(4/10)),
(4*(7/8), 4*(6/10))]
)
def test_pad_by_tuple_of_floats_at_top_side_without_keep_size(self):
# test pad by range of percentages
aug = iaa.Pad(percent=((0, 1.0), 0, 0, 0), keep_size=False)
seen = [0, 0, 0, 0, 0]
for _ in sm.xrange(500):
observed = aug.augment_image(
np.zeros((4, 4), dtype=np.uint8) + 255)
n_padded = 0
while np.all(observed[0, :] == 0):
n_padded += 1
observed = observed[1:, :]
seen[n_padded] += 1
# note that we cant just check for 100-50 < x < 100+50 here. The
# first and last value (0px and 4px) padding have half the
# probability of occuring compared to the other values. E.g. 0px is
# padded if sampled p falls in range [0, 0.125). 1px is padded if
# sampled p falls in range [0.125, 0.375].
assert np.all([v > 30 for v in seen])
def test_pad_by_tuple_of_floats_at_right_side_without_keep_size(self):
aug = iaa.Pad(percent=(0, (0, 1.0), 0, 0), keep_size=False)
seen = [0, 0, 0, 0, 0]
for _ in sm.xrange(500):
observed = aug.augment_image(np.zeros((4, 4), dtype=np.uint8) + 255)
n_padded = 0
while np.all(observed[:, -1] == 0):
n_padded += 1
observed = observed[:, 0:-1]
seen[n_padded] += 1
assert np.all([v > 30 for v in seen])
def test_pad_by_list_of_floats_at_top_side_without_keep_size(self):
aug = iaa.Pad(percent=([0.0, 1.0], 0, 0, 0), keep_size=False)
seen = [0, 0, 0, 0, 0]
for _ in sm.xrange(500):
observed = aug.augment_image(
np.zeros((4, 4), dtype=np.uint8) + 255)
n_padded = 0
while np.all(observed[0, :] == 0):
n_padded += 1
observed = observed[1:, :]
seen[n_padded] += 1
assert 250 - 50 < seen[0] < 250 + 50
assert seen[1] == 0
assert seen[2] == 0
assert seen[3] == 0
assert 250 - 50 < seen[4] < 250 + 50
def test_pad_by_list_of_floats_at_right_side_without_keep_size(self):
aug = iaa.Pad(percent=(0, [0.0, 1.0], 0, 0), keep_size=False)
seen = [0, 0, 0, 0, 0]
for _ in sm.xrange(500):
observed = aug.augment_image(
np.zeros((4, 4), dtype=np.uint8) + 255)
n_padded = 0
while np.all(observed[:, -1] == 0):
n_padded += 1
observed = observed[:, 0:-1]
seen[n_padded] += 1
assert 250 - 50 < seen[0] < 250 + 50
assert seen[1] == 0
assert seen[2] == 0
assert seen[3] == 0
assert 250 - 50 < seen[4] < 250 + 50
@classmethod
def _test_pad_empty_cba(cls, augf_name, cbaoi):
aug = iaa.Pad(px=(1, 2, 3, 4), keep_size=False)
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
expected = cbaoi.deepcopy()
expected.shape = tuple(
[1+expected.shape[0]+3, 4+expected.shape[1]+2]
+ list(expected.shape[2:]))
assert_cbaois_equal(cbaoi_aug, expected)
def test_pad_empty_keypoints(self):
cbaoi = ia.KeypointsOnImage([], shape=(2, 4, 3))
self._test_pad_empty_cba("augment_keypoints", cbaoi)
def test_pad_empty_polygons(self):
cbaoi = ia.PolygonsOnImage([], shape=(2, 4, 3))
self._test_pad_empty_cba("augment_polygons", cbaoi)
def test_pad_empty_line_strings(self):
cbaoi = ia.LineStringsOnImage([], shape=(2, 4, 3))
self._test_pad_empty_cba("augment_line_strings", cbaoi)
def test_pad_empty_bounding_boxes(self):
cbaoi = ia.BoundingBoxesOnImage([], shape=(2, 4, 3))
self._test_pad_empty_cba("augment_bounding_boxes", cbaoi)
def test_zero_sized_axes_no_keep_size(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Pad(px=1, keep_size=False)
image_aug = aug(image=image)
expected_height = shape[0] + 2
expected_width = shape[1] + 2
expected_shape = tuple([expected_height, expected_width]
+ list(shape[2:]))
assert image_aug.shape == expected_shape
def test_zero_sized_axes_keep_size(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Pad(px=1, keep_size=True)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_pad_other_dtypes_bool_by_int_without_keep_size(self):
aug = iaa.Pad(px=(1, 0, 0, 0), keep_size=False)
mask = np.zeros((4, 3), dtype=bool)
mask[2, 1] = True
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert image_aug.shape == (4, 3)
assert np.all(image_aug[~mask] == 0)
assert np.all(image_aug[mask] == 1)
def test_pad_other_dtypes_uint_int_by_int_without_keep_size(self):
aug = iaa.Pad(px=(1, 0, 0, 0), keep_size=False)
mask = np.zeros((4, 3), dtype=bool)
mask[2, 1] = True
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [
1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
values = values + [(-1) * value for value in values]
else:
values = [
1, 5, 10, 100, int(center_value),
int(0.1 * max_value), int(0.2 * max_value),
int(0.5 * max_value), max_value - 100, max_value]
for value in values:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert image_aug.shape == (4, 3)
assert np.all(image_aug[~mask] == 0)
assert np.all(image_aug[mask] == value)
def test_pad_other_dtypes_float_by_int_without_keep_size(self):
aug = iaa.Pad(px=(1, 0, 0, 0), keep_size=False)
mask = np.zeros((4, 3), dtype=bool)
mask[2, 1] = True
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == np.float16 else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [0.01, 1.0, 10.0, 100.0, 500 ** (isize - 1),
1000 ** (isize - 1)]
values = values + [(-1) * value for value in values]
values = values + [min_value, max_value]
for value in values:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype == np.dtype(dtype)
assert image_aug.shape == (4, 3)
assert np.all(_isclose(image_aug[~mask], 0))
assert np.all(_isclose(image_aug[mask],
high_res_dt(value)))
def test_pickleable(self):
aug = iaa.Pad((0, 10), seed=1)
runtest_pickleable_uint8_img(aug, iterations=5)
class TestCrop(unittest.TestCase):
def setUp(self):
reseed()
@property
def image(self):
base_img = np.array([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]], dtype=np.uint8)
base_img = base_img[:, :, np.newaxis]
return base_img
@property
def images(self):
return np.array([self.image])
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=0), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=2)]
kpsoi = ia.KeypointsOnImage(kps, shape=self.image.shape)
return kpsoi
@property
def psoi(self):
ps = [ia.Polygon([(1, 1), (2, 1), (2, 2)])]
psoi = ia.PolygonsOnImage(ps, shape=self.image.shape)
return psoi
@property
def lsoi(self):
ls = [ia.LineString([(1, 1), (2, 1), (2, 2)])]
lsoi = ia.LineStringsOnImage(ls, shape=self.image.shape)
return lsoi
@property
def bbsoi(self):
bbs = [ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=self.image.shape)
return bbsoi
@property
def heatmaps(self):
heatmaps_arr = np.float32([[0, 0, 0],
[0, 1.0, 0],
[0, 0, 0]])
return [ia.HeatmapsOnImage(heatmaps_arr, shape=self.image.shape)]
@property
def segmaps(self):
segmaps_arr = np.int32([[0, 0, 0],
[0, 1, 0],
[0, 0, 0]])
return [ia.SegmentationMapsOnImage(segmaps_arr, shape=self.image.shape)]
# TODO split up and add polys/LS/BBs
def test_crop_by_fixed_int_on_each_side_on_its_own(self):
# test crop by 1 pixel on each side
crops = [
(1, 0, 0, 0),
(0, 1, 0, 0),
(0, 0, 1, 0),
(0, 0, 0, 1),
]
for crop in crops:
with self.subTest(px=crop):
aug = iaa.Crop(px=crop, keep_size=False)
top, right, bottom, left = crop
height, width = self.image.shape[0:2]
base_img_cropped = self.image[top:height-bottom,
left:width-right,
:]
observed = aug.augment_images(self.images)
assert np.array_equal(observed, np.array([base_img_cropped]))
observed = aug.augment_images([self.image])
assert array_equal_lists(observed, [base_img_cropped])
keypoints_moved = self.kpsoi.shift(x=-left, y=-top)
observed = aug.augment_keypoints(self.kpsoi)
assert keypoints_equal(observed, keypoints_moved)
heatmaps_arr = self.heatmaps[0].get_arr()
height, width = heatmaps_arr.shape[0:2]
heatmaps_arr_cropped = heatmaps_arr[top:height-bottom,
left:width-right]
observed = aug.augment_heatmaps(self.heatmaps)[0]
assert observed.shape == base_img_cropped.shape
assert np.array_equal(observed.get_arr(), heatmaps_arr_cropped)
segmaps_arr = self.segmaps[0].get_arr()
height, width = segmaps_arr.shape[0:2]
segmaps_arr_cropped = segmaps_arr[top:height-bottom,
left:width-right]
observed = aug.augment_segmentation_maps(self.segmaps)[0]
assert observed.shape == base_img_cropped.shape
assert np.array_equal(observed.get_arr(), segmaps_arr_cropped)
# TODO split up and add polys/LS/BBs
def test_crop_by_tuple_of_ints_on_each_side_on_its_own(self):
def _to_range_tuple(val):
return val if isinstance(val, tuple) else (val, val)
crops = [
((0, 2), 0, 0, 0),
(0, (0, 2), 0, 0),
(0, 0, (0, 2), 0),
(0, 0, 0, (0, 2)),
]
for crop in crops:
with self.subTest(px=crop):
aug = iaa.Crop(px=crop, keep_size=False)
aug_det = aug.to_deterministic()
top, right, bottom, left = crop
height, width = self.image.shape[0:2]
top_range = _to_range_tuple(top)
right_range = _to_range_tuple(right)
bottom_range = _to_range_tuple(bottom)
left_range = _to_range_tuple(left)
top_values = sm.xrange(top_range[0], top_range[1]+1)
right_values = sm.xrange(right_range[0], right_range[1]+1)
bottom_values = sm.xrange(bottom_range[0], bottom_range[1]+1)
left_values = sm.xrange(left_range[0], left_range[1]+1)
images_cropped = []
keypoints_cropped = []
for top_val in top_values:
for right_val in right_values:
for bottom_val in bottom_values:
for left_val in left_values:
images_cropped.append(
self.image[top_val:height-bottom_val,
left_val:width-right_val,
:]
)
keypoints_cropped.append(
self.kpsoi.shift(
x=-left_val, y=-top_val)
)
movements = []
movements_det = []
for i in sm.xrange(100):
observed = aug.augment_images(self.images)
matches = [
(1
if np.array_equal(observed,
np.array([base_img_cropped]))
else 0)
for base_img_cropped
in images_cropped]
movements.append(np.argmax(np.array(matches)))
assert any([val == 1 for val in matches])
observed = aug_det.augment_images(self.images)
matches = [
(1
if np.array_equal(observed,
np.array([base_img_cropped]))
else 0)
for base_img_cropped
in images_cropped]
movements_det.append(np.argmax(np.array(matches)))
assert any([val == 1 for val in matches])
observed = aug.augment_images([self.image])
assert any([array_equal_lists(observed, [base_img_cropped])
for base_img_cropped
in images_cropped])
observed = aug.augment_keypoints(self.kpsoi)
assert any([keypoints_equal(observed, kp)
for kp
in keypoints_cropped])
assert len(set(movements)) == 3
assert len(set(movements_det)) == 1
# TODO split up and add polys/LS/BBs
def test_crop_by_list_of_ints_on_each_side_on_its_own(self):
# test crop by list of exact pixel values
crops = [
([0, 2], 0, 0, 0),
(0, [0, 2], 0, 0),
(0, 0, [0, 2], 0),
(0, 0, 0, [0, 2]),
]
for crop in crops:
with self.subTest(px=crop):
aug = iaa.Crop(px=crop, keep_size=False)
aug_det = aug.to_deterministic()
top, right, bottom, left = crop
height, width = self.image.shape[0:2]
top_range = top if isinstance(top, list) else [top]
right_range = right if isinstance(right, list) else [right]
bottom_range = bottom if isinstance(bottom, list) else [bottom]
left_range = left if isinstance(left, list) else [left]
images_cropped = []
keypoints_cropped = []
for top_val in top_range:
for right_val in right_range:
for bottom_val in bottom_range:
for left_val in left_range:
images_cropped.append(
self.image[top_val:height-bottom_val,
left_val:width-right_val,
:]
)
keypoints_cropped.append(
self.kpsoi.shift(
x=-left_val, y=-top_val)
)
movements = []
movements_det = []
for i in sm.xrange(100):
observed = aug.augment_images(self.images)
matches = [
(1
if np.array_equal(observed,
np.array([base_img_cropped]))
else 0)
for base_img_cropped
in images_cropped]
movements.append(np.argmax(np.array(matches)))
assert any([val == 1 for val in matches])
observed = aug_det.augment_images(self.images)
matches = [
(1
if np.array_equal(observed,
np.array([base_img_cropped]))
else 0)
for base_img_cropped in images_cropped]
movements_det.append(np.argmax(np.array(matches)))
assert any([val == 1 for val in matches])
observed = aug.augment_images([self.image])
assert any([array_equal_lists(observed, [base_img_cropped])
for base_img_cropped
in images_cropped])
observed = aug.augment_keypoints(self.kpsoi)
assert any([keypoints_equal(observed, kp)
for kp
in keypoints_cropped])
assert len(set(movements)) == 2
assert len(set(movements_det)) == 1
def test_crop_heatmaps_smaller_than_img_by_fixed_ints_without_ks(self):
# crop smaller heatmaps
# heatmap is (6, 8), image is (6, 16)
# image is cropped by (1, 4, 1, 4)
# expected image size: (4, 8)
# expected heatmap size: (4, 4)
aug = iaa.Crop(px=(1, 4, 1, 4), keep_size=False)
heatmaps_arr_small = np.zeros((6, 8), dtype=np.float32)
heatmaps_arr_small[1:-1, 1:-1] = 1.0
heatmaps = HeatmapsOnImage(heatmaps_arr_small, shape=(6, 16))
top, bottom, left, right = 1, 1, 2, 2
heatmaps_arr_small_cropped = \
heatmaps_arr_small[top:-bottom, left:-right]
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == (4, 8)
assert observed.arr_0to1.shape == (4, 4, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.arr_0to1[..., 0],
heatmaps_arr_small_cropped)
def test_crop_segmaps_smaller_than_img_by_fixed_ints_without_ks(self):
aug = iaa.Crop(px=(1, 4, 1, 4), keep_size=False)
segmaps_arr_small = np.zeros((6, 8), dtype=np.int32)
segmaps_arr_small[1:-1, 1:-1] = 1
segmaps = SegmentationMapsOnImage(segmaps_arr_small, shape=(6, 16))
top, bottom, left, right = 1, 1, 2, 2
segmaps_arr_small_cropped = segmaps_arr_small[top:-bottom, left:-right]
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == (4, 8)
assert observed.arr.shape == (4, 4, 1)
assert np.array_equal(observed.arr[..., 0], segmaps_arr_small_cropped)
def test_crop_heatmaps_smaller_than_img_by_fixed_ints_with_ks(self):
# crop smaller heatmaps, with keep_size=True
# heatmap is (6, 8), image is (6, 16)
# image is cropped by (1, 4, 1, 4)
# expected image size: (4, 8) -> (6, 16) after resize
# expected heatmap size: (4, 4) -> (6, 4) after resize
aug = iaa.Crop(px=(1, 4, 1, 4), keep_size=True)
heatmaps_arr_small = np.zeros((6, 8), dtype=np.float32)
heatmaps_arr_small[1:-1, 1:-1] = 1.0
heatmaps = HeatmapsOnImage(heatmaps_arr_small, shape=(6, 16))
top, bottom, left, right = 1, 1, 2, 2
heatmaps_arr_small_cropped = \
heatmaps_arr_small[top:-bottom, left:-right]
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == (6, 16)
assert observed.arr_0to1.shape == (6, 8, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(
observed.arr_0to1[..., 0],
np.clip(
ia.imresize_single_image(
heatmaps_arr_small_cropped,
(6, 8),
interpolation="cubic"),
0,
1.0
)
)
def test_crop_segmaps_smaller_than_img_by_fixed_ints_with_ks(self):
aug = iaa.Crop(px=(1, 4, 1, 4), keep_size=True)
segmaps_arr_small = np.zeros((6, 8), dtype=np.int32)
segmaps_arr_small[1:-1, 1:-1] = 1
segmaps = SegmentationMapsOnImage(segmaps_arr_small, shape=(6, 16))
top, bottom, left, right = 1, 1, 2, 2
segmaps_arr_small_cropped = segmaps_arr_small[top:-bottom, left:-right]
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == (6, 16)
assert observed.arr.shape == (6, 8, 1)
assert np.array_equal(
observed.arr[..., 0],
ia.imresize_single_image(
segmaps_arr_small_cropped,
(6, 8),
interpolation="nearest"),
)
def test_crop_keypoints_by_fixed_ints_without_keep_size(self):
aug = iaa.Crop((1, 0, 4, 4), keep_size=False)
kps = [ia.Keypoint(x=3, y=6), ia.Keypoint(x=8, y=5)]
kpsoi = ia.KeypointsOnImage(kps, shape=(14, 14, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (9, 10, 3)
assert len(kpsoi_aug.keypoints) == 2
assert np.allclose(kpsoi_aug.keypoints[0].x, 3-4)
assert np.allclose(kpsoi_aug.keypoints[0].y, 6-1)
assert np.allclose(kpsoi_aug.keypoints[1].x, 8-4)
assert np.allclose(kpsoi_aug.keypoints[1].y, 5-1)
def test_crop_keypoints_by_fixed_ints_with_keep_size(self):
aug = iaa.Crop((1, 0, 4, 4), keep_size=True)
kps = [ia.Keypoint(x=3, y=6), ia.Keypoint(x=8, y=5)]
kpsoi = ia.KeypointsOnImage(kps, shape=(14, 14, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (14, 14, 3)
assert len(kpsoi_aug.keypoints) == 2
assert np.allclose(kpsoi_aug.keypoints[0].x, ((3-4)/10)*14)
assert np.allclose(kpsoi_aug.keypoints[0].y, ((6-1)/9)*14)
assert np.allclose(kpsoi_aug.keypoints[1].x, ((8-4)/10)*14)
assert np.allclose(kpsoi_aug.keypoints[1].y, ((5-1)/9)*14)
def test_crop_polygons_by_fixed_ints_without_keep_size(self):
aug = iaa.Crop((1, 0, 4, 4), keep_size=False)
polygons = [ia.Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.PolygonsOnImage(polygons, shape=(10, 10, 3))
cbaoi_aug = aug.augment_polygons([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (5, 6, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(0-4, 0-1), (4-4, 0-1), (4-4, 4-1), (0-4, 4-1)]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(1-4, 1-1), (5-4, 1-1), (5-4, 5-1), (1-4, 5-1)]
)
def test_crop_polygons_by_fixed_ints_with_keep_size(self):
aug = iaa.Crop((1, 0, 4, 4), keep_size=True)
polygons = [ia.Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.PolygonsOnImage(polygons, shape=(10, 10, 3))
cbaoi_aug = aug.augment_polygons([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (10, 10, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(10*(-4/6), 10*(-1/5)),
(10*(0/6), 10*(-1/5)),
(10*(0/6), 10*(3/5)),
(10*(-4/6), 10*(3/5))]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(10*(-3/6), 10*(0/5)),
(10*(1/6), 10*(0/5)),
(10*(1/6), 10*(4/5)),
(10*(-3/6), 10*(4/5))]
)
def test_crop_line_strings_by_fixed_ints_without_keep_size(self):
aug = iaa.Crop((1, 0, 4, 4), keep_size=False)
lss = [ia.LineString([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.LineString([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.LineStringsOnImage(lss, shape=(10, 10, 3))
cbaoi_aug = aug.augment_line_strings([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (5, 6, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(0-4, 0-1), (4-4, 0-1), (4-4, 4-1), (0-4, 4-1)]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(1-4, 1-1), (5-4, 1-1), (5-4, 5-1), (1-4, 5-1)]
)
def test_crop_line_strings_by_fixed_ints_with_keep_size(self):
aug = iaa.Crop((1, 0, 4, 4), keep_size=True)
lss = [ia.LineString([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.LineString([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.LineStringsOnImage(lss, shape=(10, 10, 3))
cbaoi_aug = aug.augment_line_strings([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (10, 10, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(10*(-4/6), 10*(-1/5)),
(10*(0/6), 10*(-1/5)),
(10*(0/6), 10*(3/5)),
(10*(-4/6), 10*(3/5))]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(10*(-3/6), 10*(0/5)),
(10*(1/6), 10*(0/5)),
(10*(1/6), 10*(4/5)),
(10*(-3/6), 10*(4/5))]
)
def test_crop_bounding_boxes_by_fixed_ints_without_keep_size(self):
aug = iaa.Crop((1, 0, 4, 4), keep_size=False)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=10, y2=10),
ia.BoundingBox(x1=1, y1=2, x2=9, y2=10)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 10, 3))
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi])
assert len(bbsoi_aug) == 2
for bbsoi_aug_i in bbsoi_aug:
assert bbsoi_aug_i.shape == (5, 6, 3)
assert len(bbsoi_aug_i.bounding_boxes) == 2
assert bbsoi_aug_i.bounding_boxes[0].coords_almost_equals(
[(0-4, 0-1), (10-4, 10-1)]
)
assert bbsoi_aug_i.bounding_boxes[1].coords_almost_equals(
[(1-4, 2-1), (9-4, 10-1)]
)
def test_crop_bounding_boxes_by_fixed_ints_with_keep_size(self):
aug = iaa.Crop((1, 0, 4, 4), keep_size=True)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=10, y2=10),
ia.BoundingBox(x1=1, y1=2, x2=9, y2=10)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 10, 3))
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi])
assert len(bbsoi_aug) == 2
for bbsoi_aug_i in bbsoi_aug:
assert bbsoi_aug_i.shape == (10, 10, 3)
assert len(bbsoi_aug_i.bounding_boxes) == 2
assert bbsoi_aug_i.bounding_boxes[0].coords_almost_equals(
[(10*(-4/6), 10*(-1/5)),
(10*(6/6), 10*(9/5))]
)
assert bbsoi_aug_i.bounding_boxes[1].coords_almost_equals(
[(10*(-3/6), 10*(1/5)),
(10*(5/6), 10*(9/5))]
)
def test_crop_by_one_fixed_float_without_keep_size(self):
aug = iaa.Crop(percent=0.1, keep_size=False)
image = np.random.randint(0, 255, size=(50, 50), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.shape == (40, 40)
assert np.all(observed == image[5:-5, 5:-5])
def test_crop_by_stochastic_parameter_without_keep_size(self):
aug = iaa.Crop(percent=iap.Deterministic(0.1), keep_size=False)
image = np.random.randint(0, 255, size=(50, 50), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.shape == (40, 40)
assert np.all(observed == image[5:-5, 5:-5])
def test_crop_by_tuple_of_two_floats_without_keep_size(self):
aug = iaa.Crop(percent=(0.1, 0.2), keep_size=False)
image = np.random.randint(0, 255, size=(50, 50), dtype=np.uint8)
observed = aug.augment_image(image)
assert 30 <= observed.shape[0] <= 40
assert 30 <= observed.shape[1] <= 40
def test_invalid_datatype_for_percent_parameter_fails(self):
got_exception = False
try:
_ = iaa.Crop(percent="test", keep_size=False)
except Exception as exc:
assert "Expected " in str(exc)
got_exception = True
assert got_exception
def test_crop_by_fixed_float_on_each_side_on_its_own(self):
image = np.random.randint(0, 255, size=(50, 50), dtype=np.uint8)
height, width = image.shape[0:2]
crops = [
(0.1, 0, 0, 0),
(0, 0.1, 0, 0),
(0, 0, 0.1, 0),
(0, 0, 0, 0.1),
]
for crop in crops:
with self.subTest(percent=crop):
aug = iaa.Crop(percent=crop, keep_size=False)
top, right, bottom, left = crop
top_px = int(round(top * height))
right_px = int(round(right * width))
bottom_px = int(round(bottom * height))
left_px = int(round(left * width))
# dont use :-bottom_px and ;-right_px here, because these
# values can be 0
image_cropped = image[top_px:50-bottom_px, left_px:50-right_px]
observed = aug.augment_image(image)
assert np.array_equal(observed, image_cropped)
def _test_crop_cba_by_fixed_float_on_each_side_on_its_own(
self, augf_name, cbaoi):
height, width = cbaoi.shape[0:2]
crops = [
(0.1, 0, 0, 0),
(0, 0.1, 0, 0),
(0, 0, 0.1, 0),
(0, 0, 0, 0.1),
]
for crop in crops:
with self.subTest(augf_name=augf_name, percent=crop):
aug = iaa.Crop(percent=crop, keep_size=False)
top, right, bottom, left = crop
top_px = int(round(top * height))
right_px = int(round(right * width))
left_px = int(round(left * width))
bottom_px = int(round(bottom * height))
observed = getattr(aug, augf_name)(cbaoi)
expected = cbaoi.shift(x=-left_px, y=-top_px)
expected.shape = tuple(
[expected.shape[0] - top_px - bottom_px,
expected.shape[1] - left_px - right_px]
+ list(expected.shape[2:])
)
assert_cbaois_equal(observed, expected)
def test_crop_keypoints_by_fixed_float_on_each_side_on_its_own(self):
height, width = (50, 50)
kps = [ia.Keypoint(x=10, y=11), ia.Keypoint(x=20, y=21),
ia.Keypoint(x=30, y=31)]
kpsoi = ia.KeypointsOnImage(kps, shape=(height, width))
self._test_crop_cba_by_fixed_float_on_each_side_on_its_own(
"augment_keypoints", kpsoi)
def test_crop_polygons_by_fixed_float_on_each_side_on_its_own(self):
height, width = (50, 50)
polygons = [ia.Polygon([(0, 0), (40, 0), (40, 40), (0, 40)]),
ia.Polygon([(10, 10), (50, 10), (50, 50), (10, 50)])]
psoi = ia.PolygonsOnImage(polygons, shape=(height, width, 3))
self._test_crop_cba_by_fixed_float_on_each_side_on_its_own(
"augment_polygons", psoi)
def test_crop_line_strings_by_fixed_float_on_each_side_on_its_own(self):
height, width = (50, 50)
lss = [ia.LineString([(0, 0), (40, 0), (40, 40), (0, 40)]),
ia.LineString([(10, 10), (50, 10), (50, 50), (10, 50)])]
lsoi = ia.LineStringsOnImage(lss, shape=(height, width, 3))
self._test_crop_cba_by_fixed_float_on_each_side_on_its_own(
"augment_line_strings", lsoi)
def test_crop_bounding_boxes_by_fixed_float_on_each_side_on_its_own(self):
height, width = (50, 50)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=40, y2=40),
ia.BoundingBox(x1=10, y1=10, x2=30, y2=40)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(height, width, 3))
self._test_crop_cba_by_fixed_float_on_each_side_on_its_own(
"augment_bounding_boxes", bbsoi)
def test_crop_heatmaps_smaller_than_img_by_fixed_floats_without_ks(self):
# crop smaller heatmaps
# heatmap is (8, 12), image is (16, 32)
# image is cropped by (0.25, 0.25, 0.25, 0.25)
# expected image size: (8, 16)
# expected heatmap size: (4, 6)
aug = iaa.Crop(percent=(0.25, 0.25, 0.25, 0.25), keep_size=False)
heatmaps_arr_small = np.zeros((8, 12), dtype=np.float32)
heatmaps_arr_small[2:-2, 4:-4] = 1.0
heatmaps = ia.HeatmapsOnImage(heatmaps_arr_small, shape=(16, 32))
top, bottom, left, right = 2, 2, 3, 3
heatmaps_arr_small_cropped = \
heatmaps_arr_small[top:-bottom, left:-right]
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == (8, 16)
assert observed.arr_0to1.shape == (4, 6, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(observed.arr_0to1[..., 0], heatmaps_arr_small_cropped)
def test_crop_segmaps_smaller_than_img_by_fixed_floats_without_ks(self):
aug = iaa.Crop(percent=(0.25, 0.25, 0.25, 0.25), keep_size=False)
segmaps_arr_small = np.zeros((8, 12), dtype=np.int32)
segmaps_arr_small[2:-2, 4:-4] = 1
segmaps = SegmentationMapsOnImage(segmaps_arr_small, shape=(16, 32))
top, bottom, left, right = 2, 2, 3, 3
segmaps_arr_small_cropped = segmaps_arr_small[top:-bottom, left:-right]
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == (8, 16)
assert observed.arr.shape == (4, 6, 1)
assert np.array_equal(observed.arr[..., 0], segmaps_arr_small_cropped)
def test_crop_heatmaps_smaller_than_img_by_fixed_floats_with_ks(self):
# crop smaller heatmaps, with keep_size=True
# heatmap is (8, 12), image is (16, 32)
# image is cropped by (0.25, 0.25, 0.25, 0.25)
# expected image size: (8, 16) -> (16, 32) after resize
# expected heatmap size: (4, 6) -> (8, 12) after resize
aug = iaa.Crop(percent=(0.25, 0.25, 0.25, 0.25), keep_size=True)
heatmaps_arr_small = np.zeros((8, 12), dtype=np.float32)
heatmaps_arr_small[2:-2, 4:-4] = 1.0
heatmaps = ia.HeatmapsOnImage(heatmaps_arr_small, shape=(16, 32))
top, bottom, left, right = 2, 2, 3, 3
heatmaps_arr_small_cropped = \
heatmaps_arr_small[top:-bottom, left:-right]
observed = aug.augment_heatmaps([heatmaps])[0]
assert observed.shape == (16, 32)
assert observed.arr_0to1.shape == (8, 12, 1)
assert 0 - 1e-6 < observed.min_value < 0 + 1e-6
assert 1 - 1e-6 < observed.max_value < 1 + 1e-6
assert np.allclose(
observed.arr_0to1[..., 0],
np.clip(
ia.imresize_single_image(
heatmaps_arr_small_cropped,
(8, 12),
interpolation="cubic"),
0,
1.0
)
)
def test_crop_segmaps_smaller_than_img_by_fixed_floats_with_ks(self):
aug = iaa.Crop(percent=(0.25, 0.25, 0.25, 0.25), keep_size=True)
segmaps_arr_small = np.zeros((8, 12), dtype=np.int32)
segmaps_arr_small[2:-2, 4:-4] = 1
segmaps = SegmentationMapsOnImage(segmaps_arr_small, shape=(16, 32))
top, bottom, left, right = 2, 2, 3, 3
segmaps_arr_small_cropped = segmaps_arr_small[top:-bottom, left:-right]
observed = aug.augment_segmentation_maps([segmaps])[0]
assert observed.shape == (16, 32)
assert observed.arr.shape == (8, 12, 1)
assert np.allclose(
observed.arr[..., 0],
ia.imresize_single_image(
segmaps_arr_small_cropped,
(8, 12),
interpolation="nearest")
)
def test_crop_keypoints_by_fixed_floats_without_keep_size(self):
aug = iaa.Crop(percent=(0.25, 0, 0.5, 0.1), keep_size=False)
kps = [ia.Keypoint(x=12, y=10), ia.Keypoint(x=8, y=12)]
kpsoi = ia.KeypointsOnImage(kps, shape=(16, 20, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (4, 18, 3)
assert len(kpsoi_aug.keypoints) == 2
assert np.allclose(kpsoi_aug.keypoints[0].x, 12-2)
assert np.allclose(kpsoi_aug.keypoints[0].y, 10-4)
assert np.allclose(kpsoi_aug.keypoints[1].x, 8-2)
assert np.allclose(kpsoi_aug.keypoints[1].y, 12-4)
def test_crop_keypoints_by_fixed_floats_with_keep_size(self):
aug = iaa.Crop(percent=(0.25, 0, 0.5, 0.1), keep_size=True)
kps = [ia.Keypoint(x=12, y=10), ia.Keypoint(x=8, y=12)]
kpsoi = ia.KeypointsOnImage(kps, shape=(16, 20, 3))
kpsoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpsoi_aug.shape == (16, 20, 3)
assert len(kpsoi_aug.keypoints) == 2
assert np.allclose(kpsoi_aug.keypoints[0].x, ((12-2)/18)*20)
assert np.allclose(kpsoi_aug.keypoints[0].y, ((10-4)/4)*16)
assert np.allclose(kpsoi_aug.keypoints[1].x, ((8-2)/18)*20)
assert np.allclose(kpsoi_aug.keypoints[1].y, ((12-4)/4)*16)
def test_crop_polygons_by_fixed_floats_without_keep_size(self):
aug = iaa.Crop(percent=(0.2, 0, 0.5, 0.1), keep_size=False)
polygons = [ia.Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.PolygonsOnImage(polygons, shape=(10, 10, 3))
cbaoi_aug = aug.augment_polygons([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (3, 9, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(0-1, 0-2), (4-1, 0-2), (4-1, 4-2), (0-1, 4-2)]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(1-1, 1-2), (5-1, 1-2), (5-1, 5-2), (1-1, 5-2)]
)
def test_crop_polygons_by_fixed_floats_with_keep_size(self):
aug = iaa.Crop(percent=(0.2, 0, 0.5, 0.1), keep_size=True)
polygons = [ia.Polygon([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.Polygon([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.PolygonsOnImage(polygons, shape=(10, 10, 3))
cbaoi_aug = aug.augment_polygons([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (10, 10, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(10*(-1/9), 10*(-2/3)),
(10*(3/9), 10*(-2/3)),
(10*(3/9), 10*(2/3)),
(10*(-1/9), 10*(2/3))]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(10*(0/9), 10*(-1/3)),
(10*(4/9), 10*(-1/3)),
(10*(4/9), 10*(3/3)),
(10*(0/9), 10*(3/3))]
)
def test_crop_line_strings_by_fixed_floats_without_keep_size(self):
aug = iaa.Crop(percent=(0.2, 0, 0.5, 0.1), keep_size=False)
lss = [ia.LineString([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.LineString([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.LineStringsOnImage(lss, shape=(10, 10, 3))
cbaoi_aug = aug.augment_line_strings([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (3, 9, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(0-1, 0-2), (4-1, 0-2), (4-1, 4-2), (0-1, 4-2)]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(1-1, 1-2), (5-1, 1-2), (5-1, 5-2), (1-1, 5-2)]
)
def test_crop_line_strings_by_fixed_floats_with_keep_size(self):
aug = iaa.Crop(percent=(0.2, 0, 0.5, 0.1), keep_size=True)
lss = [ia.LineString([(0, 0), (4, 0), (4, 4), (0, 4)]),
ia.LineString([(1, 1), (5, 1), (5, 5), (1, 5)])]
cbaoi = ia.LineStringsOnImage(lss, shape=(10, 10, 3))
cbaoi_aug = aug.augment_line_strings([cbaoi, cbaoi])
assert len(cbaoi_aug) == 2
for cbaoi_aug_i in cbaoi_aug:
assert cbaoi_aug_i.shape == (10, 10, 3)
assert len(cbaoi_aug_i.items) == 2
assert cbaoi_aug_i.items[0].coords_almost_equals(
[(10*(-1/9), 10*(-2/3)),
(10*(3/9), 10*(-2/3)),
(10*(3/9), 10*(2/3)),
(10*(-1/9), 10*(2/3))]
)
assert cbaoi_aug_i.items[1].coords_almost_equals(
[(10*(0/9), 10*(-1/3)),
(10*(4/9), 10*(-1/3)),
(10*(4/9), 10*(3/3)),
(10*(0/9), 10*(3/3))]
)
def test_crop_bounding_boxes_by_fixed_floats_without_keep_size(self):
aug = iaa.Crop(percent=(0.2, 0, 0.5, 0.1), keep_size=False)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=4, y2=4),
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 10, 3))
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi])
assert len(bbsoi_aug) == 2
for bbsoi_aug_i in bbsoi_aug:
assert bbsoi_aug_i.shape == (3, 9, 3)
assert len(bbsoi_aug_i.bounding_boxes) == 2
assert bbsoi_aug_i.bounding_boxes[0].coords_almost_equals(
[(0-1, 0-2), (4-1, 4-2)]
)
assert bbsoi_aug_i.bounding_boxes[1].coords_almost_equals(
[(1-1, 2-2), (3-1, 4-2)]
)
def test_crop_bounding_boxes_by_fixed_floats_with_keep_size(self):
aug = iaa.Crop(percent=(0.2, 0, 0.5, 0.1), keep_size=True)
bbs = [ia.BoundingBox(x1=0, y1=0, x2=4, y2=4),
ia.BoundingBox(x1=1, y1=2, x2=3, y2=4)]
bbsoi = ia.BoundingBoxesOnImage(bbs, shape=(10, 10, 3))
bbsoi_aug = aug.augment_bounding_boxes([bbsoi, bbsoi])
assert len(bbsoi_aug) == 2
for bbsoi_aug_i in bbsoi_aug:
assert bbsoi_aug_i.shape == (10, 10, 3)
assert len(bbsoi_aug_i.bounding_boxes) == 2
assert bbsoi_aug_i.bounding_boxes[0].coords_almost_equals(
[(10*((0-1)/9), 10*((0-2)/3)),
(10*((4-1)/9), 10*((4-2)/3))]
)
assert bbsoi_aug_i.bounding_boxes[1].coords_almost_equals(
[(10*((1-1)/9), 10*((2-2)/3)),
(10*((3-1)/9), 10*((4-2)/3))]
)
def test_crop_by_tuple_of_floats_on_top_side_without_ks(self):
aug = iaa.Crop(percent=((0, 0.1), 0, 0, 0), keep_size=False)
image = np.zeros((40, 40), dtype=np.uint8)
seen = [0, 0, 0, 0, 0]
for _ in sm.xrange(500):
observed = aug.augment_image(image)
n_cropped = 40 - observed.shape[0]
seen[n_cropped] += 1
# note that we cant just check for 100-50 < x < 100+50 here. The first
# and last value (0px and 4px) have half the probability of occuring
# compared to the other values. E.g. 0px is cropped if sampled p
# falls in range [0, 0.125). 1px is cropped if sampled p falls in
# range [0.125, 0.375].
assert np.all([v > 30 for v in seen])
def test_crop_by_tuple_of_floats_on_right_side_without_ks(self):
aug = iaa.Crop(percent=(0, (0, 0.1), 0, 0), keep_size=False)
image = np.zeros((40, 40), dtype=np.uint8) + 255
seen = [0, 0, 0, 0, 0]
for _ in sm.xrange(500):
observed = aug.augment_image(image)
n_cropped = 40 - observed.shape[1]
seen[n_cropped] += 1
assert np.all([v > 30 for v in seen])
def test_crop_by_list_of_floats_on_top_side_without_ks(self):
aug = iaa.Crop(percent=([0.0, 0.1], 0, 0, 0), keep_size=False)
image = np.zeros((40, 40), dtype=np.uint8) + 255
seen = [0, 0, 0, 0, 0]
for _ in sm.xrange(500):
observed = aug.augment_image(image)
n_cropped = 40 - observed.shape[0]
seen[n_cropped] += 1
assert 250 - 50 < seen[0] < 250 + 50
assert seen[1] == 0
assert seen[2] == 0
assert seen[3] == 0
assert 250 - 50 < seen[4] < 250 + 50
def test_crop_by_list_of_floats_on_right_side_without_ks(self):
aug = iaa.Crop(percent=(0, [0.0, 0.1], 0, 0), keep_size=False)
image = np.zeros((40, 40), dtype=np.uint8) + 255
seen = [0, 0, 0, 0, 0]
for _ in sm.xrange(500):
observed = aug.augment_image(image)
n_cropped = 40 - observed.shape[1]
seen[n_cropped] += 1
assert 250 - 50 < seen[0] < 250 + 50
assert seen[1] == 0
assert seen[2] == 0
assert seen[3] == 0
assert 250 - 50 < seen[4] < 250 + 50
@classmethod
def _test_crop_empty_cba(cls, augf_name, cbaoi):
aug = iaa.Crop(px=(1, 2, 3, 4), keep_size=False)
cbaoi_aug = getattr(aug, augf_name)(cbaoi)
expected = cbaoi.deepcopy()
expected.shape = tuple(
[expected.shape[0]-1-3, expected.shape[1]-2-4]
+ list(expected.shape[2:]))
assert_cbaois_equal(cbaoi_aug, expected)
def test_pad_empty_keypoints(self):
cbaoi = ia.KeypointsOnImage([], shape=(12, 14, 3))
self._test_crop_empty_cba("augment_keypoints", cbaoi)
def test_pad_empty_polygons(self):
cbaoi = ia.PolygonsOnImage([], shape=(12, 14, 3))
self._test_crop_empty_cba("augment_polygons", cbaoi)
def test_pad_empty_line_strings(self):
cbaoi = ia.LineStringsOnImage([], shape=(12, 14, 3))
self._test_crop_empty_cba("augment_line_strings", cbaoi)
def test_pad_empty_bounding_boxes(self):
cbaoi = ia.BoundingBoxesOnImage([], shape=(12, 14, 3))
self._test_crop_empty_cba("augment_bounding_boxes", cbaoi)
def test_zero_sized_axes_no_keep_size(self):
# we also use height/width 2 here, because a height/width of 1 is
# actually not changed due to prevent_zero_size
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Crop(px=1, keep_size=False)
with warnings.catch_warnings(record=True) as caught_warnings:
image_aug = aug(image=image)
# we don't check the number of warnings here as it varies by
# shape
for warning in caught_warnings:
assert (
"crop amounts in CropAndPad"
in str(warning.message)
)
expected_height = 0 if shape[0] == 0 else 1
expected_width = 0 if shape[1] == 0 else 1
expected_shape = tuple([expected_height, expected_width]
+ list(shape[2:]))
assert image_aug.shape == expected_shape
def test_zero_sized_axes_keep_size(self):
# we also use height/width 2 here, because a height/width of 1 is
# actually not changed due to prevent_zero_size
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Crop(px=1, keep_size=True)
with warnings.catch_warnings(record=True) as caught_warnings:
image_aug = aug(image=image)
# we don't check the number of warnings here as it varies by
# shape
for warning in caught_warnings:
assert (
"crop amounts in CropAndPad"
in str(warning.message)
)
assert image_aug.shape == image.shape
def test_other_dtypes_bool(self):
aug = iaa.Crop(px=(1, 0, 0, 0), keep_size=False)
mask = np.zeros((2, 3), dtype=bool)
mask[0, 1] = True
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert image_aug.shape == (2, 3)
assert np.all(image_aug[~mask] == 0)
assert np.all(image_aug[mask] == 1)
def test_other_dtypes_uint_int(self):
aug = iaa.Crop(px=(1, 0, 0, 0), keep_size=False)
mask = np.zeros((2, 3), dtype=bool)
mask[0, 1] = True
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [
1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
values = values + [(-1) * value for value in values]
else:
values = [
1, 5, 10, 100, int(center_value), int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
for value in values:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert image_aug.shape == (2, 3)
assert np.all(image_aug[~mask] == 0)
assert np.all(image_aug[mask] == value)
def test_other_dtypes_float(self):
aug = iaa.Crop(px=(1, 0, 0, 0), keep_size=False)
mask = np.zeros((2, 3), dtype=bool)
mask[0, 1] = True
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
with self.subTest(dtype=dtype):
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == np.float16 else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [0.01, 1.0, 10.0, 100.0, 500 ** (isize - 1),
1000 ** (isize - 1)]
values = values + [(-1) * value for value in values]
values = values + [min_value, max_value]
for value in values:
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype == np.dtype(dtype)
assert image_aug.shape == (2, 3)
assert np.all(_isclose(image_aug[~mask], 0))
assert np.all(_isclose(image_aug[mask],
high_res_dt(value)))
def test_pickleable(self):
aug = iaa.Crop((0, 10), seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(30, 30, 1))
class TestPadToFixedSize(unittest.TestCase):
def setUp(self):
reseed()
def test_image2d_that_needs_to_be_padded_on_both_sides(self):
aug = iaa.PadToFixedSize(height=5, width=5)
image = np.uint8([[255]])
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 5)
def test_image3d_that_needs_to_be_padded_on_both_sides(self):
aug = iaa.PadToFixedSize(height=5, width=5)
image3d = np.atleast_3d(np.uint8([[255]]))
observed = aug.augment_image(image3d)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 5, 1)
def test_image3d_rgb_that_needs_to_be_padded_on_both_sides(self):
aug = iaa.PadToFixedSize(height=5, width=5)
image3d_rgb = np.tile(
np.atleast_3d(np.uint8([[255]])),
(1, 1, 3)
)
observed = aug.augment_image(image3d_rgb)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 5, 3)
# why does this exist when there is already a test for other float dtypes?
def test_image2d_with_other_dtypes(self):
aug = iaa.PadToFixedSize(height=5, width=5)
image = np.uint8([[255]])
for dtype in ["float32", "float64", "int32"]:
with self.subTest(dtype=dtype):
observed = aug.augment_image(image.astype(dtype))
assert observed.dtype.name == dtype
assert observed.shape == (5, 5)
def test_image_with_height_being_too_small(self):
aug = iaa.PadToFixedSize(height=5, width=5)
image = np.zeros((1, 5, 3), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 5, 3)
def test_image_with_width_being_too_small(self):
aug = iaa.PadToFixedSize(height=5, width=5)
image = np.zeros((5, 1, 3), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 5, 3)
def test_image_fullfills_exactly_min_shape(self):
# change no side when all sides have exactly desired size
aug = iaa.PadToFixedSize(height=5, width=5)
img5x5 = np.zeros((5, 5, 3), dtype=np.uint8)
img5x5[2, 2, :] = 255
observed = aug.augment_image(img5x5)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 5, 3)
assert np.array_equal(observed, img5x5)
def test_image_that_is_larger_than_min_shape(self):
# change no side when all sides have larger than desired size
aug = iaa.PadToFixedSize(height=5, width=5)
img6x6 = np.zeros((6, 6, 3), dtype=np.uint8)
img6x6[3, 3, :] = 255
observed = aug.augment_image(img6x6)
assert observed.dtype.name == "uint8"
assert observed.shape == (6, 6, 3)
assert np.array_equal(observed, img6x6)
def test_too_small_image_with_width_none(self):
aug = iaa.PadToFixedSize(height=5, width=None)
image = np.zeros((4, 4, 3), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 4, 3)
def test_too_small_image_with_height_none(self):
aug = iaa.PadToFixedSize(height=None, width=5)
image = np.zeros((4, 4, 3), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (4, 5, 3)
def test_image_pad_mode(self):
# make sure that pad mode is recognized
aug = iaa.PadToFixedSize(height=4, width=4, pad_mode="edge")
aug.position = (iap.Deterministic(0.5), iap.Deterministic(0.5))
img2x2 = np.uint8([
[50, 100],
[150, 200]
])
observed = aug.augment_image(img2x2)
expected = np.uint8([
[50, 50, 100, 100],
[50, 50, 100, 100],
[150, 150, 200, 200],
[150, 150, 200, 200]
])
assert observed.dtype.name == "uint8"
assert observed.shape == (4, 4)
assert np.array_equal(observed, expected)
def test_image_pad_at_left_top(self):
# explicit non-center position test
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="constant", pad_cval=128,
position="left-top")
img1x1 = np.uint8([[255]])
observed = aug.augment_image(img1x1)
expected = np.uint8([
[128, 128, 128],
[128, 128, 128],
[128, 128, 255]
])
assert observed.dtype.name == "uint8"
assert observed.shape == (3, 3)
assert np.array_equal(observed, expected)
def test_image_pad_at_right_bottom(self):
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="constant", pad_cval=128,
position="right-bottom")
img1x1 = np.uint8([[255]])
observed = aug.augment_image(img1x1)
expected = np.uint8([
[255, 128, 128],
[128, 128, 128],
[128, 128, 128]
])
assert observed.dtype.name == "uint8"
assert observed.shape == (3, 3)
assert np.array_equal(observed, expected)
def test_image_pad_at_bottom_center_given_as_tuple_of_floats(self):
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="constant", pad_cval=128,
position=(0.5, 1.0))
img1x1 = np.uint8([[255]])
observed = aug.augment_image(img1x1)
expected = np.uint8([
[128, 255, 128],
[128, 128, 128],
[128, 128, 128]
])
assert observed.dtype.name == "uint8"
assert observed.shape == (3, 3)
assert np.array_equal(observed, expected)
def test_keypoints__image_already_fullfills_min_shape(self):
# keypoint test with shape not being changed
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="edge", position="center")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_keypoints_pad_at_center(self):
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="center")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
observed = aug.augment_keypoints(kpsoi)
# padding happens at right/bottom, so KP doesn't move
expected = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_keypoints_pad_at_center__2px(self):
aug = iaa.PadToFixedSize(
height=5, width=5, pad_mode="edge", position="center")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([ia.Keypoint(x=2, y=2)], shape=(5, 5))
assert_cbaois_equal(observed, expected)
def test_keypoints_pad_at_left_top(self):
# keypoint test with explicit non-center position
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="left-top")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([ia.Keypoint(x=2, y=2)], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_keypoints_pad_at_right_bottom(self):
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="right-bottom")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_keypoints_empty(self):
aug = iaa.PadToFixedSize(height=5, width=6)
kpsoi = ia.KeypointsOnImage([], shape=(3, 3))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([], shape=(5, 6))
assert_cbaois_equal(observed, expected)
def test_polygons__image_already_fullfills_min_shape(self):
# polygons test with shape not being changed
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="edge", position="center")
psoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_polygons_pad_at_center(self):
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="center")
psoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_polygons(psoi)
# padding happens at right/bottom, so poly doesn't move
expected = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_polygons_pad_at_center__2px(self):
aug = iaa.PadToFixedSize(
height=5, width=5, pad_mode="edge", position="center")
psoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_polygons(psoi)
# padding happens at right/bottom, so poly doesn't move
expected = ia.PolygonsOnImage([
ia.Polygon([(0+1, 0+1), (3+1, 0+1), (3+1, 3+1)])
], shape=(5, 5))
assert_cbaois_equal(observed, expected)
def test_polygons_pad_at_left_top(self):
# polygon test with explicit non-center position
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="left-top")
psoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([
ia.Polygon([(1+0, 1+0), (1+3, 1+0), (1+3, 1+3)])
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_polygons_pad_at_right_bottom(self):
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="right-bottom")
psoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_polygons_empty(self):
aug = iaa.PadToFixedSize(height=5, width=6)
psoi = ia.PolygonsOnImage([], shape=(3, 3))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([], shape=(5, 6))
assert_cbaois_equal(observed, expected)
def test_line_strings__image_already_fullfills_min_shape(self):
# line string test with shape not being changed
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="edge", position="center")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_line_strings_pad_at_center(self):
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="center")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_line_strings(cbaoi)
# padding happens at right/bottom, so LS doesn't move
expected = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_line_strings_pad_at_center__2px(self):
aug = iaa.PadToFixedSize(
height=5, width=5, pad_mode="edge", position="center")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([
ia.LineString([(0+1, 0+1), (3+1, 0+1), (3+1, 3+1)])
], shape=(5, 5))
assert_cbaois_equal(observed, expected)
def test_line_strings_pad_at_left_top(self):
# line string test with explicit non-center position
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="left-top")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([
ia.LineString([(1+0, 1+0), (1+3, 1+0), (1+3, 1+3)])
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_line_strings_pad_at_right_bottom(self):
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="right-bottom")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(3, 3))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_line_strings_empty(self):
aug = iaa.PadToFixedSize(height=5, width=6)
cbaoi = ia.LineStringsOnImage([], shape=(3, 3))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([], shape=(5, 6))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes__image_already_fullfills_min_shape(self):
# bounding boxes test with shape not being changed
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="edge", position="center")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3),
], shape=(3, 3))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3),
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_pad_at_center(self):
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="center")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3),
], shape=(3, 3))
observed = aug.augment_bounding_boxes(bbsoi)
# aug adds a columns at the right and row at the bottom,
# i.e. BB is not affected
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3),
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_pad_at_center__2px(self):
aug = iaa.PadToFixedSize(
height=5, width=5, pad_mode="edge", position="center")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3),
], shape=(3, 3))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0+1, y1=1+1, x2=2+1, y2=3+1),
], shape=(5, 5))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_pad_at_left_top(self):
# bounding boxes test with explicit non-center position
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="left-top")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3),
], shape=(3, 3))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0+1, y1=1+1, x2=2+1, y2=3+1),
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_pad_at_right_bottom(self):
aug = iaa.PadToFixedSize(
height=4, width=4, pad_mode="edge", position="right-bottom")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3),
], shape=(3, 3))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3),
], shape=(4, 4))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_empty(self):
aug = iaa.PadToFixedSize(height=5, width=6)
bbsoi = ia.BoundingBoxesOnImage([], shape=(3, 3))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([], shape=(5, 6))
assert_cbaois_equal(observed, expected)
def test_heatmaps__pad_mode_should_be_ignored(self):
# basic heatmaps test
# pad_mode should be ignored for heatmaps
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="edge", position="center")
heatmaps_arr = np.zeros((1, 1, 1), dtype=np.float32) + 1.0
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(1, 1, 3))
observed = aug.augment_heatmaps([heatmaps])[0]
expected = np.float32([
[0, 0, 0],
[0, 1.0, 0],
[0, 0, 0]
])
expected = expected[..., np.newaxis]
assert observed.shape == (3, 3, 3)
assert np.allclose(observed.arr_0to1, expected)
def test_heatmaps_smaller_than_image__pad_mode_should_be_ignored(self):
# heatmaps with size unequal to image
# pad_mode should be ignored for heatmaps
aug = iaa.PadToFixedSize(
height=32, width=32, pad_mode="edge", position="left-top")
heatmaps_arr = np.zeros((15, 15, 1), dtype=np.float32) + 1.0
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(30, 30, 3))
observed = aug.augment_heatmaps([heatmaps])[0]
expected = np.zeros((16, 16, 1), dtype=np.float32) + 1.0
expected[:, 0, 0] = 0.0
expected[0, :, 0] = 0.0
assert observed.shape == (32, 32, 3)
assert np.allclose(observed.arr_0to1, expected)
def test_segmaps__pad_mode_should_be_ignored(self):
# basic segmaps test
# pad_mode should be ignored for segmaps
aug = iaa.PadToFixedSize(
height=3, width=3, pad_mode="edge", position="center")
segmaps_arr = np.ones((1, 1, 1), dtype=np.int32)
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(1, 1, 3))
observed = aug.augment_segmentation_maps([segmaps])[0]
expected = np.int32([
[0, 0, 0],
[0, 1, 0],
[0, 0, 0]
])
expected = expected[..., np.newaxis]
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.arr, expected)
def test_segmaps_smaller_than_image__pad_mode_should_be_ignored(self):
# segmaps with size unequal to image
# pad_mode should be ignored for segmaps
aug = iaa.PadToFixedSize(
height=32, width=32, pad_mode="edge", position="left-top")
segmaps_arr = np.ones((15, 15, 1), dtype=np.int32)
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(30, 30, 3))
observed = aug.augment_segmentation_maps([segmaps])[0]
expected = np.ones((16, 16, 1), dtype=np.int32)
expected[:, 0, 0] = 0
expected[0, :, 0] = 0
assert observed.shape == (32, 32, 3)
assert np.array_equal(observed.arr, expected)
def test_get_parameters(self):
aug = iaa.PadToFixedSize(width=20, height=10, pad_mode="edge",
pad_cval=10, position="center")
params = aug.get_parameters()
assert params[0] == 20
assert params[1] == 10
assert params[2].value == "edge"
assert params[3].value == 10
assert np.isclose(params[4][0].value, 0.5)
assert np.isclose(params[4][1].value, 0.5)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PadToFixedSize(height=1, width=1)
image_aug = aug(image=image)
expected_height = 1
expected_width = 1
expected_shape = tuple([expected_height, expected_width]
+ list(shape[2:]))
assert image_aug.shape == expected_shape
def test_other_dtypes_bool(self):
aug = iaa.PadToFixedSize(height=4, width=3, position="center-top")
mask = np.zeros((4, 3), dtype=bool)
mask[2, 1] = True
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert image_aug.shape == (4, 3)
assert np.all(image_aug[~mask] == 0)
assert np.all(image_aug[mask] == 1)
def test_other_dtypes_uint_int(self):
aug = iaa.PadToFixedSize(height=4, width=3, position="center-top")
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
mask = np.zeros((4, 3), dtype=bool)
mask[2, 1] = True
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [
1, 5, 10, 100, int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
values = values + [(-1) * value for value in values]
else:
values = [
1, 5, 10, 100, int(center_value), int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert image_aug.shape == (4, 3)
assert np.all(image_aug[~mask] == 0)
assert np.all(image_aug[mask] == value)
def test_other_dtypes_float(self):
aug = iaa.PadToFixedSize(height=4, width=3, position="center-top")
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
mask = np.zeros((4, 3), dtype=bool)
mask[2, 1] = True
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [0.01, 1.0, 10.0, 100.0, 500 ** (isize - 1),
1000 ** (isize - 1)]
values = values + [(-1) * value for value in values]
values = values + [min_value, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert image_aug.shape == (4, 3)
assert np.all(_isclose(image_aug[~mask], 0))
assert np.all(_isclose(image_aug[mask],
high_res_dt(value)))
def test_pickleable(self):
aug = iaa.PadToFixedSize(20, 20, position="uniform", seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(10, 10, 1))
class TestCenterPadToFixedSize(unittest.TestCase):
def setUp(self):
reseed()
def test_image2d(self):
for _ in np.arange(10):
image = np.arange(4*4*3).astype(np.uint8).reshape((4, 4, 3))
aug = iaa.CenterPadToFixedSize(height=5, width=5)
observed = aug(image=image)
expected = iaa.pad(image, right=1, bottom=1)
assert np.array_equal(observed, expected)
def test_pickleable(self):
aug = iaa.CenterPadToFixedSize(height=20, width=15)
runtest_pickleable_uint8_img(aug, shape=(10, 10, 3))
class TestCropToFixedSize(unittest.TestCase):
def setUp(self):
reseed()
def test_image2d_that_needs_to_be_cropped_on_both_sides(self):
aug = iaa.CropToFixedSize(height=1, width=1)
image = np.uint8([
[128, 129, 130],
[131, 132, 133],
[134, 135, 136]
])
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (1, 1)
def test_image3d_that_needs_to_be_cropped_on_both_sides(self):
aug = iaa.CropToFixedSize(height=1, width=1)
image = np.uint8([
[128, 129, 130],
[131, 132, 133],
[134, 135, 136]
])
image3d = np.atleast_3d(image)
observed = aug.augment_image(image3d)
assert observed.dtype.name == "uint8"
assert observed.shape == (1, 1, 1)
def test_image3d_rgb_that_needs_to_be_cropped_on_both_sides(self):
aug = iaa.CropToFixedSize(height=1, width=1)
image = np.uint8([
[128, 129, 130],
[131, 132, 133],
[134, 135, 136]
])
image3d_rgb = np.tile(
np.atleast_3d(image),
(1, 1, 3)
)
observed = aug.augment_image(image3d_rgb)
assert observed.dtype.name == "uint8"
assert observed.shape == (1, 1, 3)
def test_image2d_with_other_dtypes(self):
aug = iaa.CropToFixedSize(height=1, width=1)
image = np.uint8([
[128, 129, 130],
[131, 132, 133],
[134, 135, 136]
])
for dtype in ["float32", "float64", "int32"]:
with self.subTest(dtype=dtype):
observed = aug.augment_image(image.astype(dtype))
assert observed.dtype.name == dtype
assert observed.shape == (1, 1)
def test_image_with_height_being_too_large(self):
# change only one side when other side has already desired size
aug = iaa.CropToFixedSize(height=1, width=5)
image = np.zeros((3, 5, 3), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (1, 5, 3)
def test_image_with_width_being_too_large(self):
aug = iaa.CropToFixedSize(height=5, width=1)
image = np.zeros((5, 3, 3), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 1, 3)
def test_image_fullfills_exactly_max_shape(self):
# change no side when all sides have exactly desired size
aug = iaa.CropToFixedSize(height=5, width=5)
img5x5 = np.zeros((5, 5, 3), dtype=np.uint8)
img5x5[2, 2, :] = 255
observed = aug.augment_image(img5x5)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 5, 3)
assert np.array_equal(observed, img5x5)
def test_image_that_is_smaller_than_max_shape(self):
# change no side when all sides have smaller than desired size
aug = iaa.CropToFixedSize(height=5, width=5)
img4x4 = np.zeros((4, 4, 3), dtype=np.uint8)
img4x4[2, 2, :] = 255
observed = aug.augment_image(img4x4)
assert observed.dtype.name == "uint8"
assert observed.shape == (4, 4, 3)
assert np.array_equal(observed, img4x4)
def test_too_large_image_with_width_none(self):
aug = iaa.CropToFixedSize(height=5, width=None)
image = np.zeros((6, 6, 3), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (5, 6, 3)
def test_too_large_image_with_height_none(self):
aug = iaa.CropToFixedSize(height=None, width=5)
image = np.zeros((6, 6, 3), dtype=np.uint8)
observed = aug.augment_image(image)
assert observed.dtype.name == "uint8"
assert observed.shape == (6, 5, 3)
def test_image_crop_at_left_top(self):
# explicit non-center position test
aug = iaa.CropToFixedSize(height=3, width=3, position="left-top")
img5x5 = np.arange(25, dtype=np.uint8).reshape((5, 5))
observed = aug.augment_image(img5x5)
expected = img5x5[2:, 2:]
assert observed.dtype.name == "uint8"
assert observed.shape == (3, 3)
assert np.array_equal(observed, expected)
def test_image_crop_at_right_bottom(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="right-bottom")
img5x5 = np.arange(25, dtype=np.uint8).reshape((5, 5))
observed = aug.augment_image(img5x5)
expected = img5x5[:3, :3]
assert observed.dtype.name == "uint8"
assert observed.shape == (3, 3)
assert np.array_equal(observed, expected)
def test_image_crop_at_bottom_center_given_as_tuple_of_floats(self):
aug = iaa.CropToFixedSize(height=3, width=3, position=(0.5, 1.0))
img5x5 = np.arange(25, dtype=np.uint8).reshape((5, 5))
observed = aug.augment_image(img5x5)
expected = img5x5[:3, 1:4]
assert observed.dtype.name == "uint8"
assert observed.shape == (3, 3)
assert np.array_equal(observed, expected)
def test_keypoints__image_already_fullfills_max_shape(self):
# keypoint test with shape not being changed
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_keypoints_crop_at_center(self):
# basic keypoint test
aug = iaa.CropToFixedSize(height=1, width=1, position="center")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=1, y=1)], shape=(3, 3))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(1, 1))
assert_cbaois_equal(observed, expected)
def test_keypoints_crop_at_left_top(self):
# keypoint test with explicit non-center position
aug = iaa.CropToFixedSize(height=3, width=3, position="left-top")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=2, y=2)], shape=(5, 5))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([ia.Keypoint(x=0, y=0)], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_keypoints_crop_at_right_bottom(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="right-bottom")
kpsoi = ia.KeypointsOnImage([ia.Keypoint(x=2, y=2)], shape=(5, 5))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([ia.Keypoint(x=2, y=2)], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_keypoints_empty(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
kpsoi = ia.KeypointsOnImage([], shape=(5, 4))
observed = aug.augment_keypoints(kpsoi)
expected = ia.KeypointsOnImage([], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_polygons__image_already_fullfills_max_shape(self):
# polygons test with shape not being changed
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
psoi = ia.PolygonsOnImage([
ia.Polygon([(1, 1), (3, 1), (3, 3)])
], shape=(3, 3))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([
ia.Polygon([(1, 1), (3, 1), (3, 3)])
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_polygons_crop_at_center(self):
# basic polygons test
aug = iaa.CropToFixedSize(height=1, width=1, position="center")
psoi = ia.PolygonsOnImage([
ia.Polygon([(1, 1), (3, 1), (3, 3)])
], shape=(3, 3))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([
ia.Polygon([(1-1, 1-1), (3-1, 1-1), (3-1, 3-1)])
], shape=(1, 1))
assert_cbaois_equal(observed, expected)
def test_polygons_crop_at_left_top(self):
# polygons test with explicit non-center position
aug = iaa.CropToFixedSize(height=3, width=3, position="left-top")
psoi = ia.PolygonsOnImage([
ia.Polygon([(1, 1), (3, 1), (3, 3)])
], shape=(5, 5))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([
ia.Polygon([(1-2, 1-2), (3-2, 1-2), (3-2, 3-2)])
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_polygons_crop_at_right_bottom(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="right-bottom")
psoi = ia.PolygonsOnImage([
ia.Polygon([(1, 1), (3, 1), (3, 3)])
], shape=(5, 5))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([
ia.Polygon([(1, 1), (3, 1), (3, 3)])
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_polygons_empty(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
psoi = ia.PolygonsOnImage([], shape=(5, 4))
observed = aug.augment_polygons(psoi)
expected = ia.PolygonsOnImage([], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_line_strings__image_already_fullfills_max_shape(self):
# line strings test with shape not being changed
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(1, 1), (3, 1), (3, 3)])
], shape=(3, 3))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([
ia.LineString([(1, 1), (3, 1), (3, 3)])
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_line_strings_crop_at_center(self):
# basic line strings test
aug = iaa.CropToFixedSize(height=1, width=1, position="center")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(1, 1), (3, 1), (3, 3)])
], shape=(3, 3))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([
ia.LineString([(1-1, 1-1), (3-1, 1-1), (3-1, 3-1)])
], shape=(1, 1))
assert_cbaois_equal(observed, expected)
def test_line_strings_crop_at_left_top(self):
# polygons test with explicit non-center position
aug = iaa.CropToFixedSize(height=3, width=3, position="left-top")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(1, 1), (3, 1), (3, 3)])
], shape=(5, 5))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([
ia.LineString([(1-2, 1-2), (3-2, 1-2), (3-2, 3-2)])
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_line_strings_crop_at_right_bottom(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="right-bottom")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(1, 1), (3, 1), (3, 3)])
], shape=(5, 5))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([
ia.LineString([(1, 1), (3, 1), (3, 3)])
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_line_strings_empty(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
cbaoi = ia.LineStringsOnImage([], shape=(5, 4))
observed = aug.augment_line_strings(cbaoi)
expected = ia.LineStringsOnImage([], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes__image_already_fullfills_max_shape(self):
# bounding boxes test with shape not being changed
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
], shape=(3, 3))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_crop_at_center(self):
# basic bounding boxes test
aug = iaa.CropToFixedSize(height=1, width=1, position="center")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
], shape=(3, 3))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0-1, y1=1-1, x2=2-1, y2=3-1)
], shape=(1, 1))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_crop_at_left_top(self):
# bounding boxes test with explicit non-center position
aug = iaa.CropToFixedSize(height=3, width=3, position="left-top")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
], shape=(5, 5))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0-2, y1=1-2, x2=2-2, y2=3-2)
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_crop_at_right_bottom(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="right-bottom")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
], shape=(5, 5))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=2, y2=3)
], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_bounding_boxes_empty(self):
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
bbsoi = ia.BoundingBoxesOnImage([], shape=(5, 4))
observed = aug.augment_bounding_boxes(bbsoi)
expected = ia.BoundingBoxesOnImage([], shape=(3, 3))
assert_cbaois_equal(observed, expected)
def test_heatmaps(self):
# basic heatmaps test
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
heatmaps_arr = np.zeros((5, 5, 1), dtype=np.float32) + 1.0
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(5, 5, 3))
observed = aug.augment_heatmaps([heatmaps])[0]
expected = np.zeros((3, 3, 1), dtype=np.float32) + 1.0
assert observed.shape == (3, 3, 3)
assert np.allclose(observed.arr_0to1, expected)
def test_heatmaps_crop_at_left_top(self):
# heatmaps, crop at non-center position
aug = iaa.CropToFixedSize(height=3, width=3, position="left-top")
heatmaps_arr = np.linspace(
0.0, 1.0, 5 * 5 * 1).reshape((5, 5, 1)).astype(np.float32)
heatmaps_oi = ia.HeatmapsOnImage(heatmaps_arr, shape=(5, 5, 3))
observed = aug.augment_heatmaps([heatmaps_oi])[0]
expected = heatmaps_arr[2:, 2:, :]
assert observed.shape == (3, 3, 3)
assert np.allclose(observed.arr_0to1, expected)
def test_heatmaps_crop_at_right_bottom(self):
# heatmaps, crop at non-center position
aug = iaa.CropToFixedSize(height=3, width=3, position="right-bottom")
heatmaps_arr = np.linspace(
0.0, 1.0, 5 * 5 * 1).reshape((5, 5, 1)).astype(np.float32)
heatmaps_oi = ia.HeatmapsOnImage(heatmaps_arr, shape=(5, 5, 3))
observed = aug.augment_heatmaps([heatmaps_oi])[0]
expected = heatmaps_arr[:3, :3, :]
assert observed.shape == (3, 3, 3)
assert np.allclose(observed.arr_0to1, expected)
def test_heatmaps_smaller_than_image(self):
# heatmaps with size unequal to image
aug = iaa.CropToFixedSize(height=32, width=32, position="left-top")
heatmaps_arr = np.zeros((17, 17, 1), dtype=np.float32) + 1.0
heatmaps = ia.HeatmapsOnImage(heatmaps_arr, shape=(34, 34, 3))
observed = aug.augment_heatmaps([heatmaps])[0]
expected = np.zeros((16, 16, 1), dtype=np.float32) + 1.0
assert observed.shape == (32, 32, 3)
assert np.allclose(observed.arr_0to1, expected)
def test_segmaps_crop_at_center(self):
# basic segmaps test
aug = iaa.CropToFixedSize(height=3, width=3, position="center")
segmaps_arr = np.ones((5, 5, 1), dtype=np.int32)
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(5, 5, 3))
observed = aug.augment_segmentation_maps([segmaps])[0]
expected = np.ones((3, 3, 1), dtype=np.int32)
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.arr, expected)
def test_segmaps_crop_at_left_top(self):
# segmaps, crop at non-center position
aug = iaa.CropToFixedSize(height=3, width=3, position="left-top")
segmaps_arr = np.arange(5*5).reshape((5, 5, 1)).astype(np.int32)
segmaps_oi = SegmentationMapsOnImage(segmaps_arr, shape=(5, 5, 3))
observed = aug.augment_segmentation_maps([segmaps_oi])[0]
expected = segmaps_arr[2:, 2:, :]
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.arr, expected)
def test_segmaps_crop_at_right_bottom(self):
# segmaps, crop at non-center position
aug = iaa.CropToFixedSize(height=3, width=3, position="right-bottom")
segmaps_arr = np.arange(5*5).reshape((5, 5, 1)).astype(np.int32)
segmaps_oi = SegmentationMapsOnImage(segmaps_arr, shape=(5, 5, 3))
observed = aug.augment_segmentation_maps([segmaps_oi])[0]
expected = segmaps_arr[:3, :3, :]
assert observed.shape == (3, 3, 3)
assert np.array_equal(observed.arr, expected)
def test_segmaps_smaller_than_image(self):
# segmaps with size unequal to image
aug = iaa.CropToFixedSize(height=32, width=32, position="left-top")
segmaps_arr = np.ones((17, 17, 1), dtype=np.int32)
segmaps = SegmentationMapsOnImage(segmaps_arr, shape=(34, 34, 3))
observed = aug.augment_segmentation_maps([segmaps])[0]
expected = np.ones((16, 16, 1), dtype=np.int32)
assert observed.shape == (32, 32, 3)
assert np.array_equal(observed.arr, expected)
def test_get_parameters(self):
aug = iaa.CropToFixedSize(width=20, height=10, position="center")
params = aug.get_parameters()
assert params[0] == 20
assert params[1] == 10
assert np.isclose(params[2][0].value, 0.5)
assert np.isclose(params[2][1].value, 0.5)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.CropToFixedSize(height=1, width=1)
image_aug = aug(image=image)
expected_height = 0 if shape[0] == 0 else 1
expected_width = 0 if shape[1] == 0 else 1
expected_shape = tuple([expected_height, expected_width]
+ list(shape[2:]))
assert image_aug.shape == expected_shape
def test_other_dtypes_bool(self):
aug = iaa.CropToFixedSize(height=2, width=3, position="center-top")
mask = np.zeros((2, 3), dtype=bool)
mask[0, 1] = True
image = np.zeros((3, 3), dtype=bool)
image[1, 1] = True
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == image.dtype.name
assert image_aug.shape == (2, 3)
assert np.all(image_aug[~mask] == 0)
assert np.all(image_aug[mask] == 1)
def test_other_dtypes_uint_int(self):
aug = iaa.CropToFixedSize(height=2, width=3, position="center-top")
mask = np.zeros((2, 3), dtype=bool)
mask[0, 1] = True
dtypes = ["uint8", "uint16", "uint32", "uint64",
"int8", "int16", "int32", "int64"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
if np.dtype(dtype).kind == "i":
values = [
1, 5, 10, 100, int(0.1 * max_value), int(0.2 * max_value),
int(0.5 * max_value), max_value - 100, max_value]
values = values + [(-1) * value for value in values]
else:
values = [
1, 5, 10, 100, int(center_value), int(0.1 * max_value),
int(0.2 * max_value), int(0.5 * max_value),
max_value - 100, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert image_aug.shape == (2, 3)
assert np.all(image_aug[~mask] == 0)
assert np.all(image_aug[mask] == value)
def test_other_dtypes_float(self):
aug = iaa.CropToFixedSize(height=2, width=3, position="center-top")
mask = np.zeros((2, 3), dtype=bool)
mask[0, 1] = True
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
min_value, center_value, max_value = \
iadt.get_value_range_of_dtype(dtype)
def _isclose(a, b):
atol = 1e-4 if dtype == "float16" else 1e-8
return np.isclose(a, b, atol=atol, rtol=0)
isize = np.dtype(dtype).itemsize
values = [0.01, 1.0, 10.0, 100.0, 500 ** (isize - 1),
1000 ** (isize - 1)]
values = values + [(-1) * value for value in values]
values = values + [min_value, max_value]
for value in values:
with self.subTest(dtype=dtype, value=value):
image = np.zeros((3, 3), dtype=dtype)
image[1, 1] = value
image_aug = aug.augment_image(image)
assert image_aug.dtype.name == dtype
assert image_aug.shape == (2, 3)
assert np.all(_isclose(image_aug[~mask], 0))
assert np.all(_isclose(image_aug[mask],
high_res_dt(value)))
def test_pickleable(self):
aug = iaa.CropToFixedSize(10, 10, position="uniform", seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(20, 20, 1))
class TestCenterCropToFixedSize(unittest.TestCase):
def setUp(self):
reseed()
def test_on_single_image(self):
for _ in np.arange(10):
image = np.arange(11*11*2).astype(np.uint8).reshape((11, 11, 2))
aug = iaa.CenterCropToFixedSize(width=3, height=3)
observed = aug(image=image)
assert np.array_equal(observed, image[5-1:5+2, 5-1:5+2, :])
def test_pickleable(self):
aug = iaa.CenterCropToFixedSize(height=12, width=10)
runtest_pickleable_uint8_img(aug, shape=(15, 20, 3))
class TestCropToMultiplesOf(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.CropToMultiplesOf(width_multiple=1, height_multiple=2,
position="center")
assert aug.width_multiple == 1
assert aug.height_multiple == 2
assert np.isclose(aug.position[0].value, 0.5)
assert np.isclose(aug.position[1].value, 0.5)
def test_multiples_are_1(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToMultiplesOf(1, 1, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_on_3x3_image__no_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToMultiplesOf(3, 3, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_on_3x3_image__with_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToMultiplesOf(2, 2, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:2, 0:2, :])
def test_on_3x3_image__only_width_changed(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToMultiplesOf(height_multiple=3, width_multiple=2,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:3, 0:2, :])
def test_on_3x3_image__only_height_changed(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToMultiplesOf(height_multiple=2, width_multiple=3,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:2, 0:3, :])
def test_on_3x4_image(self):
image = np.arange((3*4*3)).astype(np.uint8).reshape((3, 4, 3))
aug = iaa.CropToMultiplesOf(2, 2, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:2, 0:4, :])
def test_on_7x9_image(self):
image = np.arange((7*9*3)).astype(np.uint8).reshape((7, 9, 3))
aug = iaa.CropToMultiplesOf(height_multiple=5, width_multiple=6,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[1:6, 1:7, :])
def test_width_multiple_is_none(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToMultiplesOf(height_multiple=2, width_multiple=None,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:2, 0:3, :])
def test_height_multiple_is_none(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToMultiplesOf(height_multiple=None, width_multiple=2,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:3, 0:2, :])
def test_heatmaps(self):
# segmaps are implemented in the same way in CropToFixesSize
# and already tested there, so there is no need to test them again here
arr = np.linspace(0, 1.0, 50*50).astype(np.float32).reshape((50, 50, 1))
heatmap = ia.HeatmapsOnImage(arr, shape=(99, 99, 3))
aug = iaa.CropToMultiplesOf(height_multiple=50, width_multiple=50,
position="center")
observed = aug(heatmaps=heatmap)
assert observed.shape == (50, 50, 3)
assert np.allclose(observed.arr_0to1,
heatmap.arr_0to1[12:-13, 12:-13, :])
def test_keypoints(self):
kps = [ia.Keypoint(x=2, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(8, 4, 3))
aug = iaa.CropToMultiplesOf(height_multiple=5, width_multiple=2,
position="center")
observed = aug(keypoints=kpsoi)
assert observed.keypoints[0].x == 2
assert observed.keypoints[0].y == 2
def test_get_parameters(self):
aug = iaa.CropToMultiplesOf(width_multiple=1, height_multiple=2,
position="center")
params = aug.get_parameters()
assert params[0] == 1
assert params[1] == 2
assert np.isclose(params[2][0].value, 0.5)
assert np.isclose(params[2][1].value, 0.5)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.CropToMultiplesOf(2, 2)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_pickleable(self):
aug = iaa.CropToMultiplesOf(5, 5, position="uniform", seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(14, 14, 1))
class TestCenterCropToMultiplesOf(unittest.TestCase):
def setUp(self):
reseed()
def test_on_3x4_image(self):
for _ in np.arange(10):
image = np.mod(np.arange((17*14*3)), 255)
image = image.astype(np.uint8).reshape((17, 14, 3))
aug = iaa.CenterCropToMultiplesOf(height_multiple=5,
width_multiple=5)
observed = aug(image=image)
assert np.array_equal(observed, image[1:-1, 2:-2, :])
def test_pickleable(self):
aug = iaa.CenterCropToMultiplesOf(5, 5)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(14, 14, 1))
class TestPadToMultiplesOf(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.PadToMultiplesOf(width_multiple=1, height_multiple=2,
position="center")
assert aug.width_multiple == 1
assert aug.height_multiple == 2
assert np.isclose(aug.position[0].value, 0.5)
assert np.isclose(aug.position[1].value, 0.5)
def test_multiples_are_1(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToMultiplesOf(1, 1, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_on_3x3_image__no_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToMultiplesOf(3, 3, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_on_3x3_image__with_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToMultiplesOf(2, 2, position="center")
observed = aug(image=image)
expected = iaa.pad(image, bottom=1, right=1)
assert np.array_equal(observed, expected)
def test_on_3x3_image__only_width_changed(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToMultiplesOf(height_multiple=3, width_multiple=2,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, right=1)
assert np.array_equal(observed, expected)
def test_on_3x3_image__only_height_changed(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToMultiplesOf(height_multiple=2, width_multiple=3,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, bottom=1)
assert np.array_equal(observed, expected)
def test_on_3x4_image(self):
image = np.arange((3*4*3)).astype(np.uint8).reshape((3, 4, 3))
aug = iaa.PadToMultiplesOf(2, 2, position="center")
observed = aug(image=image)
expected = iaa.pad(image, bottom=1)
assert np.array_equal(observed, expected)
def test_on_7x9_image(self):
image = np.arange((7*9*3)).astype(np.uint8).reshape((7, 9, 3))
aug = iaa.PadToMultiplesOf(height_multiple=5, width_multiple=6,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=1, bottom=2, left=1, right=2)
assert np.array_equal(observed, expected)
def test_on_7x9_image__cval(self):
image = np.arange((7*9*3)).astype(np.uint8).reshape((7, 9, 3))
aug = iaa.PadToMultiplesOf(height_multiple=5, width_multiple=6,
pad_cval=100,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=1, bottom=2, left=1, right=2, cval=100)
assert np.array_equal(observed, expected)
def test_on_7x9_image__mode(self):
image = np.arange((7*9*3)).astype(np.uint8).reshape((7, 9, 3))
aug = iaa.PadToMultiplesOf(height_multiple=5, width_multiple=6,
pad_mode="edge",
position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=1, bottom=2, left=1, right=2, mode="edge")
assert np.array_equal(observed, expected)
def test_width_multiple_is_none(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToMultiplesOf(height_multiple=2, width_multiple=None,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, bottom=1)
assert np.array_equal(observed, expected)
def test_height_multiple_is_none(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToMultiplesOf(height_multiple=None, width_multiple=2,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, right=1)
assert np.array_equal(observed, expected)
def test_heatmaps(self):
# segmaps are implemented in the same way in PadToFixesSize
# and already tested there, so there is no need to test them again here
arr = np.linspace(0, 1.0, 51*51).astype(np.float32).reshape((51, 51, 1))
heatmap = ia.HeatmapsOnImage(arr, shape=(101, 101, 3))
aug = iaa.PadToMultiplesOf(height_multiple=100, width_multiple=100,
position="center")
observed = aug(heatmaps=heatmap)
expected = heatmap.pad(top=25, bottom=25, left=25, right=25)
assert observed.shape == (200, 200, 3)
assert np.allclose(observed.arr_0to1, expected.arr_0to1)
def test_keypoints(self):
kps = [ia.Keypoint(x=2, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(8, 4, 3))
aug = iaa.PadToMultiplesOf(height_multiple=5, width_multiple=2,
position="center")
observed = aug(keypoints=kpsoi)
assert observed.keypoints[0].x == 2
assert observed.keypoints[0].y == 4
def test_get_parameters(self):
aug = iaa.PadToMultiplesOf(width_multiple=1, height_multiple=2,
pad_cval=5, pad_mode="edge",
position="center")
params = aug.get_parameters()
assert params[0] == 1
assert params[1] == 2
assert params[2].value == "edge"
assert params[3].value == 5
assert np.isclose(params[4][0].value, 0.5)
assert np.isclose(params[4][1].value, 0.5)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PadToMultiplesOf(2, 2)
image_aug = aug(image=image)
expected_height = 2
expected_width = 2
expected_shape = tuple([expected_height, expected_width]
+ list(shape[2:]))
assert image_aug.shape == expected_shape
def test_pickleable(self):
aug = iaa.PadToMultiplesOf(5, 5, position="uniform", seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(11, 11, 1))
class TestCenterPadToMultiplesOf(unittest.TestCase):
def setUp(self):
reseed()
def test_on_3x4_image(self):
for _ in np.arange(10):
image = np.arange((3*6*3)).astype(np.uint8).reshape((3, 6, 3))
aug = iaa.CenterPadToMultiplesOf(5, 5)
observed = aug(image=image)
expected = iaa.pad(image, top=1, right=2, bottom=1, left=2)
assert np.array_equal(observed, expected)
def test_pickleable(self):
aug = iaa.CenterPadToMultiplesOf(5, 5)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(11, 11, 1))
class TestCropToPowersOf(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.CropToPowersOf(width_base=2, height_base=3,
position="center")
assert aug.width_base == 2
assert aug.height_base == 3
assert np.isclose(aug.position[0].value, 0.5)
assert np.isclose(aug.position[1].value, 0.5)
def test_on_3x3_image__no_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToPowersOf(3, 3, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_on_3x3_image__with_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToPowersOf(2, 2, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:2, 0:2, :])
def test_on_3x3_image__only_width_changed(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToPowersOf(height_base=3, width_base=2,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:3, 0:2, :])
def test_on_3x3_image__only_height_changed(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToPowersOf(height_base=2, width_base=3,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:2, 0:3, :])
def test_on_3x4_image(self):
image = np.arange((3*4*3)).astype(np.uint8).reshape((3, 4, 3))
aug = iaa.CropToPowersOf(2, 2, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:2, 0:4, :])
def test_on_17x26_image(self):
image = np.mod(
np.arange((17*26*3)),
255
).astype(np.uint8).reshape((17, 26, 3))
aug = iaa.CropToPowersOf(height_base=2, width_base=3,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:16, 8:17, :])
def test_does_not_crop_towards_exponent_of_zero(self):
# Test for: axis_size < B,
# this should not lead to crops that result in exponent of B^0=1,
# i.e. the respective axes should simply not be changed.
image = np.arange((3*4*3)).astype(np.uint8).reshape((3, 4, 3))
aug = iaa.CropToPowersOf(10, 10, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_width_base_is_none(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToPowersOf(height_base=2, width_base=None,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:2, 0:3, :])
def test_height_base_is_none(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CropToPowersOf(height_base=None, width_base=2,
position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:3, 0:2, :])
def test_heatmaps(self):
# segmaps are implemented in the same way in CropToFixesSize
# and already tested there, so there is no need to test them again here
arr = np.linspace(0, 1.0, 50*50).astype(np.float32).reshape((50, 50, 1))
heatmap = ia.HeatmapsOnImage(arr, shape=(99, 99, 3))
aug = iaa.CropToPowersOf(height_base=50, width_base=50,
position="center")
observed = aug(heatmaps=heatmap)
assert observed.shape == (50, 50, 3)
assert np.allclose(observed.arr_0to1,
heatmap.arr_0to1[12:-13, 12:-13, :])
def test_keypoints(self):
kps = [ia.Keypoint(x=2, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(8, 4, 3))
aug = iaa.CropToPowersOf(height_base=5, width_base=2,
position="center")
observed = aug(keypoints=kpsoi)
assert observed.keypoints[0].x == 2
assert observed.keypoints[0].y == 2
def test_get_parameters(self):
aug = iaa.CropToPowersOf(width_base=1, height_base=2,
position="center")
params = aug.get_parameters()
assert params[0] == 1
assert params[1] == 2
assert np.isclose(params[2][0].value, 0.5)
assert np.isclose(params[2][1].value, 0.5)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.CropToPowersOf(2, 2)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_pickleable(self):
aug = iaa.CropToPowersOf(2, 2, position="uniform", seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(15, 15, 1))
class TestCenterCropToPowersOf(unittest.TestCase):
def setUp(self):
reseed()
def test_on_3x3_image__with_change(self):
for _ in np.arange(10):
image = np.arange((11*13*3)).astype(np.uint8).reshape((11, 13, 3))
aug = iaa.CenterCropToPowersOf(height_base=2, width_base=3)
observed = aug(image=image)
assert np.array_equal(observed, image[1:-2, 2:-2, :])
def test_pickleable(self):
aug = iaa.CenterCropToPowersOf(2, 2)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(15, 15, 1))
class TestPadToPowersOf(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.PadToPowersOf(width_base=2, height_base=3,
position="center")
assert aug.width_base == 2
assert aug.height_base == 3
assert np.isclose(aug.position[0].value, 0.5)
assert np.isclose(aug.position[1].value, 0.5)
def test_on_3x3_image__no_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToPowersOf(3, 3, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_on_3x3_image__with_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToPowersOf(2, 2, position="center")
observed = aug(image=image)
expected = iaa.pad(image, bottom=1, right=1)
assert np.array_equal(observed, expected)
def test_on_3x3_image__only_width_changed(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToPowersOf(height_base=3, width_base=2,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, right=1)
assert np.array_equal(observed, expected)
def test_on_3x3_image__only_height_changed(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToPowersOf(height_base=2, width_base=3,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, bottom=1)
assert np.array_equal(observed, expected)
def test_on_3x4_image(self):
image = np.arange((3*4*3)).astype(np.uint8).reshape((3, 4, 3))
aug = iaa.PadToPowersOf(2, 2, position="center")
observed = aug(image=image)
expected = iaa.pad(image, bottom=1)
assert np.array_equal(observed, expected)
def test_on_7x22_image(self):
image = np.mod(
np.arange((7*22*3)),
255
).astype(np.uint8).reshape((7, 22, 3))
aug = iaa.PadToPowersOf(height_base=12, width_base=2,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=2, bottom=3, left=5, right=5)
assert np.array_equal(observed, expected)
def test_on_7x22_image__cval(self):
image = np.mod(
np.arange((7*22*3)),
255
).astype(np.uint8).reshape((7, 22, 3))
aug = iaa.PadToPowersOf(height_base=12, width_base=2,
pad_cval=100,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=2, bottom=3, left=5, right=5, cval=100)
assert np.array_equal(observed, expected)
def test_on_7x22_image__mode(self):
image = np.mod(
np.arange((7*22*3)),
255
).astype(np.uint8).reshape((7, 22, 3))
aug = iaa.PadToPowersOf(height_base=12, width_base=2,
pad_mode="edge",
position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=2, bottom=3, left=5, right=5, mode="edge")
assert np.array_equal(observed, expected)
def test_width_base_is_none(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToPowersOf(height_base=2, width_base=None,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, bottom=1)
assert np.array_equal(observed, expected)
def test_height_base_is_none(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToPowersOf(height_base=None, width_base=2,
position="center")
observed = aug(image=image)
expected = iaa.pad(image, right=1)
assert np.array_equal(observed, expected)
def test_heatmaps(self):
# segmaps are implemented in the same way in PadToFixesSize
# and already tested there, so there is no need to test them again here
arr = np.linspace(0, 1.0, 51*51).astype(np.float32).reshape((51, 51, 1))
heatmap = ia.HeatmapsOnImage(arr, shape=(101, 101, 3))
aug = iaa.PadToPowersOf(height_base=200, width_base=200,
position="center")
observed = aug(heatmaps=heatmap)
expected = heatmap.pad(top=25, bottom=25, left=25, right=25)
assert observed.shape == (200, 200, 3)
assert np.allclose(observed.arr_0to1, expected.arr_0to1)
def test_keypoints(self):
kps = [ia.Keypoint(x=2, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(14, 4, 3))
aug = iaa.PadToPowersOf(height_base=4, width_base=2,
position="center")
observed = aug(keypoints=kpsoi)
assert observed.keypoints[0].x == 2
assert observed.keypoints[0].y == 4
def test_get_parameters(self):
aug = iaa.PadToPowersOf(width_base=1, height_base=2,
pad_cval=5, pad_mode="edge",
position="center")
params = aug.get_parameters()
assert params[0] == 1
assert params[1] == 2
assert params[2].value == "edge"
assert params[3].value == 5
assert np.isclose(params[4][0].value, 0.5)
assert np.isclose(params[4][1].value, 0.5)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PadToPowersOf(2, 2)
image_aug = aug(image=image)
expected_height = 2
expected_width = 2
expected_shape = tuple([expected_height, expected_width]
+ list(shape[2:]))
assert image_aug.shape == expected_shape
def test_pickleable(self):
aug = iaa.PadToPowersOf(2, 2, position="uniform", seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(9, 9, 1))
class TestCenterPadToPowersOf(unittest.TestCase):
def setUp(self):
reseed()
def test_on_3x3_image__with_change(self):
for _ in np.arange(10):
image = np.arange((5*13*3)).astype(np.uint8).reshape((5, 13, 3))
aug = iaa.CenterPadToPowersOf(height_base=2, width_base=2)
observed = aug(image=image)
expected = iaa.pad(image, top=1, right=2, bottom=2, left=1)
assert np.array_equal(observed, expected)
def test_pickleable(self):
aug = iaa.CenterPadToPowersOf(2, 2)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(9, 9, 1))
class TestCropToAspectRatio(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.CropToAspectRatio(2.0, position="center")
assert np.isclose(aug.aspect_ratio, 2.0)
assert np.isclose(aug.position[0].value, 0.5)
assert np.isclose(aug.position[1].value, 0.5)
def test_on_4x4_image__no_change(self):
image = np.arange((4*4*3)).astype(np.uint8).reshape((4, 4, 3))
aug = iaa.CropToAspectRatio(1.0, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_on_4x4_image__with_change__wider(self):
image = np.arange((4*4*3)).astype(np.uint8).reshape((4, 4, 3))
aug = iaa.CropToAspectRatio(2.0, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[1:3, 0:4, :])
def test_on_4x4_image__with_change__higher(self):
image = np.arange((4*4*3)).astype(np.uint8).reshape((4, 4, 3))
aug = iaa.CropToAspectRatio(0.5, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[0:4, 1:3, :])
def test_on_5x4_image__with_change__wider(self):
image = np.arange((5*4*3)).astype(np.uint8).reshape((5, 4, 3))
aug = iaa.CropToAspectRatio(2.0, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[1:3, 0:4, :])
def test_on_5x4_image__with_change__higher(self):
image = np.arange((5*4*3)).astype(np.uint8).reshape((5, 4, 3))
aug = iaa.CropToAspectRatio(0.5, position="center")
observed = aug(image=image)
# Here it could either crop 1px or 2px from the width (leading to
# aspect ratios of 3/5=0.6 or 2/5=0.4. The underlying method rather
# crops one pixel too few than one too many, hence we only crop 1px
# here.
assert np.array_equal(observed, image[0:5, 0:3, :])
def test_unreachable_aspect_ratio__wider(self):
image = np.arange((5*4*3)).astype(np.uint8).reshape((5, 4, 3))
aug = iaa.CropToAspectRatio(20.0, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[2:3, 0:4, :])
def test_unreachable_aspect_ratio__higher(self):
image = np.arange((5*4*3)).astype(np.uint8).reshape((5, 4, 3))
aug = iaa.CropToAspectRatio(0.01, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[:, 1:2, :])
def test_heatmaps(self):
# segmaps are implemented in the same way in CropToFixesSize
# and already tested there, so there is no need to test them again here
arr = np.linspace(0, 1.0, 50*50).astype(np.float32).reshape((50, 50, 1))
heatmap = ia.HeatmapsOnImage(arr, shape=(100, 100, 3))
aug = iaa.CropToAspectRatio(2.0, position="center")
observed = aug(heatmaps=heatmap)
assert observed.shape == (50, 100, 3)
assert np.allclose(observed.arr_0to1,
heatmap.arr_0to1[12:-13, :, :])
def test_keypoints(self):
kps = [ia.Keypoint(x=2, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(8, 8, 3))
aug = iaa.CropToAspectRatio(2.0, position="center")
observed = aug(keypoints=kpsoi)
assert observed.keypoints[0].x == 2
assert observed.keypoints[0].y == 1
def test_get_parameters(self):
aug = iaa.CropToAspectRatio(2.0, position="center")
params = aug.get_parameters()
assert np.isclose(params[0], 2.0)
assert np.isclose(params[1][0].value, 0.5)
assert np.isclose(params[1][1].value, 0.5)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
for aspect_ratio in [2.0, 1.0, 0.5]:
with self.subTest(shape=shape, aspect_ratio=aspect_ratio):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.CropToAspectRatio(aspect_ratio)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_pickleable(self):
aug = iaa.CropToAspectRatio(1.0, position="uniform", seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(20, 10, 1))
class TestCenterCropToAspectRatio(unittest.TestCase):
def setUp(self):
reseed()
def test_on_5x4_image__with_change__wider(self):
for _ in np.arange(10):
image = np.arange((5*4*3)).astype(np.uint8).reshape((5, 4, 3))
aug = iaa.CenterCropToAspectRatio(2.0)
observed = aug(image=image)
assert np.array_equal(observed, image[1:3, 0:4, :])
def test_pickleable(self):
aug = iaa.CenterCropToAspectRatio(1.0)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(20, 10, 1))
class TestPadToAspectRatio(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
aug = iaa.PadToAspectRatio(2.0, position="center")
assert np.isclose(aug.aspect_ratio, 2.0)
assert np.isclose(aug.position[0].value, 0.5)
assert np.isclose(aug.position[1].value, 0.5)
def test_on_3x3_image__no_change(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToAspectRatio(1.0, position="center")
observed = aug(image=image)
assert np.array_equal(observed, image)
def test_on_3x3_image__with_change__wider(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToAspectRatio(2.0, position="center")
observed = aug(image=image)
expected = iaa.pad(image, left=1, right=2)
assert np.array_equal(observed, expected)
def test_on_3x3_image__with_change__higher(self):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.PadToAspectRatio(0.5, position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=1, bottom=2)
assert np.array_equal(observed, expected)
def test_on_3x4_image(self):
image = np.arange((3*4*3)).astype(np.uint8).reshape((3, 4, 3))
aug = iaa.PadToAspectRatio(2.0, position="center")
observed = aug(image=image)
expected = iaa.pad(image, left=1, right=1)
assert np.array_equal(observed, expected)
def test_on_7x22_image__height_padded_even_though_ratio_is_wide(self):
image = np.mod(
np.arange((7*22*3)),
255
).astype(np.uint8).reshape((7, 22, 3))
aug = iaa.PadToAspectRatio(2.0, position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=2, bottom=2)
assert np.array_equal(observed, expected)
def test_on_10x6_image__cval(self):
image = np.arange((10*6*3)).astype(np.uint8).reshape((10, 6, 3))
aug = iaa.PadToAspectRatio(0.5, pad_cval=100, position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=1, bottom=1, cval=100)
assert np.array_equal(observed, expected)
def test_on_10x6_image__mode(self):
image = np.arange((10*6*3)).astype(np.uint8).reshape((10, 6, 3))
aug = iaa.PadToAspectRatio(0.5,
pad_mode="edge",
position="center")
observed = aug(image=image)
expected = iaa.pad(image, top=1, bottom=1, mode="edge")
assert np.array_equal(observed, expected)
def test_heatmaps(self):
# segmaps are implemented in the same way in PadToFixesSize
# and already tested there, so there is no need to test them again here
arr = np.linspace(0, 1.0, 50*50).astype(np.float32).reshape((50, 50, 1))
heatmap = ia.HeatmapsOnImage(arr, shape=(100, 100, 3))
aug = iaa.PadToAspectRatio(2.0, position="center")
observed = aug(heatmaps=heatmap)
expected = heatmap.pad(top=0, bottom=0, left=25, right=25)
assert observed.shape == (100, 200, 3)
assert np.allclose(observed.arr_0to1, expected.arr_0to1)
def test_keypoints(self):
kps = [ia.Keypoint(x=2, y=3)]
kpsoi = ia.KeypointsOnImage(kps, shape=(10, 5, 3))
aug = iaa.PadToAspectRatio(1.0, position="center")
observed = aug(keypoints=kpsoi)
assert observed.keypoints[0].x == 2+2
assert observed.keypoints[0].y == 3
def test_get_parameters(self):
aug = iaa.PadToAspectRatio(2.0,
pad_cval=5, pad_mode="edge",
position="center")
params = aug.get_parameters()
assert np.isclose(params[0], 2.0)
assert params[1].value == "edge"
assert params[2].value == 5
assert np.isclose(params[3][0].value, 0.5)
assert np.isclose(params[3][1].value, 0.5)
def test_zero_sized_axes__wider(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PadToAspectRatio(2.0)
image_aug = aug(image=image)
height, width = shape[0:2]
if width == 0 and height == 0:
h_exp, w_exp = (1, 2)
elif width == 0:
h_exp, w_exp = (height, height * 2)
else: # height == 0
h_exp, w_exp = (1, 2)
expected_shape = tuple([h_exp, w_exp] + list(shape[2:]))
assert image_aug.shape == expected_shape
def test_zero_sized_axes__higher(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.PadToAspectRatio(0.5)
image_aug = aug(image=image)
height, width = shape[0:2]
if width == 0 and height == 0:
h_exp, w_exp = (2, 1)
elif height == 0:
h_exp, w_exp = (width * 2, width)
else: # width == 0
h_exp, w_exp = (2, 1)
expected_shape = tuple([h_exp, w_exp] + list(shape[2:]))
assert image_aug.shape == expected_shape
def test_pickleable(self):
aug = iaa.PadToAspectRatio(2.0, position="uniform", seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(10, 10, 1))
class TestCenterPadToAspectRatio(unittest.TestCase):
def setUp(self):
reseed()
def test_on_3x3_image(self):
for _ in np.arange(10):
image = np.arange((3*3*3)).astype(np.uint8).reshape((3, 3, 3))
aug = iaa.CenterPadToAspectRatio(2.0)
observed = aug(image=image)
expected = iaa.pad(image, left=1, right=2)
assert np.array_equal(observed, expected)
def test_pickleable(self):
aug = iaa.CenterPadToAspectRatio(2.0)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(10, 10, 1))
class TestCropToSquare(unittest.TestCase):
def setUp(self):
reseed()
def test_on_7x4_image(self):
image = np.arange((7*4*3)).astype(np.uint8).reshape((7, 4, 3))
aug = iaa.CropToSquare(position="center")
observed = aug(image=image)
assert np.array_equal(observed, image[1:5, 0:4, :])
def test_pickleable(self):
aug = iaa.CropToSquare(position="uniform")
runtest_pickleable_uint8_img(aug, iterations=5, shape=(10, 15, 1))
class TestCenterCropToSquare(unittest.TestCase):
def setUp(self):
reseed()
def test_on_7x4_image(self):
for _ in np.arange(10):
image = np.arange((7*4*3)).astype(np.uint8).reshape((7, 4, 3))
aug = iaa.CenterCropToSquare()
observed = aug(image=image)
assert np.array_equal(observed, image[1:5, 0:4, :])
def test_pickleable(self):
aug = iaa.CenterCropToSquare()
runtest_pickleable_uint8_img(aug, iterations=5, shape=(10, 15, 1))
class TestPadToSquare(unittest.TestCase):
def setUp(self):
reseed()
def test_on_7x4_image(self):
image = np.arange((7*4*3)).astype(np.uint8).reshape((7, 4, 3))
aug = iaa.PadToSquare(position="center")
observed = aug(image=image)
expected = iaa.pad(image, left=1, right=2)
assert np.array_equal(observed, expected)
def test_pickleable(self):
aug = iaa.PadToSquare(position="uniform")
runtest_pickleable_uint8_img(aug, iterations=5, shape=(10, 15, 1))
class TestCenterPadToSquare(unittest.TestCase):
def setUp(self):
reseed()
def test_on_7x4_image(self):
for _ in np.arange(10):
image = np.arange((7*4*3)).astype(np.uint8).reshape((7, 4, 3))
aug = iaa.CenterPadToSquare()
observed = aug(image=image)
expected = iaa.pad(image, left=1, right=2)
assert np.array_equal(observed, expected)
def test_pickleable(self):
aug = iaa.CenterPadToSquare()
runtest_pickleable_uint8_img(aug, iterations=5, shape=(10, 15, 1))
class TestKeepSizeByResize(unittest.TestCase):
def setUp(self):
reseed()
@property
def children(self):
return iaa.Crop((1, 0, 0, 0), keep_size=False)
@property
def kpsoi(self):
kps = [ia.Keypoint(x=0, y=1), ia.Keypoint(x=1, y=1),
ia.Keypoint(x=2, y=3)]
return ia.KeypointsOnImage(kps, shape=(4, 4, 3))
@property
def heatmaps(self):
heatmaps_arr = np.linspace(
0.0, 1.0, 4*4*1).reshape((4, 4, 1)).astype(np.float32)
return HeatmapsOnImage(heatmaps_arr, shape=(4, 4, 1))
@property
def heatmaps_cubic(self):
heatmaps_arr = self.heatmaps.get_arr()
heatmaps_oi_cubic = HeatmapsOnImage(
heatmaps_arr[1:, :, :], shape=(3, 4, 3)
).resize((4, 4), interpolation="cubic")
heatmaps_oi_cubic.shape = (4, 4, 3)
return heatmaps_oi_cubic
@property
def heatmaps_nearest(self):
heatmaps_arr = self.heatmaps.get_arr()
heatmaps_oi_nearest = HeatmapsOnImage(
heatmaps_arr[1:, :, :], shape=(3, 4, 1)
).resize((4, 4), interpolation="nearest")
heatmaps_oi_nearest.shape = (4, 4, 3)
return heatmaps_oi_nearest
@property
def segmaps(self):
segmaps_arr = np.arange(4*4*1).reshape((4, 4, 1)).astype(np.int32)
return SegmentationMapsOnImage(segmaps_arr, shape=(4, 4, 1))
@property
def segmaps_nearest(self):
segmaps_arr = self.segmaps.get_arr()
segmaps_oi_nearest = SegmentationMapsOnImage(
segmaps_arr[1:, :, :], shape=(3, 4, 1)
).resize((4, 4), interpolation="nearest")
segmaps_oi_nearest.shape = (4, 4, 3)
return segmaps_oi_nearest
def test__draw_samples_each_one_interpolation(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation="nearest",
interpolation_heatmaps="linear",
interpolation_segmaps="cubic")
samples, samples_heatmaps, samples_segmaps = aug._draw_samples(
1000, iarandom.RNG(1))
assert "nearest" in samples
assert len(set(samples)) == 1
assert "linear" in samples_heatmaps
assert len(set(samples_heatmaps)) == 1
assert "cubic" in samples_segmaps
assert len(set(samples_segmaps)) == 1
def test__draw_samples_each_one_interpolation_via_cv2_constants(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation=cv2.INTER_LINEAR,
interpolation_heatmaps=cv2.INTER_NEAREST,
interpolation_segmaps=cv2.INTER_CUBIC)
samples, samples_heatmaps, samples_segmaps = aug._draw_samples(
1000, iarandom.RNG(1))
assert cv2.INTER_LINEAR in samples
assert len(set(samples)) == 1
assert cv2.INTER_NEAREST in samples_heatmaps
assert len(set(samples_heatmaps)) == 1
assert cv2.INTER_CUBIC in samples_segmaps
assert len(set(samples_segmaps)) == 1
def test__draw_samples_with_images_no_resize_and_others_same_as_imgs(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation=iaa.KeepSizeByResize.NO_RESIZE,
interpolation_heatmaps=iaa.KeepSizeByResize.SAME_AS_IMAGES,
interpolation_segmaps=iaa.KeepSizeByResize.SAME_AS_IMAGES)
samples, samples_heatmaps, samples_segmaps = aug._draw_samples(
1000, iarandom.RNG(1))
assert iaa.KeepSizeByResize.NO_RESIZE in samples
assert len(set(samples)) == 1
assert iaa.KeepSizeByResize.NO_RESIZE in samples_heatmaps
assert len(set(samples_heatmaps)) == 1
assert iaa.KeepSizeByResize.NO_RESIZE in samples_segmaps
assert len(set(samples_segmaps)) == 1
def test__draw_samples_list_of_interpolations_incl_same_as_images(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation=["cubic", "nearest"],
interpolation_heatmaps=[
"linear", iaa.KeepSizeByResize.SAME_AS_IMAGES],
interpolation_segmaps=[
"linear", iaa.KeepSizeByResize.SAME_AS_IMAGES])
samples, samples_heatmaps, samples_segmaps = aug._draw_samples(
5000, iarandom.RNG(1))
assert "cubic" in samples
assert "nearest" in samples
assert len(set(samples)) == 2
assert "linear" in samples_heatmaps
assert "nearest" in samples_heatmaps
assert len(set(samples_heatmaps)) == 3
assert np.isclose(
np.sum(samples == samples_heatmaps) / samples_heatmaps.size,
0.5,
rtol=0, atol=0.1)
assert "linear" in samples_segmaps
assert "nearest" in samples_segmaps
assert len(set(samples_segmaps)) == 3
assert np.isclose(
np.sum(samples == samples_segmaps) / samples_segmaps.size,
0.5,
rtol=0, atol=0.1)
def test__draw_samples_list_of_each_two_interpolations(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation=iap.Choice(["cubic", "linear"]),
interpolation_heatmaps=iap.Choice(["linear", "nearest"]),
interpolation_segmaps=iap.Choice(["linear", "nearest"]))
samples, samples_heatmaps, samples_segmaps = aug._draw_samples(
10000, iarandom.RNG(1))
assert "cubic" in samples
assert "linear" in samples
assert len(set(samples)) == 2
assert "linear" in samples_heatmaps
assert "nearest" in samples_heatmaps
assert len(set(samples_heatmaps)) == 2
assert "linear" in samples_segmaps
assert "nearest" in samples_segmaps
assert len(set(samples_segmaps)) == 2
def test_image_interpolation_is_cubic(self):
aug = iaa.KeepSizeByResize(self.children, interpolation="cubic")
img = np.arange(0, 4*4*3, 1).reshape((4, 4, 3)).astype(np.uint8)
observed = aug.augment_image(img)
assert observed.shape == (4, 4, 3)
assert observed.dtype.type == np.uint8
expected = ia.imresize_single_image(
img[1:, :, :], img.shape[0:2], interpolation="cubic")
assert np.allclose(observed, expected)
def test_image_interpolation_is_no_resize(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation=iaa.KeepSizeByResize.NO_RESIZE)
img = np.arange(0, 4*4*3, 1).reshape((4, 4, 3)).astype(np.uint8)
observed = aug.augment_image(img)
expected = img[1:, :, :]
assert observed.shape == (3, 4, 3)
assert observed.dtype.type == np.uint8
assert np.allclose(observed, expected)
def test_images_input_is_single_array(self):
# input is single array, children turn in into list of arrays()
# => must be combined to a single output array
images = np.zeros((10, 100, 100), dtype=np.uint8)
aug = iaa.KeepSizeByResize(iaa.Crop((0, 40), keep_size=False))
images_aug = aug(images=images)
assert images.dtype.name == "uint8"
assert images.shape == (10, 100, 100)
def test_keypoints_interpolation_is_cubic(self):
aug = iaa.KeepSizeByResize(self.children, interpolation="cubic")
kpsoi = self.kpsoi
kpoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpoi_aug.shape == (4, 4, 3)
assert np.isclose(kpoi_aug.keypoints[0].x, 0, rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[0].y,
((1-1)/3)*4,
rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[1].x, 1, rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[1].y,
((1-1)/3)*4,
rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[2].x, 2, rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[2].y,
((3-1)/3)*4,
rtol=0, atol=1e-4)
def test_keypoints_interpolation_is_no_resize(self):
aug = iaa.KeepSizeByResize(
self.children, interpolation=iaa.KeepSizeByResize.NO_RESIZE)
kpsoi = self.kpsoi
kpoi_aug = aug.augment_keypoints([kpsoi])[0]
assert kpoi_aug.shape == (3, 4, 3)
assert np.isclose(kpoi_aug.keypoints[0].x, 0, rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[0].y, 0, rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[1].x, 1, rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[1].y, 0, rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[2].x, 2, rtol=0, atol=1e-4)
assert np.isclose(kpoi_aug.keypoints[2].y, 2, rtol=0, atol=1e-4)
def test_polygons_interpolation_is_cubic(self):
aug = iaa.KeepSizeByResize(self.children, interpolation="cubic")
cbaoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(4, 4, 3))
cbaoi_aug = aug.augment_polygons(cbaoi)
assert cbaoi_aug.shape == (4, 4, 3)
assert np.allclose(
cbaoi_aug.items[0].coords,
[(0, ((0-1)/3)*4),
(3, ((0-1)/3)*4),
(3, ((3-1)/3)*4)]
)
def test_polygons_interpolation_is_no_resize(self):
aug = iaa.KeepSizeByResize(
self.children, interpolation=iaa.KeepSizeByResize.NO_RESIZE)
cbaoi = ia.PolygonsOnImage([
ia.Polygon([(0, 0), (3, 0), (3, 3)])
], shape=(4, 4, 3))
cbaoi_aug = aug.augment_polygons(cbaoi)
assert cbaoi_aug.shape == (3, 4, 3)
assert np.allclose(
cbaoi_aug.items[0].coords,
[(0, 0-1),
(3, 0-1),
(3, 3-1)]
)
def test_line_strings_interpolation_is_cubic(self):
aug = iaa.KeepSizeByResize(self.children, interpolation="cubic")
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(4, 4, 3))
cbaoi_aug = aug.augment_line_strings(cbaoi)
assert cbaoi_aug.shape == (4, 4, 3)
assert np.allclose(
cbaoi_aug.items[0].coords,
[(0, ((0-1)/3)*4),
(3, ((0-1)/3)*4),
(3, ((3-1)/3)*4)]
)
def test_line_strings_interpolation_is_no_resize(self):
aug = iaa.KeepSizeByResize(
self.children, interpolation=iaa.KeepSizeByResize.NO_RESIZE)
cbaoi = ia.LineStringsOnImage([
ia.LineString([(0, 0), (3, 0), (3, 3)])
], shape=(4, 4, 3))
cbaoi_aug = aug.augment_line_strings(cbaoi)
assert cbaoi_aug.shape == (3, 4, 3)
assert np.allclose(
cbaoi_aug.items[0].coords,
[(0, 0-1),
(3, 0-1),
(3, 3-1)]
)
def test_bounding_boxes_interpolation_is_cubic(self):
aug = iaa.KeepSizeByResize(self.children, interpolation="cubic")
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=3, y2=4)
], shape=(4, 4, 3))
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
assert bbsoi_aug.shape == (4, 4, 3)
assert np.allclose(
bbsoi_aug.bounding_boxes[0].coords,
[(0, ((1-1)/3)*4),
(3, ((4-1)/3)*4)]
)
def test_bounding_boxes_interpolation_is_no_resize(self):
aug = iaa.KeepSizeByResize(
self.children, interpolation=iaa.KeepSizeByResize.NO_RESIZE)
bbsoi = ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=0, y1=1, x2=3, y2=4)
], shape=(4, 4, 3))
bbsoi_aug = aug.augment_bounding_boxes(bbsoi)
assert bbsoi_aug.shape == (3, 4, 3)
assert np.allclose(
bbsoi_aug.bounding_boxes[0].coords,
[(0, 1-1),
(3, 4-1)]
)
def test_heatmaps_specific_interpolation_set_to_no_nearest(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation="cubic",
interpolation_heatmaps="nearest")
heatmaps_oi = self.heatmaps
heatmaps_oi_nearest = self.heatmaps_nearest
heatmaps_oi_aug = aug.augment_heatmaps([heatmaps_oi])[0]
assert heatmaps_oi_aug.arr_0to1.shape == (4, 4, 1)
assert np.allclose(heatmaps_oi_aug.arr_0to1,
heatmaps_oi_nearest.arr_0to1)
def test_heatmaps_specific_interpolation_set_to_list_of_two(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation="cubic",
interpolation_heatmaps=["nearest", "cubic"])
heatmaps_oi = self.heatmaps
heatmaps_oi_cubic = self.heatmaps_cubic
heatmaps_oi_nearest = self.heatmaps_nearest
hmoi_aug = aug.augment_heatmaps([heatmaps_oi])[0]
assert hmoi_aug.arr_0to1.shape == (4, 4, 1)
assert (
np.allclose(hmoi_aug.arr_0to1, heatmaps_oi_nearest.arr_0to1)
or np.allclose(hmoi_aug.arr_0to1, heatmaps_oi_cubic.arr_0to1)
)
def test_heatmaps_specific_interpolation_set_to_no_resize(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation="cubic",
interpolation_heatmaps=iaa.KeepSizeByResize.NO_RESIZE)
heatmaps_oi = self.heatmaps
heatmaps_oi_aug = aug.augment_heatmaps([heatmaps_oi])[0]
assert heatmaps_oi_aug.arr_0to1.shape == (3, 4, 1)
assert np.allclose(
heatmaps_oi_aug.arr_0to1, heatmaps_oi.arr_0to1[1:, :, :])
def test_heatmaps_specific_interpolation_set_to_same_as_images(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation="cubic",
interpolation_heatmaps=iaa.KeepSizeByResize.SAME_AS_IMAGES)
heatmaps_oi = self.heatmaps
heatmaps_oi_cubic = self.heatmaps_cubic
heatmaps_oi_aug = aug.augment_heatmaps([heatmaps_oi])[0]
assert heatmaps_oi_aug.arr_0to1.shape == (4, 4, 1)
assert np.allclose(
heatmaps_oi_aug.arr_0to1, heatmaps_oi_cubic.arr_0to1)
def test_segmaps_general_interpolation_set_to_cubic(self):
aug = iaa.KeepSizeByResize(self.children, interpolation="cubic")
segmaps_oi = self.segmaps
segmaps_oi_nearest = self.segmaps_nearest
segmaps_oi_aug = aug.augment_segmentation_maps([segmaps_oi])[0]
assert segmaps_oi_aug.arr.shape == (4, 4, 1)
assert np.array_equal(segmaps_oi_aug.arr, segmaps_oi_nearest.arr)
def test_segmaps_specific_interpolation_set_to_nearest(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation="cubic",
interpolation_segmaps="nearest")
segmaps_oi = self.segmaps
segmaps_oi_nearest = self.segmaps_nearest
segmaps_oi_aug = aug.augment_segmentation_maps([segmaps_oi])[0]
assert segmaps_oi_aug.arr.shape == (4, 4, 1)
assert np.array_equal(segmaps_oi_aug.arr, segmaps_oi_nearest.arr)
def test_segmaps_specific_interpolation_set_to_no_resize(self):
aug = iaa.KeepSizeByResize(
self.children,
interpolation="cubic",
interpolation_segmaps=iaa.KeepSizeByResize.NO_RESIZE)
segmaps_oi = self.segmaps
segmaps_oi_aug = aug.augment_segmentation_maps([segmaps_oi])[0]
assert segmaps_oi_aug.arr.shape == (3, 4, 1)
assert np.array_equal(segmaps_oi_aug.arr, segmaps_oi.arr[1:, :, :])
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1),
(0, 2),
(2, 0),
(0, 2, 0),
(2, 0, 0),
(0, 2, 1),
(2, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.KeepSizeByResize(
iaa.CropToFixedSize(height=1, width=1)
)
image_aug = aug(image=image)
assert image_aug.shape == image.shape
def test_pickleable(self):
aug = iaa.KeepSizeByResize([
iaa.CropToFixedSize(10, 10, position="uniform", seed=1)
], interpolation=["nearest", "linear"], seed=1)
runtest_pickleable_uint8_img(aug, iterations=5, shape=(15, 15, 1))
def test_get_children_lists(self):
child = iaa.Identity()
aug = iaa.KeepSizeByResize([child])
children_lsts = aug.get_children_lists()
assert len(children_lsts) == 1
assert len(children_lsts[0]) == 1
assert children_lsts[0][0] is child
def test_to_deterministic(self):
child = iaa.Identity()
aug = iaa.KeepSizeByResize([child])
aug_det = aug.to_deterministic()
assert aug_det.deterministic
assert aug_det.random_state is not aug.random_state
assert aug_det.children[0].deterministic
================================================
FILE: test/augmenters/test_weather.py
================================================
from __future__ import print_function, division, absolute_import
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import cv2
import imgaug as ia
from imgaug import augmenters as iaa
from imgaug import parameters as iap
from imgaug.testutils import (reseed, runtest_pickleable_uint8_img,
is_parameter_instance)
class _TwoValueParam(iap.StochasticParameter):
def __init__(self, v1, v2):
super(_TwoValueParam, self).__init__()
self.v1 = v1
self.v2 = v2
def _draw_samples(self, size, random_state):
arr = np.full(size, self.v1, dtype=np.float32)
arr[1::2] = self.v2
return arr
class TestFastSnowyLandscape(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
# check parameters
aug = iaa.FastSnowyLandscape(
lightness_threshold=[100, 200],
lightness_multiplier=[1.0, 4.0])
assert is_parameter_instance(aug.lightness_threshold, iap.Choice)
assert len(aug.lightness_threshold.a) == 2
assert aug.lightness_threshold.a[0] == 100
assert aug.lightness_threshold.a[1] == 200
assert is_parameter_instance(aug.lightness_multiplier, iap.Choice)
assert len(aug.lightness_multiplier.a) == 2
assert np.allclose(aug.lightness_multiplier.a[0], 1.0)
assert np.allclose(aug.lightness_multiplier.a[1], 4.0)
def test_basic_functionality(self):
# basic functionality test
aug = iaa.FastSnowyLandscape(
lightness_threshold=100,
lightness_multiplier=2.0)
image = np.arange(0, 6*6*3).reshape((6, 6, 3)).astype(np.uint8)
image_hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
mask = (image_hls[..., 1] < 100)
expected = np.copy(image_hls).astype(np.float32)
expected[..., 1][mask] *= 2.0
expected = np.clip(np.round(expected), 0, 255).astype(np.uint8)
expected = cv2.cvtColor(expected, cv2.COLOR_HLS2RGB)
observed = aug.augment_image(image)
assert np.array_equal(observed, expected)
def test_vary_lightness_threshold(self):
# test when varying lightness_threshold between images
image = np.arange(0, 6*6*3).reshape((6, 6, 3)).astype(np.uint8)
image_hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
aug = iaa.FastSnowyLandscape(
lightness_threshold=_TwoValueParam(75, 125),
lightness_multiplier=2.0)
mask = (image_hls[..., 1] < 75)
expected1 = np.copy(image_hls).astype(np.float64)
expected1[..., 1][mask] *= 2.0
expected1 = np.clip(np.round(expected1), 0, 255).astype(np.uint8)
expected1 = cv2.cvtColor(expected1, cv2.COLOR_HLS2RGB)
mask = (image_hls[..., 1] < 125)
expected2 = np.copy(image_hls).astype(np.float64)
expected2[..., 1][mask] *= 2.0
expected2 = np.clip(np.round(expected2), 0, 255).astype(np.uint8)
expected2 = cv2.cvtColor(expected2, cv2.COLOR_HLS2RGB)
observed = aug.augment_images([image] * 4)
assert np.array_equal(observed[0], expected1)
assert np.array_equal(observed[1], expected2)
assert np.array_equal(observed[2], expected1)
assert np.array_equal(observed[3], expected2)
def test_vary_lightness_multiplier(self):
# test when varying lightness_multiplier between images
image = np.arange(0, 6*6*3).reshape((6, 6, 3)).astype(np.uint8)
image_hls = cv2.cvtColor(image, cv2.COLOR_RGB2HLS)
aug = iaa.FastSnowyLandscape(
lightness_threshold=100,
lightness_multiplier=_TwoValueParam(1.5, 2.0))
mask = (image_hls[..., 1] < 100)
expected1 = np.copy(image_hls).astype(np.float64)
expected1[..., 1][mask] *= 1.5
expected1 = np.clip(np.round(expected1), 0, 255).astype(np.uint8)
expected1 = cv2.cvtColor(expected1, cv2.COLOR_HLS2RGB)
mask = (image_hls[..., 1] < 100)
expected2 = np.copy(image_hls).astype(np.float64)
expected2[..., 1][mask] *= 2.0
expected2 = np.clip(np.round(expected2), 0, 255).astype(np.uint8)
expected2 = cv2.cvtColor(expected2, cv2.COLOR_HLS2RGB)
observed = aug.augment_images([image] * 4)
assert np.array_equal(observed[0], expected1)
assert np.array_equal(observed[1], expected2)
assert np.array_equal(observed[2], expected1)
assert np.array_equal(observed[3], expected2)
def test_from_colorspace(self):
# test BGR colorspace
aug = iaa.FastSnowyLandscape(
lightness_threshold=100,
lightness_multiplier=2.0,
from_colorspace="BGR")
image = np.arange(0, 6*6*3).reshape((6, 6, 3)).astype(np.uint8)
image_hls = cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
mask = (image_hls[..., 1] < 100)
expected = np.copy(image_hls).astype(np.float32)
expected[..., 1][mask] *= 2.0
expected = np.clip(np.round(expected), 0, 255).astype(np.uint8)
expected = cv2.cvtColor(expected, cv2.COLOR_HLS2BGR)
observed = aug.augment_image(image)
assert np.array_equal(observed, expected)
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.FastSnowyLandscape(100, 1.5,
from_colorspace="RGB")
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.FastSnowyLandscape(lightness_threshold=(50, 150),
lightness_multiplier=(1.0, 3.0),
seed=1)
runtest_pickleable_uint8_img(aug)
# only a very rough test here currently, because the augmenter is fairly hard
# to test
# TODO add more tests, improve testability
class TestClouds(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _test_very_roughly(cls, nb_channels):
if nb_channels is None:
img = np.zeros((100, 100), dtype=np.uint8)
else:
img = np.zeros((100, 100, nb_channels), dtype=np.uint8)
imgs_aug = iaa.Clouds().augment_images([img] * 5)
assert 20 < np.average(imgs_aug) < 250
assert np.max(imgs_aug) > 150
for img_aug in imgs_aug:
img_aug_f32 = img_aug.astype(np.float32)
grad_x = img_aug_f32[:, 1:] - img_aug_f32[:, :-1]
grad_y = img_aug_f32[1:, :] - img_aug_f32[:-1, :]
assert np.sum(np.abs(grad_x)) > 5 * img.shape[1]
assert np.sum(np.abs(grad_y)) > 5 * img.shape[0]
def test_very_roughly_three_channels(self):
self._test_very_roughly(3)
def test_very_roughly_one_channel(self):
self._test_very_roughly(1)
def test_very_roughly_no_channel(self):
self._test_very_roughly(None)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Clouds()
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Clouds()
image_aug = aug(image=image)
assert np.any(image_aug > 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.Clouds(seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(20, 20, 3))
# only a very rough test here currently, because the augmenter is fairly hard
# to test
# TODO add more tests, improve testability
class TestFog(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _test_very_roughly(cls, nb_channels):
if nb_channels is None:
img = np.zeros((100, 100), dtype=np.uint8)
else:
img = np.zeros((100, 100, nb_channels), dtype=np.uint8)
imgs_aug = iaa.Clouds().augment_images([img] * 5)
assert 50 < np.average(imgs_aug) < 255
assert np.max(imgs_aug) > 100
for img_aug in imgs_aug:
img_aug_f32 = img_aug.astype(np.float32)
grad_x = img_aug_f32[:, 1:] - img_aug_f32[:, :-1]
grad_y = img_aug_f32[1:, :] - img_aug_f32[:-1, :]
assert np.sum(np.abs(grad_x)) > 1 * img.shape[1]
assert np.sum(np.abs(grad_y)) > 1 * img.shape[0]
def test_very_roughly_three_channels(self):
self._test_very_roughly(3)
def test_very_roughly_one_channel(self):
self._test_very_roughly(1)
def test_very_roughly_no_channel(self):
self._test_very_roughly(None)
def test_zero_sized_axes(self):
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Fog()
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_unusual_channel_numbers(self):
shapes = [
(1, 1, 4),
(1, 1, 5),
(1, 1, 512),
(1, 1, 513)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Fog()
image_aug = aug(image=image)
assert np.any(image_aug > 0)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.Fog(seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(20, 20, 3))
# only a very rough test here currently, because the augmenter is fairly hard
# to test
# TODO add more tests, improve testability
class TestSnowflakes(unittest.TestCase):
def setUp(self):
reseed()
def _test_very_roughly(self, nb_channels):
if nb_channels is None:
img = np.zeros((100, 100), dtype=np.uint8)
else:
img = np.zeros((100, 100, nb_channels), dtype=np.uint8)
imgs_aug = iaa.Snowflakes().augment_images([img] * 5)
assert 0.01 < np.average(imgs_aug) < 100
assert np.max(imgs_aug) > 100
for img_aug in imgs_aug:
img_aug_f32 = img_aug.astype(np.float32)
grad_x = img_aug_f32[:, 1:] - img_aug_f32[:, :-1]
grad_y = img_aug_f32[1:, :] - img_aug_f32[:-1, :]
assert np.sum(np.abs(grad_x)) > 5 * img.shape[1]
assert np.sum(np.abs(grad_y)) > 5 * img.shape[0]
# test density
imgs_aug_undense = iaa.Snowflakes(
density=0.001,
density_uniformity=0.99).augment_images([img] * 5)
imgs_aug_dense = iaa.Snowflakes(
density=0.1,
density_uniformity=0.99).augment_images([img] * 5)
assert (
np.average(imgs_aug_undense)
< np.average(imgs_aug_dense)
)
# test density_uniformity
imgs_aug_ununiform = iaa.Snowflakes(
density=0.4,
density_uniformity=0.1).augment_images([img] * 30)
imgs_aug_uniform = iaa.Snowflakes(
density=0.4,
density_uniformity=0.9).augment_images([img] * 30)
ununiform_uniformity = np.average([
self._measure_uniformity(img_aug)
for img_aug in imgs_aug_ununiform])
uniform_uniformity = np.average([
self._measure_uniformity(img_aug)
for img_aug in imgs_aug_uniform])
assert ununiform_uniformity < uniform_uniformity
def test_very_roughly_three_channels(self):
self._test_very_roughly(3)
def test_very_roughly_one_channel(self):
self._test_very_roughly(1)
def test_very_roughly_no_channels(self):
self._test_very_roughly(None)
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Snowflakes()
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.Snowflakes(seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(20, 20, 3))
@classmethod
def _measure_uniformity(cls, image, patch_size=5, n_patches=50):
pshalf = (patch_size-1) // 2
image_f32 = image.astype(np.float32)
grad_x = image_f32[:, 1:] - image_f32[:, :-1]
grad_y = image_f32[1:, :] - image_f32[:-1, :]
grad = np.abs(grad_x[1:, :] + grad_y[:, 1:])
points_y = np.random.randint(0, image.shape[0], size=(n_patches,))
points_x = np.random.randint(0, image.shape[0], size=(n_patches,))
stds = []
for y, x in zip(points_y, points_x):
bb = ia.BoundingBox(
x1=x-pshalf,
y1=y-pshalf,
x2=x+pshalf,
y2=y+pshalf)
patch = bb.extract_from_image(grad)
stds.append(np.std(patch))
return 1 / (1+np.std(stds))
class TestSnowflakesLayer(unittest.TestCase):
def setUp(self):
reseed()
def test_large_snowflakes_size(self):
# Test for PR #471
# Snowflakes size is achieved via downscaling. Large values for
# snowflakes_size lead to more downscaling. Hence, values close to 1.0
# incur risk that the image is downscaled to (0, 0) or similar values.
aug = iaa.SnowflakesLayer(
density=0.95,
density_uniformity=0.5,
flake_size=1.0,
flake_size_uniformity=0.5,
angle=0.0,
speed=0.5,
blur_sigma_fraction=0.001
)
nb_seen = 0
for _ in np.arange(50):
image = np.zeros((16, 16, 3), dtype=np.uint8)
image_aug = aug.augment_image(image)
assert np.std(image_aug) < 1
if np.average(image_aug) > 128:
nb_seen += 1
assert nb_seen > 30 # usually around 45
# only a very rough test here currently, because the augmenter is fairly hard
# to test
# TODO add more tests, improve testability
class TestRain(unittest.TestCase):
def setUp(self):
reseed()
@classmethod
def _test_very_roughly(cls, nb_channels):
if nb_channels is None:
img = np.zeros((100, 100), dtype=np.uint8)
else:
img = np.zeros((100, 100, nb_channels), dtype=np.uint8)
imgs_aug = iaa.Rain()(images=[img] * 5)
assert 5 < np.average(imgs_aug) < 200
assert np.max(imgs_aug) > 70
for img_aug in imgs_aug:
img_aug_f32 = img_aug.astype(np.float32)
grad_x = img_aug_f32[:, 1:] - img_aug_f32[:, :-1]
grad_y = img_aug_f32[1:, :] - img_aug_f32[:-1, :]
assert np.sum(np.abs(grad_x)) > 10 * img.shape[1]
assert np.sum(np.abs(grad_y)) > 10 * img.shape[0]
def test_very_roughly_three_channels(self):
self._test_very_roughly(3)
def test_very_roughly_one_channel(self):
self._test_very_roughly(1)
def test_very_roughly_no_channels(self):
self._test_very_roughly(None)
def test_zero_sized_axes(self):
shapes = [
(0, 0, 3),
(0, 1, 3),
(1, 0, 3)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
aug = iaa.Rain()
image_aug = aug(image=image)
assert image_aug.dtype.name == "uint8"
assert image_aug.shape == shape
def test_pickleable(self):
aug = iaa.Rain(seed=1)
runtest_pickleable_uint8_img(aug, iterations=3, shape=(20, 20, 3))
================================================
FILE: test/requirements.txt
================================================
# 4.6.0 currently causes crashes, see https://github.com/pytest-dev/pytest/issues/5358
pytest>=3.0.5,<4.6.0
#
# add subTest() support for pytest.
# only available for py3.4+
pytest-subtests; python_version >= '3.4'
mock; python_version < '3.3' # unittest.mock does not exist in older versions
unittest2; python_version < '3.4' # in 3.4, self.subTest was added
xdoctest >= 0.7.2
# used in imgaug.augmenters.imgcorrupt
# that library has scikit-image 15+ as a requirement, which does not exist
# in <=3.4, so it is not tested here
imagecorruptions; python_version >= '3.5'
================================================
FILE: test/run_doctests.sh
================================================
#!/bin/bash
# Note: requires xdoctest 0.7.0
xdoctest imgaug --global-exec="import imgaug as ia\nfrom imgaug import augmenters as iaa"
================================================
FILE: test/run_tests.sh
================================================
#!/bin/bash
# This is expected to be executed from /imgaug, not from /imgaug/test.
# That way it is ensured that you use the current code of the library in
# the imgaug/imgaug/ subfolder, not the installed version of the library.
#
# The command below executes all tests.
# To execute only one specific test, use e.g.
# pytest ./test/augmenters/test_geometric.py::TestRot90::test_empty_polygons
python -m pytest ./test --verbose --xdoctest-modules -s --durations=20 -Walways
================================================
FILE: test/test_data.py
================================================
from __future__ import print_function, division, absolute_import
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import imgaug as ia
import imgaug.data as iadata
from imgaug.data import _quokka_normalize_extract, _compute_resized_shape
class Test__quokka_normalize_extract(unittest.TestCase):
def test_string_square(self):
observed = _quokka_normalize_extract("square")
assert isinstance(observed, ia.BoundingBox)
assert observed.x1 == 0
assert observed.y1 == 0
assert observed.x2 == 643
assert observed.y2 == 643
def test_tuple(self):
observed = _quokka_normalize_extract((1, 1, 644, 642))
assert isinstance(observed, ia.BoundingBox)
assert observed.x1 == 1
assert observed.y1 == 1
assert observed.x2 == 644
assert observed.y2 == 642
def test_boundingbox(self):
observed = _quokka_normalize_extract(ia.BoundingBox(x1=1, y1=1, x2=644, y2=642))
assert isinstance(observed, ia.BoundingBox)
assert observed.x1 == 1
assert observed.y1 == 1
assert observed.x2 == 644
assert observed.y2 == 642
def test_boundingboxesonimage(self):
observed = _quokka_normalize_extract(
ia.BoundingBoxesOnImage([
ia.BoundingBox(x1=1, y1=1, x2=644, y2=642)
],
shape=(643, 960, 3)
)
)
assert isinstance(observed, ia.BoundingBox)
assert observed.x1 == 1
assert observed.y1 == 1
assert observed.x2 == 644
assert observed.y2 == 642
def test_wrong_input_type(self):
got_exception = False
try:
_ = _quokka_normalize_extract(False)
except Exception as exc:
assert "Expected 'square' or tuple" in str(exc)
got_exception = True
assert got_exception
class Test__compute_resized_shape(unittest.TestCase):
def test_to_shape_is_tuple_of_ints_2d(self):
from_shape = (10, 15, 3)
to_shape = (20, 30)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (20, 30, 3)
def test_to_shape_is_tuple_of_ints_3d(self):
from_shape = (10, 15, 3)
to_shape = (20, 30, 3)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (20, 30, 3)
def test_to_shape_is_tuple_of_floats(self):
from_shape = (10, 15, 3)
to_shape = (2.0, 3.0)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (20, 45, 3)
def test_to_shape_is_float_and_int(self):
# tuple of int and float
from_shape = (10, 15, 3)
to_shape = (2.0, 25)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (20, 25, 3)
def test_to_shape_is_int_and_float(self):
from_shape = (10, 17, 3)
to_shape = (15, 2.0)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (15, 34, 3)
def test_to_shape_is_none(self):
from_shape = (10, 10, 3)
to_shape = None
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == from_shape
def test_to_shape_is_int_and_none(self):
from_shape = (10, 15, 3)
to_shape = (2.0, None)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (20, 15, 3)
def test_to_shape_is_none_and_int(self):
from_shape = (10, 15, 3)
to_shape = (None, 25)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (10, 25, 3)
def test_to_shape_is_single_int(self):
from_shape = (10, 15, 3)
to_shape = 20
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (20, 20, 3)
def test_to_shape_is_float(self):
from_shape = (10, 15, 3)
to_shape = 2.0
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (20, 30, 3)
def test_from_shape_and_to_shape_are_arrays(self):
# from/to shape as arrays
from_shape = (10, 10, 3)
to_shape = (20, 30, 3)
observed = _compute_resized_shape(
np.zeros(from_shape),
np.zeros(to_shape)
)
assert observed == to_shape
def test_from_shape_is_2d_and_to_shape_is_2d(self):
# from_shape is 2D
from_shape = (10, 15)
to_shape = (20, 30)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == to_shape
def test_from_shape_is_2d_and_to_shape_is_3d(self):
from_shape = (10, 15)
to_shape = (20, 30, 3)
observed = _compute_resized_shape(from_shape, to_shape)
assert observed == (20, 30, 3)
# we are intentionally a bit looser here with atol=0.1, because
# apparently on some systems there are small differences in what
# exactly is loaded, see issue #414
class Test_quokka(unittest.TestCase):
def test_no_parameters(self):
img = iadata.quokka()
assert img.shape == (643, 960, 3)
assert np.allclose(
np.average(img, axis=(0, 1)),
[107.93576659, 118.18765066, 122.99378564],
rtol=0, atol=0.1
)
def test_extract_square(self):
img = iadata.quokka(extract="square")
assert img.shape == (643, 643, 3)
assert np.allclose(
np.average(img, axis=(0, 1)),
[111.25929196, 121.19431175, 125.71316898],
rtol=0, atol=0.1
)
def test_size_tuple_of_ints(self):
img = iadata.quokka(size=(642, 959))
assert img.shape == (642, 959, 3)
assert np.allclose(
np.average(img, axis=(0, 1)),
[107.84615822, 118.09832412, 122.90446467],
rtol=0, atol=0.1
)
# we are intentionally a bit looser here with atol=0.1, because apparently
# on some systems there are small differences in what exactly is loaded,
# see issue #414
class Test_quokka_square(unittest.TestCase):
def test_standard_call(self):
img = iadata.quokka_square()
assert img.shape == (643, 643, 3)
assert np.allclose(
np.average(img, axis=(0, 1)),
[111.25929196, 121.19431175, 125.71316898],
rtol=0, atol=0.1
)
# we are intentionally a bit looser here with atol=0.1, because apparently
# on some systems there are small differences in what exactly is loaded,
# see issue #414
class Test_quokka_heatmap(unittest.TestCase):
def test_no_parameters(self):
hm = iadata.quokka_heatmap()
assert hm.shape == (643, 960, 3)
assert hm.arr_0to1.shape == (643, 960, 1)
assert np.allclose(
np.average(hm.arr_0to1),
0.57618505,
rtol=0,
atol=1e-3
)
def test_extract_square(self):
hm = iadata.quokka_heatmap(extract="square")
assert hm.shape == (643, 643, 3)
assert hm.arr_0to1.shape == (643, 643, 1)
# TODO this value is 0.48026073 in python 2.7, while 0.48026952 in
# 3.7 -- why?
assert np.allclose(
np.average(hm.arr_0to1),
0.48026952,
rtol=0,
atol=1e-3
)
def test_size_tuple_of_ints(self):
hm = iadata.quokka_heatmap(size=(642, 959))
assert hm.shape == (642, 959, 3)
assert hm.arr_0to1.shape == (642, 959, 1)
assert np.allclose(
np.average(hm.arr_0to1),
0.5762454,
rtol=0,
atol=1e-3
)
class Test_quokka_segmentation_map(unittest.TestCase):
def test_no_parameters(self):
segmap = iadata.quokka_segmentation_map()
assert segmap.shape == (643, 960, 3)
assert segmap.arr.shape == (643, 960, 1)
assert np.allclose(np.average(segmap.arr), 0.3016427, rtol=0, atol=1e-3)
def test_extract_square(self):
segmap = iadata.quokka_segmentation_map(extract="square")
assert segmap.shape == (643, 643, 3)
assert segmap.arr.shape == (643, 643, 1)
assert np.allclose(np.average(segmap.arr), 0.450353, rtol=0, atol=1e-3)
def test_size_is_tuple_of_ints(self):
segmap = iadata.quokka_segmentation_map(size=(642, 959))
assert segmap.shape == (642, 959, 3)
assert segmap.arr.shape == (642, 959, 1)
assert np.allclose(np.average(segmap.arr), 0.30160266, rtol=0, atol=1e-3)
class Test_quokka_keypoints(unittest.TestCase):
def test_non_parameters(self):
kpsoi = iadata.quokka_keypoints()
assert len(kpsoi.keypoints) > 0
assert np.allclose(kpsoi.keypoints[0].x, 163.0)
assert np.allclose(kpsoi.keypoints[0].y, 78.0)
assert kpsoi.shape == (643, 960, 3)
def test_non_square_vs_square(self):
kpsoi = iadata.quokka_keypoints()
img = iadata.quokka()
patches = []
for kp in kpsoi.keypoints:
bb = ia.BoundingBox(x1=kp.x-1, x2=kp.x+2, y1=kp.y-1, y2=kp.y+2)
patches.append(bb.extract_from_image(img))
img_square = iadata.quokka(extract="square")
kpsoi_square = iadata.quokka_keypoints(extract="square")
assert len(kpsoi.keypoints) == len(kpsoi_square.keypoints)
assert kpsoi_square.shape == (643, 643, 3)
for kp, patch in zip(kpsoi_square.keypoints, patches):
bb = ia.BoundingBox(x1=kp.x-1, x2=kp.x+2, y1=kp.y-1, y2=kp.y+2)
patch_square = bb.extract_from_image(img_square)
assert np.average(
np.abs(
patch.astype(np.float32)
- patch_square.astype(np.float32)
)
) < 1.0
def test_size_is_tuple_of_ints(self):
kpsoi = iadata.quokka_keypoints()
kpsoi_resized = iadata.quokka_keypoints(size=(642, 959))
assert kpsoi_resized.shape == (642, 959, 3)
assert len(kpsoi.keypoints) == len(kpsoi_resized.keypoints)
for kp, kp_resized in zip(kpsoi.keypoints, kpsoi_resized.keypoints):
d = np.sqrt(
(kp.x - kp_resized.x) ** 2
+ (kp.y - kp_resized.y) ** 2
)
assert d < 1.0
class Test_quokka_bounding_boxes(unittest.TestCase):
def test_no_parameters(self):
bbsoi = iadata.quokka_bounding_boxes()
assert len(bbsoi.bounding_boxes) > 0
bb0 = bbsoi.bounding_boxes[0]
assert np.allclose(bb0.x1, 148.0)
assert np.allclose(bb0.y1, 50.0)
assert np.allclose(bb0.x2, 550.0)
assert np.allclose(bb0.y2, 642.0)
assert bbsoi.shape == (643, 960, 3)
def test_non_square_vs_square(self):
bbsoi = iadata.quokka_bounding_boxes()
img = iadata.quokka()
patches = []
for bb in bbsoi.bounding_boxes:
patches.append(bb.extract_from_image(img))
img_square = iadata.quokka(extract="square")
bbsoi_square = iadata.quokka_bounding_boxes(extract="square")
assert len(bbsoi.bounding_boxes) == len(bbsoi_square.bounding_boxes)
assert bbsoi_square.shape == (643, 643, 3)
for bb, patch in zip(bbsoi_square.bounding_boxes, patches):
patch_square = bb.extract_from_image(img_square)
assert np.average(
np.abs(
patch.astype(np.float32)
- patch_square.astype(np.float32)
)
) < 1.0
def test_size_is_tuple_of_ints(self):
bbsoi = iadata.quokka_bounding_boxes()
bbsoi_resized = iadata.quokka_bounding_boxes(size=(642, 959))
assert bbsoi_resized.shape == (642, 959, 3)
assert len(bbsoi.bounding_boxes) == len(bbsoi_resized.bounding_boxes)
for bb, bb_resized in zip(bbsoi.bounding_boxes,
bbsoi_resized.bounding_boxes):
d = np.sqrt(
(bb.center_x - bb_resized.center_x) ** 2
+ (bb.center_y - bb_resized.center_y) ** 2
)
assert d < 1.0
================================================
FILE: test/test_dtypes.py
================================================
from __future__ import print_function, division, absolute_import
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
from imgaug import dtypes as iadt
from imgaug.testutils import ensure_deprecation_warning
class Test_normalize_dtypes(unittest.TestCase):
@mock.patch("imgaug.dtypes.normalize_dtype")
def test_single_non_list(self, mock_nd):
mock_nd.return_value = "foo"
dtypes = iadt.normalize_dtypes("int16")
assert dtypes == ["foo"]
assert mock_nd.call_count == 1
assert mock_nd.call_args_list[0][0][0] == "int16"
def test_single_dtype(self):
dtypes = iadt.normalize_dtypes(np.dtype("int16"))
assert isinstance(dtypes, list)
assert len(dtypes) == 1
assert isinstance(dtypes[0], np.dtype)
assert dtypes[0].name == "int16"
def test_empty_list(self):
dtypes = iadt.normalize_dtypes([])
assert isinstance(dtypes, list)
assert len(dtypes) == 0
@mock.patch("imgaug.dtypes.normalize_dtype")
def test_list_of_dtype_names(self, mock_nd):
mock_nd.return_value = "foo"
dtypes = iadt.normalize_dtypes(["int16", "int32"])
assert dtypes == ["foo", "foo"]
assert mock_nd.call_count == 2
assert mock_nd.call_args_list[0][0][0] == "int16"
assert mock_nd.call_args_list[1][0][0] == "int32"
class Test_normalize_dtype(unittest.TestCase):
def test_dtype(self):
dtype = iadt.normalize_dtype(np.dtype("int16"))
assert isinstance(dtype, np.dtype)
assert dtype.name == "int16"
def test_dtype_name(self):
dtype = iadt.normalize_dtype("int16")
assert isinstance(dtype, np.dtype)
assert dtype.name == "int16"
def test_dtype_name_short(self):
dtype = iadt.normalize_dtype("i2")
assert isinstance(dtype, np.dtype)
assert dtype.name == "int16"
def test_dtype_function(self):
dtype = iadt.normalize_dtype(np.int16)
assert isinstance(dtype, np.dtype)
assert dtype.name == "int16"
def test_ndarray(self):
arr = np.zeros((1,), dtype=np.int16)
dtype = iadt.normalize_dtype(arr)
assert isinstance(dtype, np.dtype)
assert dtype.name == "int16"
def test_numpy_scalar(self):
scalar = np.int16(0)
dtype = iadt.normalize_dtype(scalar)
assert isinstance(dtype, np.dtype)
assert dtype.name == "int16"
# change_dtype_() is already indirectly tested via Test_change_dtypes_(),
# so were don't have to be very thorough here
class Test_change_dtype_(unittest.TestCase):
def test_no_clip_no_round(self):
arr = np.array([[0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0]],
dtype=np.float32)
dtype = np.int8
observed = iadt.change_dtype_(np.copy(arr), dtype,
clip=False, round=False)
expected = np.array([[0, 0, 0, -128+1-1, 127-1+1]], dtype=np.int8)
assert observed.dtype.name == "int8"
assert np.array_equal(observed, expected)
def test_clip_and_round(self):
arr = np.array([[0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0]],
dtype=np.float32)
dtype = np.int8
observed = iadt.restore_dtypes_(np.copy(arr), dtype)
expected = np.array([[0, 0, 1, 127, -128]], dtype=np.int8)
assert observed.dtype.name == "int8"
assert np.array_equal(observed, expected)
def test_dtype_not_changed(self):
arr = np.array([[-128, -1, 0, 1, 127]], dtype=np.int8)
dtype = np.int8
observed = iadt.restore_dtypes_(arr, dtype,
clip=False, round=False)
assert observed is arr
@mock.patch('numpy.round')
def test_no_round_if_dtype_not_changed(self, mock_round):
arr = np.array([[-128, -1, 0, 1, 127]], dtype=np.int8)
dtype = np.int8
observed = iadt.restore_dtypes_(arr, dtype, clip=False)
assert observed is arr
assert mock_round.call_count == 0
def test_round_float_dtypes(self):
arr = np.array([[-128, -1.1, 0.7, 1.1, 127]], dtype=np.float32)
dtype = np.int8
observed = iadt.restore_dtypes_(np.copy(arr), dtype, clip=False)
expected = np.array([[-128, -1, 1, 1, 127]], dtype=np.int8)
assert observed.dtype.name == "int8"
assert np.array_equal(observed, expected)
@mock.patch('numpy.round')
def test_dont_round_non_float_dtypes(self, mock_round):
arr = np.array([[-128, -1, 0, 1, 127]], dtype=np.int8)
dtype = np.float32
_ = iadt.restore_dtypes_(np.copy(arr), dtype, clip=False)
assert mock_round.call_count == 0
def test_int16_to_int8(self):
arr = np.zeros((1,), dtype=np.int16) + 1
observed = iadt.change_dtype_(arr, np.int8, clip=False, round=False)
assert observed.shape == (1,)
assert observed.dtype.name == "int8"
assert np.all(observed == 1)
def test_int16_to_int8_with_overflow(self):
arr = np.zeros((1,), dtype=np.int16) + 128
observed = iadt.change_dtype_(arr, np.int8, clip=False, round=False)
assert observed.shape == (1,)
assert observed.dtype.name == "int8"
assert np.all(observed == -128)
def test_float32_to_int8(self):
arr = np.zeros((1,), dtype=np.int32) + 1
observed = iadt.change_dtype_(arr, np.int8, clip=False, round=False)
assert observed.shape == (1,)
assert observed.dtype.name == "int8"
assert np.all(observed == 1)
def test_float32_to_int8_with_overflow(self):
arr = np.zeros((1,), dtype=np.int32) + 1
observed = iadt.change_dtype_(arr, np.int8, clip=False, round=False)
assert observed.shape == (1,)
assert observed.dtype.name == "int8"
assert np.all(observed == 1)
def test_dtype_given_as_string(self):
arr = np.zeros((1,), dtype=np.int8) + 1
observed = iadt.change_dtype_(arr, "int16", clip=False, round=False)
assert observed.shape == (1,)
assert observed.dtype.name == "int16"
assert np.all(observed == 1)
class Test_change_dtypes_(unittest.TestCase):
def test_array_input_single_dtype_no_clip_no_round(self):
arr = np.array([[0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0]],
dtype=np.float32)
dtype = np.int8
observed = iadt.restore_dtypes_(np.copy(arr), dtype,
clip=False, round=False)
expected = np.array([[0, 0, 0, -128+1-1, 127-1+1]], dtype=np.int8)
assert observed.dtype.name == "int8"
assert np.array_equal(observed, expected)
def test_array_input_single_dtype_with_clip_no_round(self):
arr = np.array([[0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0]],
dtype=np.float32)
dtype = np.int8
observed = iadt.restore_dtypes_(np.copy(arr), dtype,
clip=True, round=False)
expected = np.array([[0, 0, 0, 127, -128]], dtype=np.int8)
assert observed.dtype.name == "int8"
assert np.array_equal(observed, expected)
def test_array_input_single_dtype_no_clip_with_round(self):
arr = np.array([[0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0]],
dtype=np.float32)
dtype = np.int8
observed = iadt.restore_dtypes_(np.copy(arr), dtype,
clip=False, round=True)
expected = np.array([[0, 0, 1, -128+1-1, 127-1+1]], dtype=np.int8)
assert observed.dtype.name == "int8"
assert np.array_equal(observed, expected)
def test_array_input_fail_if_many_different_dtypes(self):
arr = np.array([
[0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0],
[0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0],
], dtype=np.float32)
dtypes = [np.int8, np.int16]
with self.assertRaises(AssertionError) as context:
_observed = iadt.restore_dtypes_(np.copy(arr), dtypes,
clip=False, round=False)
assert (
"or an iterable of N times the *same* dtype"
in str(context.exception)
)
def test_array_input_many_dtypes_no_clip_no_round(self):
arr = np.array([
[0.0, 0.1, 0.9, 127.0+0.0, -128.0-0.0],
[0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0],
], dtype=np.float32)
dtypes = [np.int8, np.int8]
observed = iadt.restore_dtypes_(np.copy(arr), dtypes,
clip=False, round=False)
expected = np.array([
[0, 0, 0, 127, -128],
[0, 0, 0, -128+1-1, 127-1+1]
], dtype=np.int8)
assert observed.dtype.name == "int8"
assert np.array_equal(observed, expected)
def test_empty_array_input(self):
arr = np.zeros((0, 5), dtype=np.float32)
dtypes = np.int8
observed = iadt.restore_dtypes_(np.copy(arr), dtypes,
clip=False, round=False)
assert observed.dtype.name == "int8"
assert observed.shape == (0, 5)
def test_empty_list_input(self):
arrs = []
dtypes = np.int8
observed = iadt.restore_dtypes_(arrs, dtypes,
clip=False, round=False)
assert len(observed) == 0
def test_many_items_list_input_single_dtype(self):
arrs = [
np.array([0.0, 0.1, 0.9, 127.0+0.0, -128.0-0.0], dtype=np.float32),
np.array([0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0], dtype=np.float32)
]
dtypes = np.int8
observed = iadt.restore_dtypes_(
[np.copy(arr) for arr in arrs],
dtypes,
clip=False,
round=False)
expected = [
np.array([0, 0, 0, 127, -128], dtype=np.int8),
np.array([0, 0, 0, -128+1-1, 127-1+1], dtype=np.int8)
]
assert len(observed) == 2
assert observed[0].dtype.name == "int8"
assert observed[1].dtype.name == "int8"
assert np.array_equal(observed[0], expected[0])
assert np.array_equal(observed[1], expected[1])
def test_many_items_list_input_many_dtypes(self):
arrs = [
np.array([0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0], dtype=np.float32),
np.array([0.0, 0.1, 0.9, 127.0+1.0, -128.0-1.0], dtype=np.float32)
]
dtypes = [np.int8, np.int16]
observed = iadt.restore_dtypes_(
[np.copy(arr) for arr in arrs],
dtypes,
clip=False,
round=False)
expected = [
np.array([0, 0, 0, -128+1-1, 127-1+1], dtype=np.int8),
np.array([0, 0, 0, 127+1, -128-1], dtype=np.int16)
]
assert len(observed) == 2
assert observed[0].dtype.name == "int8"
assert observed[1].dtype.name == "int16"
assert np.array_equal(observed[0], expected[0])
assert np.array_equal(observed[1], expected[1])
def test_invalid_input(self):
arr = False
with self.assertRaises(Exception) as context:
_ = iadt.restore_dtypes_(arr, np.int8)
assert "Expected numpy array or " in str(context.exception)
def test_int_to_float(self):
arr = np.array([[-100, -1, 0, 1, 100]], dtype=np.int8)
dtype = np.float32
observed = iadt.restore_dtypes_(np.copy(arr), dtype,
clip=False, round=False)
expected = np.array([[-100.0, -1.0, 0.0, 1.0, 100.0]],
dtype=np.float32)
assert observed.dtype.name == "float32"
assert np.allclose(observed, expected)
def test_increase_float_resolution(self):
arr = np.array([[-100.0, -1.0, 0.0, 1.0, 100.0]], dtype=np.float32)
dtype = np.float64
observed = iadt.restore_dtypes_(np.copy(arr), dtype,
clip=False, round=False)
expected = np.array([[-100.0, -1.0, 0.0, 1.0, 100.0]],
dtype=np.float32)
assert observed.dtype.name == "float64"
assert np.allclose(observed, expected)
def test_int_to_uint(self):
arr = np.array([[-100, -1, 0, 1, 100]], dtype=np.int8)
dtype = np.uint8
observed = iadt.restore_dtypes_(np.copy(arr), dtype,
clip=False, round=False)
expected = np.array([[255-100+1, 255-1+1, 0, 1, 100]],
dtype=np.uint8)
assert observed.dtype.name == "uint8"
assert np.allclose(observed, expected)
def test_int_to_uint_with_clip(self):
arr = np.array([[-100, -1, 0, 1, 100]], dtype=np.int8)
dtype = np.uint8
observed = iadt.restore_dtypes_(np.copy(arr), dtype,
clip=True, round=False)
expected = np.array([[0, 0, 0, 1, 100]], dtype=np.uint8)
assert observed.dtype.name == "uint8"
assert np.allclose(observed, expected)
# TODO is the copy_* function still used anywhere
class Test_copy_dtypes_for_restore(unittest.TestCase):
def test_images_as_list(self):
# TODO using dtype=np.bool is causing this to fail as it ends up
# being instead of .
# Any problems from that for the library?
images = [
np.zeros((1, 1, 3), dtype=np.uint8),
np.zeros((10, 16, 3), dtype=np.float32),
np.zeros((20, 10, 6), dtype=np.int32)
]
dtypes_copy = iadt.copy_dtypes_for_restore(images, force_list=False)
assert np.all([
dtype_observed.name == dtype_expected
for dtype_observed, dtype_expected
in zip(
dtypes_copy,
["uint8", "float32", "int32"]
)
])
def test_images_as_single_array(self):
dts = ["uint8", "float32", "int32"]
for dt in dts:
with self.subTest(dtype=dt):
images = np.zeros((10, 16, 32, 3), dtype=dt)
dtypes_copy = iadt.copy_dtypes_for_restore(images)
assert isinstance(dtypes_copy, np.dtype)
assert dtypes_copy.name == dt
def test_images_as_single_array_force_list(self):
dts = ["uint8", "float32", "int32"]
for dt in dts:
with self.subTest(dtype=dt):
images = np.zeros((10, 16, 32, 3), dtype=dt)
dtypes_copy = iadt.copy_dtypes_for_restore(images,
force_list=True)
assert isinstance(dtypes_copy, list)
assert np.all([dtype_i.name == dt for dtype_i in dtypes_copy])
class Test_increase_itemsize_of_dtype(unittest.TestCase):
def test_factor_is_1(self):
dts = [
np.int8, np.int16, np.int32, np.int64,
np.uint8, np.uint16, np.uint32, np.uint64,
np.float16, np.float32, np.float64
]
for dt in dts:
dt = np.dtype(dt)
with self.subTest(dtype=dt.name):
dt_increased = iadt.increase_itemsize_of_dtype(dt, 1)
assert dt_increased.name == dt.name
def test_factor_is_2(self):
dts = [
np.int8, np.int16, np.int32,
np.uint8, np.uint16, np.uint32,
np.float16, np.float32
]
expecteds = [
np.int16, np.int32, np.int64,
np.uint16, np.uint32, np.uint64,
np.float32, np.float64
]
for dt, expected in zip(dts, expecteds):
dt = np.dtype(dt)
expected = np.dtype(expected)
with self.subTest(dtype=dt.name):
dt_increased = iadt.increase_itemsize_of_dtype(dt, 2)
assert dt_increased.name == expected.name
def test_dtype_as_string(self):
dt_names = [
"int8", "int16", "int32",
"uint8", "uint16", "uint32",
"float16", "float32"
]
expecteds = [
np.int16, np.int32, np.int64,
np.uint16, np.uint32, np.uint64,
np.float32, np.float64
]
for dt_name, expected in zip(dt_names, expecteds):
expected = np.dtype(expected)
with self.subTest(dtype=dt_name):
dt_increased = iadt.increase_itemsize_of_dtype(dt_name, 2)
assert dt_increased.name == expected.name
def test_unknown_dtype(self):
with self.assertRaises(TypeError) as context:
_ = iadt.increase_itemsize_of_dtype(np.uint64, 2)
assert (
"Unable to create a numpy dtype matching"
in str(context.exception))
class Test_get_minimal_dtype(unittest.TestCase):
def test_with_dtype_function(self):
dt_funcs = [
np.int8, np.int16, np.int32, np.int64,
np.uint8, np.uint16, np.uint32, np.uint64,
np.float16, np.float32, np.float64,
np.bool_
]
for dt_func in dt_funcs:
with self.subTest(dtype=np.dtype(dt_func).name):
inputs = [dt_func]
promoted_dt = iadt.get_minimal_dtype(inputs)
assert promoted_dt.name == np.dtype(dt_func).name
def test_with_lists_of_identical_dtypes(self):
dts = [
np.int8, np.int16, np.int32, np.int64,
np.uint8, np.uint16, np.uint32, np.uint64,
np.float16, np.float32, np.float64,
np.bool_
]
for dt in dts:
dt = np.dtype(dt)
for length in [1, 2, 3]:
with self.subTest(dtype=dt.name, length=length):
inputs = [dt for _ in range(length)]
promoted_dt = iadt.get_minimal_dtype(inputs)
assert promoted_dt.name == dt.name
def test_with_lists_of_identical_dtype_arrays(self):
dts = [
np.int8, np.int16, np.int32, np.int64,
np.uint8, np.uint16, np.uint32, np.uint64,
np.float16, np.float32, np.float64,
np.bool_
]
for dt in dts:
dt = np.dtype(dt)
for length in [1, 2, 3]:
with self.subTest(dtype=dt.name, length=length):
inputs = [np.zeros((1, 1, 3), dtype=dt)
for _ in range(length)]
promoted_dt = iadt.get_minimal_dtype(inputs)
assert promoted_dt.name == dt.name
def test_with_lists_of_different_arrays(self):
dt_lists = [
[np.uint8, np.uint16],
[np.uint8, np.uint32],
[np.uint8, np.int8],
[np.uint8, np.bool_],
[np.int8, np.int16],
[np.float16, np.float32],
[np.uint8, np.float32],
[np.uint8, np.int8, np.int16],
[np.uint8, np.int8, np.bool_],
[np.uint8, np.int8, np.float32],
]
expecteds = [
np.uint16,
np.uint32,
np.int16,
np.uint8,
np.int16,
np.float32,
np.float32,
np.int16,
np.int16,
np.float32
]
for dt_list, expected in zip(dt_lists, expecteds):
expected = np.dtype(expected)
dt_list = [np.dtype(dt) for dt in dt_list]
dt_names = ", ".join([dt.name for dt in dt_list])
with self.subTest(dtypes=dt_names):
promoted_dt = iadt.get_minimal_dtype(dt_list)
assert promoted_dt.name == expected.name
@mock.patch("imgaug.dtypes.increase_itemsize_of_dtype")
def test_calls_increase_itemsize_factor(self, mock_iibf):
dt = np.int8
factor = 2
_ = iadt.get_minimal_dtype([dt], factor)
assert mock_iibf.call_count == 1
class Test_promote_array_dtypes_(unittest.TestCase):
@mock.patch("imgaug.dtypes.get_minimal_dtype")
@mock.patch("imgaug.dtypes.change_dtypes_")
def test_calls_subfunctions(self, mock_cd, mock_gmd):
mock_gmd.return_value = np.dtype("int16")
mock_cd.return_value = "foo"
arrays = [np.zeros((1,), dtype=np.int8)]
observed = iadt.promote_array_dtypes_(arrays)
assert mock_gmd.call_count == 1
assert mock_cd.call_count == 1
# call 0, args, arg 0, dtype 0
assert mock_gmd.call_args_list[0][0][0][0].name == "int8"
assert mock_gmd.call_args_list[0][1]["increase_itemsize_factor"] == 1
assert mock_cd.call_args_list[0][0][0] is arrays
assert observed == "foo"
@mock.patch("imgaug.dtypes.get_minimal_dtype")
@mock.patch("imgaug.dtypes.change_dtypes_")
def test_calls_subfunctions_dtypes_set(self, mock_cd, mock_gmd):
mock_gmd.return_value = np.dtype("int16")
mock_cd.return_value = "foo"
arrays = [np.zeros((1,), dtype=np.int8)]
observed = iadt.promote_array_dtypes_(
arrays,
dtypes=["float32"])
assert mock_gmd.call_count == 1
assert mock_cd.call_count == 1
# call 0, args, arg 0, dtype 0
assert mock_gmd.call_args_list[0][0][0][0] == "float32"
assert mock_gmd.call_args_list[0][1]["increase_itemsize_factor"] == 1
assert mock_cd.call_args_list[0][0][0] is arrays
assert observed == "foo"
@mock.patch("imgaug.dtypes.get_minimal_dtype")
@mock.patch("imgaug.dtypes.change_dtypes_")
def test_calls_subfunctions_increase_itemsize_factor_set(self, mock_cd,
mock_gmd):
mock_gmd.return_value = np.dtype("int16")
mock_cd.return_value = "foo"
arrays = [np.zeros((1,), dtype=np.int8)]
observed = iadt.promote_array_dtypes_(
arrays,
increase_itemsize_factor=2)
assert mock_gmd.call_count == 1
assert mock_cd.call_count == 1
# call 0, args, arg 0, dtype 0
assert mock_gmd.call_args_list[0][0][0][0].name == "int8"
assert mock_gmd.call_args_list[0][1]["increase_itemsize_factor"] == 2
assert mock_cd.call_args_list[0][0][0] is arrays
assert observed == "foo"
def test_promote_single_array(self):
arr = np.zeros((1,), dtype=np.int8)
observed = iadt.promote_array_dtypes_(arr)
assert observed.dtype.name == "int8"
def test_promote_single_array_single_dtype_set(self):
arr = np.zeros((1,), dtype=np.int8)
observed = iadt.promote_array_dtypes_(arr, np.int16)
assert observed.dtype.name == "int16"
def test_promote_single_array_dtypes_set(self):
arr = np.zeros((1,), dtype=np.int8)
observed = iadt.promote_array_dtypes_(arr, [np.int16])
assert observed.dtype.name == "int16"
def test_promote_single_array_increase_itemsize_factor_set(self):
arr = np.zeros((1,), dtype=np.int8)
observed = iadt.promote_array_dtypes_(arr, increase_itemsize_factor=2)
assert observed.dtype.name == "int16"
def test_promote_list_of_single_array(self):
arrays = [np.zeros((1,), dtype=np.int8)]
observed = iadt.promote_array_dtypes_(arrays,
increase_itemsize_factor=2)
assert observed[0].dtype.name == "int16"
def test_promote_list_of_two_arrays(self):
arrays = [np.zeros((1,), dtype=np.int8),
np.zeros((1,), dtype=np.int16)]
observed = iadt.promote_array_dtypes_(arrays,
increase_itemsize_factor=2)
assert observed[0].dtype.name == "int32"
assert observed[1].dtype.name == "int32"
def test_promote_list_of_two_arrays_dtypes_set(self):
arrays = [np.zeros((1,), dtype=np.int8),
np.zeros((1,), dtype=np.int16)]
observed = iadt.promote_array_dtypes_(arrays,
dtypes=[np.float32, np.float64])
assert observed[0].dtype.name == "float64"
assert observed[1].dtype.name == "float64"
def test_promote_list_of_three_arrays(self):
arrays = [np.zeros((1,), dtype=np.int8),
np.zeros((1,), dtype=np.int16),
np.zeros((1,), dtype=np.uint8)]
observed = iadt.promote_array_dtypes_(arrays,
increase_itemsize_factor=2)
assert observed[0].dtype.name == "int32"
assert observed[1].dtype.name == "int32"
assert observed[2].dtype.name == "int32"
class Test_increase_array_resolutions_(unittest.TestCase):
def test_single_array_factor_1(self):
arr = np.zeros((1,), dtype=np.int8)
observed = iadt.increase_array_resolutions_(arr, 1)
assert observed.dtype.name == "int8"
def test_single_array_factor_2(self):
arr = np.zeros((1,), dtype=np.int8)
observed = iadt.increase_array_resolutions_(arr, 2)
assert observed.dtype.name == "int16"
def test_list_of_one_array(self):
arr = np.zeros((1,), dtype=np.int8)
observed = iadt.increase_array_resolutions_([arr], 2)
assert observed[0].dtype.name == "int16"
def test_list_of_two_arrays(self):
arrays = [
np.zeros((1,), dtype=np.int8),
np.zeros((1,), dtype=np.int16)
]
observed = iadt.increase_array_resolutions_(arrays, 2)
assert observed[0].dtype.name == "int16"
assert observed[1].dtype.name == "int32"
class Test_get_value_range_of_dtype(unittest.TestCase):
def test_bool(self):
minv, center, maxv = iadt.get_value_range_of_dtype(np.dtype(bool))
assert minv == 0
assert center is None
assert maxv == 1
def test_uint8_string_name(self):
assert (
iadt.get_value_range_of_dtype("uint8")
== iadt.get_value_range_of_dtype(np.dtype("uint8"))
)
def test_uint8(self):
minv, center, maxv = iadt.get_value_range_of_dtype(np.dtype("uint8"))
assert minv == 0
assert np.isclose(center, 0.5*255)
assert maxv == 255
def test_uint16(self):
minv, center, maxv = iadt.get_value_range_of_dtype(np.dtype("uint16"))
assert minv == 0
assert np.isclose(center, 0.5*65535)
assert maxv == 65535
def test_int8(self):
minv, center, maxv = iadt.get_value_range_of_dtype(np.dtype("int8"))
assert minv == -128
assert np.isclose(center, -0.5)
assert maxv == 127
def test_int16(self):
minv, center, maxv = iadt.get_value_range_of_dtype(np.dtype("int16"))
assert minv == -32768
assert np.isclose(center, -0.5)
assert maxv == 32767
def test_float16(self):
minv, center, maxv = iadt.get_value_range_of_dtype(np.dtype("float16"))
assert minv < 100.0
assert np.isclose(center, 0.0)
assert maxv > 100.0
# TODO extend tests towards all dtypes and actual minima/maxima of value ranges
# TODO what happens if both bounds are negative, but input dtype is uint*?
class Test_clip_(unittest.TestCase):
def test_values_hit_lower_bound_int32(self):
arr = np.int32([0, 1, 2, 3, 4, 5])
observed = iadt.clip_(arr, 0, 10)
assert np.array_equal(observed, np.int32([0, 1, 2, 3, 4, 5]))
def test_values_hit_lower_and_upper_bound_int32(self):
arr = np.int32([0, 1, 2, 3, 4, 5])
observed = iadt.clip_(arr, 0, 5)
assert np.array_equal(observed, np.int32([0, 1, 2, 3, 4, 5]))
def test_values_hit_lower_bound_exceed_upper_bound_int32(self):
arr = np.int32([0, 1, 2, 3, 4, 5])
observed = iadt.clip_(arr, 0, 4)
assert np.array_equal(observed, np.int32([0, 1, 2, 3, 4, 4]))
def test_values_exceed_lower_bound_float32(self):
arr = np.float32([-1.0])
observed = iadt.clip_(arr, 0, 1)
assert np.allclose(observed, np.float32([0.0]))
def test_values_hit_lower_bound_float32(self):
arr = np.float32([-1.0])
observed = iadt.clip_(arr, -1.0, 1)
assert np.allclose(observed, np.float32([-1.0]))
def test_values_hit_lower_bound_uint32(self):
arr = np.uint32([0])
observed = iadt.clip_(arr, 0, 1)
assert np.array_equal(observed, np.uint32([0]))
def test_values_hit_upper_bound_uint32(self):
arr = np.uint32([1])
observed = iadt.clip_(arr, 0, 1)
assert np.array_equal(observed, np.uint32([1]))
def test_values_exceed_upper_bound_uint32(self):
arr = np.uint32([2])
observed = iadt.clip_(arr, 0, 1)
assert np.array_equal(observed, np.uint32([1]))
def test_values_hit_upper_bound_negative_lower_bound_uint32(self):
arr = np.uint32([1])
observed = iadt.clip_(arr, -1, 1)
assert np.array_equal(observed, np.uint32([1]))
def test_values_exceed_upper_bound_negative_lower_bound_uint32(self):
arr = np.uint32([10])
observed = iadt.clip_(arr, -1, 1)
assert np.array_equal(observed, np.uint32([1]))
def test_values_hit_upper_bound_int8(self):
arr = np.int8([127])
observed = iadt.clip_(arr, 0, 127)
assert np.array_equal(observed, np.int8([127]))
def test_values_within_bounds_upper_bound_is_dtype_limit_int8(self):
arr = np.int8([127])
observed = iadt.clip_(arr, 0, 128)
assert np.array_equal(observed, np.int8([127]))
def test_values_hit_upper_bound_negative_lower_bound_int8(self):
arr = np.int8([127])
observed = iadt.clip_(arr, -1, 127)
assert np.array_equal(observed, np.int8([127]))
def test_both_bounds_are_none_int8(self):
arr = np.int8([1])
observed = iadt.clip_(arr, None, None)
assert np.array_equal(observed, np.int8([1]))
def test_lower_bound_is_none_int8(self):
arr = np.int8([1])
observed = iadt.clip_(arr, None, 10)
assert np.array_equal(observed, np.int8([1]))
def test_upper_bound_is_none_int8(self):
arr = np.int8([1])
observed = iadt.clip_(arr, -10, None)
assert np.array_equal(observed, np.int8([1]))
def test_values_exceed_upper_bound_and_lower_bound_is_none_int8(self):
arr = np.int8([10])
observed = iadt.clip_(arr, None, 1)
assert np.array_equal(observed, np.int8([1]))
def test_values_exceed_lower_bound_and_upper_bound_is_none_int8(self):
arr = np.int8([-10])
observed = iadt.clip_(arr, -1, None)
assert np.array_equal(observed, np.int8([-1]))
def test_numpy_scalar_hits_lower_bound_int8(self):
# single value arrays, shape == tuple()
arr = np.int8(-10)
observed = iadt.clip_(arr, -10, 10)
assert np.array_equal(observed, np.int8(-10))
def test_numpy_scalar_exceeds_lower_bound_int8(self):
arr = np.int8(-10)
observed = iadt.clip_(arr, -1, 10)
assert np.array_equal(observed, np.int8(-1))
def test_numpy_scalar_exceeds_upper_bound_int8(self):
arr = np.int8(10)
observed = iadt.clip_(arr, -10, 1)
assert np.array_equal(observed, np.int8(1))
class Test_clip_to_dtype_value_range(unittest.TestCase):
def test_clip_to_wider_dtype(self):
arr = np.array([-10, -1, 0, 1, 10, 255, 256], dtype=np.int16)
arr_clipped = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int32, validate=False)
assert np.array_equal(arr_clipped, arr)
assert arr_clipped.dtype.name == "int16"
def test_clip_to_wider_dtype_given_by_name(self):
arr = np.array([-10, -1, 0, 1, 10, 255, 256], dtype=np.int16)
arr_clipped = iadt.clip_to_dtype_value_range_(
np.copy(arr), "int32", validate=False)
assert np.array_equal(arr_clipped, arr)
assert arr_clipped.dtype.name == "int16"
def test_clip_to_wider_dtype_different_kind(self):
arr = np.array([-10, -1, 0, 1, 10, 255, 256], dtype=np.int16)
arr_clipped = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.float64, validate=False)
assert np.array_equal(arr_clipped, arr)
assert arr_clipped.dtype.name == "int16"
def test_clip_to_same_dtype(self):
arr = np.array([-10, -1, 0, 1, 10, 255, 256], dtype=np.int16)
arr_clipped = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int16, validate=False)
assert np.array_equal(arr_clipped, arr)
assert arr_clipped.dtype.name == "int16"
def test_clip_to_narrower_dtype(self):
arr = np.array([-10, -1, 0, 1, 10, 255, 256], dtype=np.int16)
arr_clipped = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int8, validate=False)
expected = np.array([-10, -1, 0, 1, 10, 127, 127], dtype=np.int16)
assert np.array_equal(arr_clipped, expected)
assert arr_clipped.dtype.name == "int16"
def test_dtype_is_array(self):
arr = np.array([-10, -1, 0, 1, 10, 255, 256], dtype=np.int16)
dt_arr = np.array([1], dtype=np.int32)
arr_clipped = iadt.clip_to_dtype_value_range_(
np.copy(arr), dt_arr, validate=False)
assert np.array_equal(arr_clipped, arr)
assert arr_clipped.dtype.name == "int16"
def test_validate_true_all_values_within_value_range(self):
arr = np.array([-10, -1, 0, 1, 10, 126, 127], dtype=np.int16)
arr_clipped = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int8, validate=True)
assert np.array_equal(arr_clipped, arr)
assert arr_clipped.dtype.name == "int16"
def test_validate_true_min_value_outside_value_range(self):
arr = np.array([-200, -1, 0, 1, 10, 126, 127], dtype=np.int16)
with self.assertRaises(AssertionError) as context:
_ = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int8, validate=True)
assert (
"Minimum value of array is outside of allowed value range "
"(-200.0000 vs -128.0000 to 127.0000)." in str(context.exception))
def test_validate_true_max_value_outside_value_range(self):
arr = np.array([-10, -1, 0, 1, 10, 126, 200], dtype=np.int16)
with self.assertRaises(AssertionError) as context:
_ = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int8, validate=True)
assert (
"Maximum value of array is outside of allowed value range "
"(200.0000 vs -128.0000 to 127.0000)." in str(context.exception))
def test_validate_too_few_values(self):
arr = np.array([-10, 0, 200], dtype=np.int16)
_ = iadt.clip_to_dtype_value_range_(np.copy(arr), np.int8, validate=2)
def test_validate_enough_values(self):
arr = np.array([-10, 0, 200], dtype=np.int16)
with self.assertRaises(AssertionError) as context:
_ = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int8, validate=3)
assert (
"Maximum value of array is outside of allowed value range "
"(200.0000 vs -128.0000 to 127.0000)." in str(context.exception))
def test_validate_too_many_values(self):
arr = np.array([-10, 0, 200], dtype=np.int16)
with self.assertRaises(AssertionError) as context:
_ = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int8, validate=100)
assert (
"Maximum value of array is outside of allowed value range "
"(200.0000 vs -128.0000 to 127.0000)." in str(context.exception))
def test_validate_values_set(self):
arr = np.array([-10, -1, 0, 1, 10, 126, 200], dtype=np.int16)
with self.assertRaises(AssertionError) as context:
_ = iadt.clip_to_dtype_value_range_(
np.copy(arr), np.int8, validate=True,
validate_values=(-5, 201))
assert (
"Maximum value of array is outside of allowed value range "
"(201.0000 vs -128.0000 to 127.0000)." in str(context.exception))
class Test_gate_dtypes_strs(unittest.TestCase):
def test_standard_scenario_all_allowed(self):
for _ in range(3):
dtypes = {np.dtype("uint8"), np.dtype("uint8"),
np.dtype("float32"), np.dtype("int64")}
allowed = "uint8 float32 int64"
disallowed = "bool"
iadt.gate_dtypes_strs(dtypes, allowed, disallowed)
def test_standard_scenario_one_disallowed(self):
for _ in range(3):
dtypes = {np.dtype("uint8"), np.dtype("uint8"),
np.dtype("float32"), np.dtype("int64")}
allowed = "uint8 float32"
disallowed = "bool int64"
with self.assertRaises(ValueError) as context:
iadt.gate_dtypes_strs(dtypes, allowed, disallowed)
assert "Got dtype 'int64'" in str(context.exception)
def test_empty_set_as_dtypes(self):
for _ in range(3):
dtypes = set()
allowed = "uint8 float32 int64"
disallowed = "bool"
iadt.gate_dtypes_strs(dtypes, allowed, disallowed)
def test_allowed_dtype_does_not_exist(self):
dtypes = {np.dtype("uint8")}
with self.assertRaises(KeyError):
iadt.gate_dtypes_strs(dtypes, "uint8 int1000", "int8")
def test_disallowed_dtype_does_not_exist(self):
dtypes = {np.dtype("uint8")}
with self.assertRaises(KeyError):
iadt.gate_dtypes_strs(dtypes, "uint8", "int8 int1000")
def test_overlap_between_allowed_and_disallowed(self):
dtypes = {np.dtype("uint8"), np.dtype("float32"), np.dtype("int64")}
allowed = "uint8 float32 int64"
disallowed = "uint8 bool"
with self.assertRaises(AssertionError) as context:
iadt.gate_dtypes_strs(dtypes, allowed, disallowed)
assert (
"Expected 'allowed' and 'disallowed' dtypes to not contain "
"the same dtypes" in str(context.exception)
)
def test_single_array_allowed(self):
arr = np.zeros((1, 1, 3), dtype=np.int8)
iadt.gate_dtypes_strs(arr, "int8", "")
def test_single_array_disallowed(self):
arr = np.zeros((1, 1, 3), dtype=np.int8)
with self.assertRaises(ValueError) as context:
iadt.gate_dtypes_strs(arr, "uint8", "int8")
assert "Got dtype 'int8'" in str(context.exception)
def test_list_of_single_array(self):
arr = np.zeros((1, 1, 3), dtype=np.int8)
iadt.gate_dtypes_strs([arr], "int8", "")
def test_list_of_two_arrays_same_dtypes(self):
arrays = [
np.zeros((1, 1, 3), dtype=np.int8),
np.zeros((1, 1, 3), dtype=np.int8)
]
iadt.gate_dtypes_strs(arrays, "int8", "")
def test_list_of_two_arrays_different_dtypes(self):
arrays = [
np.zeros((1, 1, 3), dtype=np.int8),
np.zeros((1, 1, 3), dtype=np.uint8)
]
iadt.gate_dtypes_strs(arrays, "int8 uint8", "")
def test_list_of_two_arrays_same_dtypes_one_disallowed(self):
arrays = [
np.zeros((1, 1, 3), dtype=np.int8),
np.zeros((1, 1, 3), dtype=np.uint8)
]
with self.assertRaises(ValueError) as context:
iadt.gate_dtypes_strs(arrays, "int8", "uint8")
assert "Got dtype 'uint8', which" in str(context.exception)
class Test_gate_dtypes(unittest.TestCase):
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_standard_scenario_all_allowed(self):
dtypes = [np.dtype("uint8"), np.dtype("uint8"),
np.dtype("float32"), np.dtype("int64")]
allowed = [np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")]
disallowed = [np.dtype("bool")]
iadt.gate_dtypes(dtypes, allowed, disallowed)
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_standard_scenario_one_disallowed(self):
dtypes = [np.dtype("uint8"), np.dtype("uint8"),
np.dtype("float32"), np.dtype("int64")]
allowed = [np.dtype("uint8"), np.dtype("float32")]
disallowed = [np.dtype("bool"), np.dtype("int64")]
with self.assertRaises(ValueError) as context:
iadt.gate_dtypes(dtypes, allowed, disallowed)
assert "Got dtype 'int64'" in str(context.exception)
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_standard_scenario_all_allowed_dtype_names(self):
dtypes = [np.dtype("uint8"), np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")]
allowed = ["uint8", "float32", "int64"]
disallowed = ["bool"]
iadt.gate_dtypes(dtypes, allowed, disallowed)
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_standard_scenario_one_disallowed_dtype_names(self):
dtypes = [np.dtype("uint8"), np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")]
allowed = ["uint8", "float32"]
disallowed = ["bool", "int64"]
with self.assertRaises(ValueError) as context:
iadt.gate_dtypes(dtypes, allowed, disallowed)
assert "Got dtype 'int64'" in str(context.exception)
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_standard_scenario_all_allowed_dtype_functions(self):
dtypes = [np.dtype("uint8"), np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")]
allowed = [np.uint8, np.float32, np.int64]
disallowed = [np.bool_]
iadt.gate_dtypes(dtypes, allowed, disallowed)
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_standard_scenario_one_disallowed_dtype_functions(self):
dtypes = [np.dtype("uint8"), np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")]
allowed = [np.uint8, np.float32]
disallowed = [np.bool_, np.int64]
with self.assertRaises(ValueError) as context:
iadt.gate_dtypes(dtypes, allowed, disallowed)
assert "Got dtype 'int64'" in str(context.exception)
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_single_array_allowed(self):
arr = np.zeros((1, 1, 3), dtype=np.int8)
iadt.gate_dtypes(arr, ["int8"], [])
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_single_array_disallowed(self):
arr = np.zeros((1, 1, 3), dtype=np.int8)
with self.assertRaises(ValueError) as context:
iadt.gate_dtypes(arr, ["uint8"], ["int8"])
assert "Got dtype 'int8'" in str(context.exception)
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_list_of_single_array(self):
arr = np.zeros((1, 1, 3), dtype=np.int8)
iadt.gate_dtypes([arr], [np.dtype("int8")], [])
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_list_of_two_arrays_same_dtypes(self):
arrays = [
np.zeros((1, 1, 3), dtype=np.int8),
np.zeros((1, 1, 3), dtype=np.int8)
]
iadt.gate_dtypes(arrays, [np.dtype("int8")], [])
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_list_of_two_arrays_different_dtypes(self):
arrays = [
np.zeros((1, 1, 3), dtype=np.int8),
np.zeros((1, 1, 3), dtype=np.uint8)
]
iadt.gate_dtypes(arrays, ["int8", "uint8"], [])
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_list_of_two_arrays_same_dtypes_one_disallowed(self):
arrays = [
np.zeros((1, 1, 3), dtype=np.int8),
np.zeros((1, 1, 3), dtype=np.uint8)
]
with self.assertRaises(ValueError) as context:
iadt.gate_dtypes(arrays, "int8", "uint8")
assert "Got dtype 'uint8', which" in str(context.exception)
@ensure_deprecation_warning("imgaug.dtypes.gate_dtypes_strs")
def test_single_dtype_function(self):
dtype = np.int8
iadt.gate_dtypes(dtype, ["int8"], [])
class Test__gate_dtypes(unittest.TestCase):
def test_standard_scenario_all_allowed(self):
dtypes = {np.dtype("uint8"), np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")}
allowed = {np.dtype("uint8"), np.dtype("float32"), np.dtype("int64")}
disallowed = {np.dtype("bool")}
iadt._gate_dtypes(dtypes, allowed, disallowed)
def test_standard_scenario_one_disallowed(self):
dtypes = {np.dtype("uint8"), np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")}
allowed = {np.dtype("uint8"), np.dtype("float32")}
disallowed = {np.dtype("bool"), np.dtype("int64")}
with self.assertRaises(ValueError) as context:
iadt._gate_dtypes(dtypes, allowed, disallowed)
assert "Got dtype 'int64'" in str(context.exception)
def test_all_allowed_input_is_list_of_dtypes(self):
dtypes = {np.dtype("uint8"), np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")}
allowed = {np.dtype("uint8"), np.dtype("float32"), np.dtype("int64")}
disallowed = {np.dtype("bool")}
iadt._gate_dtypes(dtypes, allowed, disallowed)
def test_all_allowed_input_is_list_of_dtype_functions(self):
dtypes = {np.dtype("uint8"), np.dtype("uint8"), np.dtype("float32"),
np.dtype("int64")}
allowed = {np.dtype("uint8"), np.dtype("float32"), np.dtype("int64")}
disallowed = {np.dtype("bool")}
iadt._gate_dtypes(dtypes, allowed, disallowed)
def test_empty_set_as_dtypes(self):
dtypes = set()
allowed = {np.dtype("uint8"), np.dtype("float32"), np.dtype("int64")}
disallowed = {np.dtype("bool")}
iadt._gate_dtypes(dtypes, allowed, disallowed)
def test_single_dtype_allowed(self):
dtype = np.dtype("int8")
iadt._gate_dtypes({dtype}, {np.dtype("int8")}, None)
def test_single_dtype_disallowed(self):
dtype = np.dtype("int8")
with self.assertRaises(ValueError) as context:
iadt._gate_dtypes({dtype}, {np.dtype("uint8")}, {np.dtype("int8")})
assert "Got dtype 'int8', which" in str(context.exception)
def test_single_dtype_disallowed_augmenter_set(self):
class _DummyAugmenter(object):
def __init__(self):
self.name = "foo"
dtype = np.dtype("int8")
dummy_augmenter = _DummyAugmenter()
with self.assertRaises(ValueError) as context:
iadt._gate_dtypes(
{dtype},
{np.dtype("uint8")},
{np.dtype("int8")},
augmenter=dummy_augmenter
)
assert "Got dtype 'int8' in augmenter 'foo'" in str(context.exception)
def test_list_of_two_dtypes_both_same(self):
dtypes = {
np.dtype("int8"),
np.dtype("int8")
}
iadt._gate_dtypes(dtypes, {np.dtype("int8")}, None)
def test_list_of_two_dtypes_both_different(self):
dtypes = {
np.dtype("int8"),
np.dtype("uint8")
}
iadt._gate_dtypes(dtypes, {np.dtype("int8"), np.dtype("uint8")}, None)
def test_list_of_two_dtypes_both_different_one_disallowed(self):
dtypes = {
np.dtype("int8"),
np.dtype("uint8")
}
with self.assertRaises(ValueError) as context:
iadt._gate_dtypes(dtypes, {np.dtype("int8")}, {np.dtype("uint8")})
assert "Got dtype 'uint8', which" in str(context.exception)
def test_dtype_not_in_allowed_or_disallowed(self):
dtypes = {
np.dtype("int8"),
np.dtype("float32")
}
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
iadt._gate_dtypes(dtypes, {np.dtype("int8")}, {np.dtype("uint8")})
assert len(caught_warnings) == 1
assert (
"Got dtype 'float32', which" in str(caught_warnings[-1].message))
def test_dtype_not_in_allowed_or_disallowed_augmenter_set(self):
class _DummyAugmenter(object):
def __init__(self):
self.name = "foo"
dtypes = {
np.dtype("int8"),
np.dtype("float32")
}
dummy_augmenter = _DummyAugmenter()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
iadt._gate_dtypes(
dtypes,
{np.dtype("int8")},
{np.dtype("uint8")},
augmenter=dummy_augmenter
)
assert len(caught_warnings) == 1
assert (
"Got dtype 'float32' in augmenter 'foo'"
in str(caught_warnings[-1].message))
================================================
FILE: test/test_imgaug.py
================================================
from __future__ import print_function, division, absolute_import
import time
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import matplotlib
matplotlib.use('Agg') # fix execution of tests involving matplotlib on travis
import numpy as np
import six.moves as sm
import cv2
import imgaug as ia
from imgaug import dtypes as iadt
import imgaug.random as iarandom
from imgaug.testutils import assertWarns
# TODO clean up this file
def main():
time_start = time.time()
test_is_np_array()
test_is_single_integer()
test_is_single_float()
test_is_single_number()
test_is_iterable()
test_is_string()
test_is_single_bool()
test_is_integer_array()
test_is_float_array()
test_is_callable()
test_caller_name()
# test_seed()
# test_current_random_state()
# test_new_random_state()
# test_dummy_random_state()
# test_copy_random_state()
# test_derive_random_state()
# test_derive_random_states()
# test_forward_random_state()
# test_angle_between_vectors()
test_compute_line_intersection_point()
test_draw_text()
test_imresize_many_images()
test_imresize_single_image()
test_pool()
test_avg_pool()
test_max_pool()
test_min_pool()
test_draw_grid()
# test_show_grid()
# test_do_assert()
# test_HooksImages_is_activated()
# test_HooksImages_is_propagating()
# test_HooksImages_preprocess()
# test_HooksImages_postprocess()
test_classes_and_functions_marked_deprecated()
time_end = time.time()
print("<%s> Finished without errors in %.4fs." % (__file__, time_end - time_start,))
def test_is_np_array():
class _Dummy(object):
pass
values_true = [
np.zeros((1, 2), dtype=np.uint8),
np.zeros((64, 64, 3), dtype=np.uint8),
np.zeros((1, 2), dtype=np.float32),
np.zeros((100,), dtype=np.float64)
]
values_false = [
"A", "BC", "1", True, False, (1.0, 2.0), [1.0, 2.0], _Dummy(),
-100, 1, 0, 1, 100, -1.2, -0.001, 0.0, 0.001, 1.2, 1e-4
]
for value in values_true:
assert ia.is_np_array(value) is True
for value in values_false:
assert ia.is_np_array(value) is False
def test_is_single_integer():
assert ia.is_single_integer("A") is False
assert ia.is_single_integer(None) is False
assert ia.is_single_integer(1.2) is False
assert ia.is_single_integer(1.0) is False
assert ia.is_single_integer(np.ones((1,), dtype=np.float32)[0]) is False
assert ia.is_single_integer(1) is True
assert ia.is_single_integer(1234) is True
assert ia.is_single_integer(np.ones((1,), dtype=np.uint8)[0]) is True
assert ia.is_single_integer(np.ones((1,), dtype=np.int32)[0]) is True
def test_is_single_float():
assert ia.is_single_float("A") is False
assert ia.is_single_float(None) is False
assert ia.is_single_float(1.2) is True
assert ia.is_single_float(1.0) is True
assert ia.is_single_float(np.ones((1,), dtype=np.float32)[0]) is True
assert ia.is_single_float(1) is False
assert ia.is_single_float(1234) is False
assert ia.is_single_float(np.ones((1,), dtype=np.uint8)[0]) is False
assert ia.is_single_float(np.ones((1,), dtype=np.int32)[0]) is False
def test_caller_name():
assert ia.caller_name() == 'test_caller_name'
class TestDeprecatedDataFunctions(unittest.TestCase):
def test_quokka(self):
with assertWarns(self, ia.DeprecationWarning):
img = ia.quokka()
assert ia.is_np_array(img)
def test_quokka_square(self):
with assertWarns(self, ia.DeprecationWarning):
img = ia.quokka_square()
assert ia.is_np_array(img)
def test_quokka_heatmap(self):
with assertWarns(self, ia.DeprecationWarning):
result = ia.quokka_heatmap()
assert isinstance(result, ia.HeatmapsOnImage)
def test_quokka_segmentation_map(self):
with assertWarns(self, ia.DeprecationWarning):
result = ia.quokka_segmentation_map()
assert isinstance(result, ia.SegmentationMapsOnImage)
def test_quokka_keypoints(self):
with assertWarns(self, ia.DeprecationWarning):
result = ia.quokka_keypoints()
assert isinstance(result, ia.KeypointsOnImage)
def test_quokka_bounding_boxes(self):
with assertWarns(self, ia.DeprecationWarning):
result = ia.quokka_bounding_boxes()
assert isinstance(result, ia.BoundingBoxesOnImage)
def test_is_single_number():
class _Dummy(object):
pass
values_true = [-100, 1, 0, 1, 100, -1.2, -0.001, 0.0, 0.001, 1.2, 1e-4]
values_false = ["A", "BC", "1", True, False, (1.0, 2.0), [1.0, 2.0], _Dummy(), np.zeros((1, 2), dtype=np.uint8)]
for value in values_true:
assert ia.is_single_number(value) is True
for value in values_false:
assert ia.is_single_number(value) is False
def test_is_iterable():
class _Dummy(object):
pass
values_true = [
[0, 1, 2],
["A", "X"],
[[123], [456, 789]],
[],
(1, 2, 3),
(1,),
tuple(),
"A",
"ABC",
"",
np.zeros((100,), dtype=np.uint8)
]
values_false = [1, 100, 0, -100, -1, 1.2, -1.2, True, False, _Dummy()]
for value in values_true:
assert ia.is_iterable(value) is True, value
for value in values_false:
assert ia.is_iterable(value) is False
def test_is_string():
class _Dummy(object):
pass
values_true = ["A", "BC", "1", ""]
values_false = [-100, 1, 0, 1, 100, -1.2, -0.001, 0.0, 0.001, 1.2, 1e-4, True, False, (1.0, 2.0), [1.0, 2.0],
_Dummy(), np.zeros((1, 2), dtype=np.uint8)]
for value in values_true:
assert ia.is_string(value) is True
for value in values_false:
assert ia.is_string(value) is False
def test_is_single_bool():
class _Dummy(object):
pass
values_true = [False, True]
values_false = [-100, 1, 0, 1, 100, -1.2, -0.001, 0.0, 0.001, 1.2, 1e-4, (1.0, 2.0), [1.0, 2.0], _Dummy(),
np.zeros((1, 2), dtype=np.uint8), np.zeros((1,), dtype=bool)]
for value in values_true:
assert ia.is_single_bool(value) is True
for value in values_false:
assert ia.is_single_bool(value) is False
def test_is_integer_array():
class _Dummy(object):
pass
values_true = [
np.zeros((1, 2), dtype=np.uint8),
np.zeros((100,), dtype=np.uint8),
np.zeros((1, 2), dtype=np.uint16),
np.zeros((1, 2), dtype=np.int32),
np.zeros((1, 2), dtype=np.int64)
]
values_false = [
"A", "BC", "1", "", -100, 1, 0, 1, 100, -1.2, -0.001, 0.0, 0.001, 1.2, 1e-4, True, False,
(1.0, 2.0), [1.0, 2.0], _Dummy(),
np.zeros((1, 2), dtype=np.float16),
np.zeros((100,), dtype=np.float32),
np.zeros((1, 2), dtype=np.float64),
np.zeros((1, 2), dtype=np.bool)
]
for value in values_true:
assert ia.is_integer_array(value) is True
for value in values_false:
assert ia.is_integer_array(value) is False
def test_is_float_array():
class _Dummy(object):
pass
values_true = [
np.zeros((1, 2), dtype=np.float16),
np.zeros((100,), dtype=np.float32),
np.zeros((1, 2), dtype=np.float64)
]
values_false = [
"A", "BC", "1", "", -100, 1, 0, 1, 100, -1.2, -0.001, 0.0, 0.001, 1.2, 1e-4, True, False,
(1.0, 2.0), [1.0, 2.0], _Dummy(),
np.zeros((1, 2), dtype=np.uint8),
np.zeros((100,), dtype=np.uint8),
np.zeros((1, 2), dtype=np.uint16),
np.zeros((1, 2), dtype=np.int32),
np.zeros((1, 2), dtype=np.int64),
np.zeros((1, 2), dtype=np.bool)
]
for value in values_true:
assert ia.is_float_array(value) is True
for value in values_false:
assert ia.is_float_array(value) is False
def test_is_callable():
def _dummy_func():
pass
_dummy_func2 = lambda x: x
class _Dummy1(object):
pass
class _Dummy2(object):
def __call__(self):
pass
class _Dummy3(object):
def foo(self):
pass
class _Dummy4(object):
@classmethod
def foo(cls):
pass
class _Dummy5(object):
@classmethod
def foo(cls):
pass
values_true = [_dummy_func, _dummy_func2, _Dummy2(), _Dummy3().foo,
_Dummy4.foo, _Dummy5.foo]
values_false = [
"A", "BC", "1", "", -100, 1, 0, 1, 100, -1.2, -0.001, 0.0, 0.001, 1.2,
1e-4, True, False, (1.0, 2.0), [1.0, 2.0], _Dummy1(),
np.zeros((1, 2), dtype=np.uint8)]
for value in values_true:
assert ia.is_callable(value) is True
for value in values_false:
assert ia.is_callable(value) is False
@mock.patch("imgaug.random.seed")
def test_seed(mock_seed):
ia.seed(10017)
mock_seed.assert_called_once_with(10017)
def test_current_random_state():
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
rng = ia.current_random_state()
assert rng.is_global_rng()
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.RNG")
def test_new_random_state__induce_pseudo_random(mock_rng):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = ia.new_random_state(seed=None, fully_random=False)
assert mock_rng.create_pseudo_random_.call_count == 1
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.RNG")
def test_new_random_state__induce_fully_random(mock_rng):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = ia.new_random_state(seed=None, fully_random=True)
assert mock_rng.create_fully_random.call_count == 1
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.RNG")
def test_new_random_state__use_seed(mock_rng):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = ia.new_random_state(seed=1)
mock_rng.assert_called_once_with(1)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.RNG")
def test_dummy_random_state(mock_rng):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_ = ia.dummy_random_state()
mock_rng.assert_called_once_with(1)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.copy_generator")
@mock.patch("imgaug.random.copy_generator_unless_global_generator")
def test_copy_random_state__not_global(mock_copy_gen_glob, mock_copy_gen):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
gen = iarandom.convert_seed_to_generator(1)
_ = ia.copy_random_state(gen, force_copy=False)
assert mock_copy_gen.call_count == 0
mock_copy_gen_glob.assert_called_once_with(gen)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.copy_generator")
@mock.patch("imgaug.random.copy_generator_unless_global_generator")
def test_copy_random_state__also_global(mock_copy_gen_glob, mock_copy_gen):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
gen = iarandom.convert_seed_to_generator(1)
_ = ia.copy_random_state(gen, force_copy=True)
mock_copy_gen.assert_called_once_with(gen)
assert mock_copy_gen_glob.call_count == 0
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.derive_generator_")
def test_derive_random_state(mock_derive):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
gen = iarandom.convert_seed_to_generator(1)
_ = ia.derive_random_state(gen)
mock_derive.assert_called_once_with(gen)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.derive_generators_")
def test_derive_random_states(mock_derive):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
gen = iarandom.convert_seed_to_generator(1)
_ = ia.derive_random_states(gen, n=2)
mock_derive.assert_called_once_with(gen, n=2)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
@mock.patch("imgaug.random.advance_generator_")
def test_forward_random_state(mock_advance):
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
gen = iarandom.convert_seed_to_generator(1)
_ = ia.forward_random_state(gen)
mock_advance.assert_called_once_with(gen)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
def test_compute_line_intersection_point():
# intersecting lines
line1 = (0, 0, 1, 0)
line2 = (0.5, -1, 0.5, 1)
point = ia.compute_line_intersection_point(
line1[0], line1[1], line1[2], line1[3],
line2[0], line2[1], line2[2], line2[3]
)
assert np.allclose(point[0], 0.5)
assert np.allclose(point[1], 0)
# intersection point outside of defined interval of one line, should not change anything
line1 = (0, 0, 1, 0)
line2 = (0.5, -1, 0.5, -0.5)
point = ia.compute_line_intersection_point(
line1[0], line1[1], line1[2], line1[3],
line2[0], line2[1], line2[2], line2[3]
)
assert np.allclose(point[0], 0.5)
assert np.allclose(point[1], 0)
# touching lines
line1 = (0, 0, 1, 0)
line2 = (0.5, -1, 0.5, 0)
point = ia.compute_line_intersection_point(
line1[0], line1[1], line1[2], line1[3],
line2[0], line2[1], line2[2], line2[3]
)
assert np.allclose(point[0], 0.5)
assert np.allclose(point[1], 0)
# parallel, not intersecting lines
line1 = (0, 0, 1, 0)
line2 = (0, -0.1, 1, -0.1)
point = ia.compute_line_intersection_point(
line1[0], line1[1], line1[2], line1[3],
line2[0], line2[1], line2[2], line2[3]
)
assert point is False
# parallel and overlapping lines (infinite intersection points)
line1 = (0, 0, 1, 0)
line2 = (0.1, 0, 1, 0)
point = ia.compute_line_intersection_point(
line1[0], line1[1], line1[2], line1[3],
line2[0], line2[1], line2[2], line2[3]
)
assert point is False
def test_draw_text():
# make roughly sure that shape of drawn text matches expected text
img = np.zeros((20, 50, 3), dtype=np.uint8)
img_text = ia.draw_text(img, y=5, x=5, text="---------", size=10, color=[255, 255, 255])
assert np.max(img_text) == 255
assert np.min(img_text) == 0
assert np.sum(img_text == 255) / np.sum(img_text == 0)
first_row = None
last_row = None
first_col = None
last_col = None
for i in range(img.shape[0]):
if np.max(img_text[i, :, :]) == 255:
first_row = i
break
for i in range(img.shape[0]-1, 0, -1):
if np.max(img_text[i, :, :]) == 255:
last_row = i
break
for i in range(img.shape[1]):
if np.max(img_text[:, i, :]) == 255:
first_col = i
break
for i in range(img.shape[1]-1, 0, -1):
if np.max(img_text[:, i, :]) == 255:
last_col = i
break
bb = ia.BoundingBox(x1=first_col, y1=first_row, x2=last_col, y2=last_row)
assert bb.width > 4.0*bb.height
# test x
img = np.zeros((20, 100, 3), dtype=np.uint8)
img_text1 = ia.draw_text(img, y=5, x=5, text="XXXXXXX", size=10, color=[255, 255, 255])
img_text2 = ia.draw_text(img, y=5, x=50, text="XXXXXXX", size=10, color=[255, 255, 255])
first_col1 = None
first_col2 = None
for i in range(img.shape[1]):
if np.max(img_text1[:, i, :]) == 255:
first_col1 = i
break
for i in range(img.shape[1]):
if np.max(img_text2[:, i, :]) == 255:
first_col2 = i
break
assert 0 < first_col1 < 10
assert 45 < first_col2 < 55
# test y
img = np.zeros((100, 20, 3), dtype=np.uint8)
img_text1 = ia.draw_text(img, y=5, x=5, text="XXXXXXX", size=10, color=[255, 255, 255])
img_text2 = ia.draw_text(img, y=50, x=5, text="XXXXXXX", size=10, color=[255, 255, 255])
first_row1 = None
first_row2 = None
for i in range(img.shape[0]):
if np.max(img_text1[i, :, :]) == 255:
first_row1 = i
break
for i in range(img.shape[0]):
if np.max(img_text2[i, :, :]) == 255:
first_row2 = i
break
assert 0 < first_row1 < 15
assert 45 < first_row2 < 60
# test size
img = np.zeros((100, 100, 3), dtype=np.uint8)
img_text_small = ia.draw_text(img, y=5, x=5, text="X", size=10, color=[255, 255, 255])
img_text_large = ia.draw_text(img, y=5, x=5, text="X", size=50, color=[255, 255, 255])
nb_filled_small = np.sum(img_text_small > 10)
nb_filled_large = np.sum(img_text_large > 10)
assert nb_filled_large > 2*nb_filled_small
# text color
img = np.zeros((20, 20, 3), dtype=np.uint8)
img_text = ia.draw_text(img, y=5, x=5, text="X", size=10, color=[128, 129, 130])
maxcol = np.max(img_text, axis=(0, 1))
assert maxcol[0] == 128
assert maxcol[1] == 129
assert maxcol[2] == 130
def test_imresize_many_images():
interpolations = [None,
"nearest", "linear", "area", "cubic",
cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_AREA, cv2.INTER_CUBIC]
for c in [1, 3]:
image1 = np.zeros((16, 16, c), dtype=np.uint8) + 255
image2 = np.zeros((16, 16, c), dtype=np.uint8)
image3 = np.pad(
np.zeros((8, 8, c), dtype=np.uint8) + 255,
((4, 4), (4, 4), (0, 0)),
mode="constant",
constant_values=0
)
image1_small = np.zeros((8, 8, c), dtype=np.uint8) + 255
image2_small = np.zeros((8, 8, c), dtype=np.uint8)
image3_small = np.pad(
np.zeros((4, 4, c), dtype=np.uint8) + 255,
((2, 2), (2, 2), (0, 0)),
mode="constant",
constant_values=0
)
image1_large = np.zeros((32, 32, c), dtype=np.uint8) + 255
image2_large = np.zeros((32, 32, c), dtype=np.uint8)
image3_large = np.pad(
np.zeros((16, 16, c), dtype=np.uint8) + 255,
((8, 8), (8, 8), (0, 0)),
mode="constant",
constant_values=0
)
images = np.uint8([image1, image2, image3])
images_small = np.uint8([image1_small, image2_small, image3_small])
images_large = np.uint8([image1_large, image2_large, image3_large])
for images_this_iter in [images, list(images)]: # test for ndarray and list(ndarray) input
for interpolation in interpolations:
images_same_observed = ia.imresize_many_images(images_this_iter, (16, 16), interpolation=interpolation)
for image_expected, image_observed in zip(images_this_iter, images_same_observed):
diff = np.abs(image_expected.astype(np.int32) - image_observed.astype(np.int32))
assert np.sum(diff) == 0
for interpolation in interpolations:
images_small_observed = ia.imresize_many_images(images_this_iter, (8, 8), interpolation=interpolation)
for image_expected, image_observed in zip(images_small, images_small_observed):
diff = np.abs(image_expected.astype(np.int32) - image_observed.astype(np.int32))
diff_fraction = np.sum(diff) / (image_observed.size * 255)
assert diff_fraction < 0.5
for interpolation in interpolations:
images_large_observed = ia.imresize_many_images(images_this_iter, (32, 32), interpolation=interpolation)
for image_expected, image_observed in zip(images_large, images_large_observed):
diff = np.abs(image_expected.astype(np.int32) - image_observed.astype(np.int32))
diff_fraction = np.sum(diff) / (image_observed.size * 255)
assert diff_fraction < 0.5
# test size given as single int
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, 8)
assert observed.shape == (1, 8, 8, 3)
# test size given as single float
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, 2.0)
assert observed.shape == (1, 8, 8, 3)
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, 0.5)
assert observed.shape == (1, 2, 2, 3)
# test size given as (float, float)
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, (2.0, 2.0))
assert observed.shape == (1, 8, 8, 3)
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, (0.5, 0.5))
assert observed.shape == (1, 2, 2, 3)
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, (2.0, 0.5))
assert observed.shape == (1, 8, 2, 3)
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, (0.5, 2.0))
assert observed.shape == (1, 2, 8, 3)
# test size given as int+float or float+int
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, (11, 2.0))
assert observed.shape == (1, 11, 8, 3)
images = np.zeros((1, 4, 4, 3), dtype=np.uint8)
observed = ia.imresize_many_images(images, (2.0, 11))
assert observed.shape == (1, 8, 11, 3)
# test no channels
images = np.zeros((1, 4, 4), dtype=np.uint8)
images_rs = ia.imresize_many_images(images, (2, 2))
assert images_rs.shape == (1, 2, 2)
images = [np.zeros((4, 4), dtype=np.uint8)]
images_rs = ia.imresize_many_images(images, (2, 2))
assert isinstance(images_rs, list)
assert images_rs[0].shape == (2, 2)
# test len 0 input
observed = ia.imresize_many_images(np.zeros((0, 8, 8, 3), dtype=np.uint8), (4, 4))
assert ia.is_np_array(observed)
assert observed.dtype.type == np.uint8
assert len(observed) == 0
observed = ia.imresize_many_images([], (4, 4))
assert isinstance(observed, list)
assert len(observed) == 0
# test images with zero height/width
shapes = [(0, 4, 3), (4, 0, 3), (0, 0, 3)]
for shape in shapes:
images = [np.zeros(shape, dtype=np.uint8)]
got_exception = False
try:
_ = ia.imresize_many_images(images, sizes=(2, 2))
except Exception as exc:
assert (
"Cannot resize images, because at least one image has a height "
"and/or width and/or number of channels of zero."
in str(exc)
)
got_exception = True
assert got_exception
# test invalid sizes
sizes_all = [(-1, 2)]
sizes_all = sizes_all\
+ [(float(a), b) for a, b in sizes_all]\
+ [(a, float(b)) for a, b in sizes_all]\
+ [(float(a), float(b)) for a, b in sizes_all]\
+ [(-a, -b) for a, b in sizes_all]\
+ [(-float(a), -b) for a, b in sizes_all]\
+ [(-a, -float(b)) for a, b in sizes_all]\
+ [(-float(a), -float(b)) for a, b in sizes_all]
sizes_all = sizes_all\
+ [(b, a) for a, b in sizes_all]
sizes_all = sizes_all\
+ [-1.0, -1]
for sizes in sizes_all:
images = [np.zeros((4, 4, 3), dtype=np.uint8)]
got_exception = False
try:
_ = ia.imresize_many_images(images, sizes=sizes)
except Exception as exc:
assert ">= 0" in str(exc)
got_exception = True
assert got_exception
# test list input but all with same shape
images = [np.zeros((8, 8, 3), dtype=np.uint8) for _ in range(2)]
observed = ia.imresize_many_images(images, (4, 4))
assert isinstance(observed, list)
assert all([image.shape == (4, 4, 3) for image in observed])
assert all([image.dtype.type == np.uint8 for image in observed])
# test multiple shapes
images = [np.zeros((8, 8, 3), dtype=np.uint8), np.zeros((4, 4), dtype=np.uint8)]
observed = ia.imresize_many_images(images, (4, 4))
assert observed[0].shape == (4, 4, 3)
assert observed[1].shape == (4, 4)
assert observed[0].dtype == np.uint8
assert observed[1].dtype == np.uint8
###################
# test other dtypes
###################
# interpolation="nearest"
image = np.zeros((4, 4), dtype=bool)
image[1, :] = True
image[2, :] = True
expected = np.zeros((3, 3), dtype=bool)
expected[1, :] = True
expected[2, :] = True
image_rs = ia.imresize_many_images([image], (3, 3), interpolation="nearest")[0]
assert image_rs.dtype.type == image.dtype.type
assert np.all(image_rs == expected)
for dtype in [np.uint8, np.uint16, np.int8, np.int16, np.int32]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
for value in [min_value, max_value]:
image = np.zeros((4, 4), dtype=dtype)
image[1, :] = value
image[2, :] = value
expected = np.zeros((3, 3), dtype=dtype)
expected[1, :] = value
expected[2, :] = value
image_rs = ia.imresize_many_images([image], (3, 3), interpolation="nearest")[0]
assert image_rs.dtype.type == dtype
assert np.all(image_rs == expected)
for dtype in [np.float16, np.float32, np.float64]:
isize = np.dtype(dtype).itemsize
for value in [0.5, -0.5, 1.0, -1.0, 10.0, -10.0, -1000 ** (isize-1), 1000 * (isize+1)]:
image = np.zeros((4, 4), dtype=dtype)
image[1, :] = value
image[2, :] = value
expected = np.zeros((3, 3), dtype=dtype)
expected[1, :] = value
expected[2, :] = value
image_rs = ia.imresize_many_images([image], (3, 3), interpolation="nearest")[0]
assert image_rs.dtype.type == dtype
assert np.allclose(image_rs, expected, rtol=0, atol=1e-8)
# other interpolations
for ip in ["linear", "cubic", "area"]:
mask = np.zeros((4, 4), dtype=np.uint8)
mask[1, :] = 255
mask[2, :] = 255
mask = ia.imresize_many_images([mask], (3, 3), interpolation=ip)[0]
mask = mask.astype(np.float64) / 255.0
image = np.zeros((4, 4), dtype=bool)
image[1, :] = True
image[2, :] = True
expected = mask > 0.5
image_rs = ia.imresize_many_images([image], (3, 3), interpolation=ip)[0]
assert image_rs.dtype.type == image.dtype.type
assert np.all(image_rs == expected)
for dtype in [np.uint8, np.uint16, np.int8, np.int16]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
dynamic_range = max_value - min_value
for value in [min_value+1, max_value-1]:
image = np.zeros((4, 4), dtype=dtype)
image[1, :] = value
image[2, :] = value
expected = np.round(mask * value).astype(dtype)
image_rs = ia.imresize_many_images([image], (3, 3), interpolation=ip)[0]
assert image_rs.dtype.type == dtype
diff = np.abs(image_rs.astype(np.int64) - expected.astype(np.int64))
assert np.all(diff < 2 * (1/255) * dynamic_range)
mask = np.zeros((4, 4), dtype=np.float64)
mask[1, :] = 1.0
mask[2, :] = 1.0
mask = ia.imresize_many_images([mask], (3, 3), interpolation=ip)[0]
mask = mask.astype(np.float64)
for dtype in [np.float16, np.float32, np.float64]:
isize = np.dtype(dtype).itemsize
for value in [0.5, -0.5, 1.0, -1.0, 10.0, -10.0, -1000 ** (isize-1), 1000 * (isize+1)]:
image = np.zeros((4, 4), dtype=dtype)
image[1, :] = value
image[2, :] = value
expected = (mask * np.float64(value)).astype(dtype)
image_rs = ia.imresize_many_images([image], (3, 3), interpolation=ip)[0]
assert image_rs.dtype.type == dtype
# Our basis for the expected image is derived from uint8 as that is most likely to work, so we will
# have to accept here deviations of around 1/255.
atol = np.float64(1 / 255) * np.abs(np.float64(value)) + 1e-8
assert np.allclose(image_rs, expected, rtol=0, atol=atol)
# Expect at least one cell to have a difference between observed and expected image of approx. 0,
# currently we seem to be able to get away with this despite the above mentioned inaccuracy.
assert np.any(np.isclose(image_rs, expected, rtol=0, atol=1e-4))
def test_imresize_single_image():
for c in [-1, 1, 3]:
image1 = np.zeros((16, 16, abs(c)), dtype=np.uint8) + 255
image2 = np.zeros((16, 16, abs(c)), dtype=np.uint8)
image3 = np.pad(
np.zeros((8, 8, abs(c)), dtype=np.uint8) + 255,
((4, 4), (4, 4), (0, 0)),
mode="constant",
constant_values=0
)
image1_small = np.zeros((8, 8, abs(c)), dtype=np.uint8) + 255
image2_small = np.zeros((8, 8, abs(c)), dtype=np.uint8)
image3_small = np.pad(
np.zeros((4, 4, abs(c)), dtype=np.uint8) + 255,
((2, 2), (2, 2), (0, 0)),
mode="constant",
constant_values=0
)
image1_large = np.zeros((32, 32, abs(c)), dtype=np.uint8) + 255
image2_large = np.zeros((32, 32, abs(c)), dtype=np.uint8)
image3_large = np.pad(
np.zeros((16, 16, abs(c)), dtype=np.uint8) + 255,
((8, 8), (8, 8), (0, 0)),
mode="constant",
constant_values=0
)
images = np.uint8([image1, image2, image3])
images_small = np.uint8([image1_small, image2_small, image3_small])
images_large = np.uint8([image1_large, image2_large, image3_large])
if c == -1:
images = images[:, :, 0]
images_small = images_small[:, :, 0]
images_large = images_large[:, :, 0]
interpolations = [None,
"nearest", "linear", "area", "cubic",
cv2.INTER_NEAREST, cv2.INTER_LINEAR, cv2.INTER_AREA, cv2.INTER_CUBIC]
for interpolation in interpolations:
for image in images:
image_observed = ia.imresize_single_image(image, (16, 16), interpolation=interpolation)
diff = np.abs(image.astype(np.int32) - image_observed.astype(np.int32))
assert np.sum(diff) == 0
for interpolation in interpolations:
for image, image_expected in zip(images, images_small):
image_observed = ia.imresize_single_image(image, (8, 8), interpolation=interpolation)
diff = np.abs(image_expected.astype(np.int32) - image_observed.astype(np.int32))
diff_fraction = np.sum(diff) / (image_observed.size * 255)
assert diff_fraction < 0.5
for interpolation in interpolations:
for image, image_expected in zip(images, images_large):
image_observed = ia.imresize_single_image(image, (32, 32), interpolation=interpolation)
diff = np.abs(image_expected.astype(np.int32) - image_observed.astype(np.int32))
diff_fraction = np.sum(diff) / (image_observed.size * 255)
assert diff_fraction < 0.5
def test_pool():
# -----
# uint, int
# -----
for dtype in [np.uint8, np.uint16, np.uint32, np.int8, np.int16, np.int32]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
for func in [np.min, np.average, np.max]:
arr = np.array([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
], dtype=dtype)
arr_pooled = ia.pool(arr, 2, func)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == np.dtype(dtype)
assert arr_pooled[0, 0] == int(func([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(func([2, 3, 6, 7]))
assert arr_pooled[1, 0] == int(func([8, 9, 12, 13]))
assert arr_pooled[1, 1] == int(func([10, 11, 14, 15]))
arr = np.array([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
], dtype=dtype)
arr = np.tile(arr[:, :, np.newaxis], (1, 1, 3))
arr[..., 1] += 1
arr[..., 2] += 2
arr_pooled = ia.pool(arr, 2, func)
assert arr_pooled.shape == (2, 2, 3)
assert arr_pooled.dtype == np.dtype(dtype)
for c in sm.xrange(3):
assert arr_pooled[0, 0, c] == int(func([0, 1, 4, 5])) + c
assert arr_pooled[0, 1, c] == int(func([2, 3, 6, 7])) + c
assert arr_pooled[1, 0, c] == int(func([8, 9, 12, 13])) + c
assert arr_pooled[1, 1, c] == int(func([10, 11, 14, 15])) + c
for value in [min_value, min_value+50, min_value+100, 0, 10, max_value,
int(center_value + 0.10*max_value),
int(center_value + 0.20*max_value),
int(center_value + 0.25*max_value),
int(center_value + 0.33*max_value)]:
arr = np.full((4, 4), value, dtype=dtype)
arr_pooled = ia.pool(arr, 2, func)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == np.dtype(dtype)
assert np.all(arr_pooled == value)
arr = np.full((4, 4, 3), value, dtype=dtype)
arr_pooled = ia.pool(arr, 2, func)
assert arr_pooled.shape == (2, 2, 3)
assert arr_pooled.dtype == np.dtype(dtype)
assert np.all(arr_pooled == value)
# -----
# float
# -----
try:
high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
dtype = np.dtype(dtype)
def _allclose(a, b):
atol = 1e-4 if dtype == np.float16 else 1e-8
return np.allclose(a, b, atol=atol, rtol=0)
for func in [np.min, np.average, np.max]:
arr = np.array([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
], dtype=dtype)
arr_pooled = ia.pool(arr, 2, func)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == np.dtype(dtype)
assert arr_pooled[0, 0] == func([0, 1, 4, 5])
assert arr_pooled[0, 1] == func([2, 3, 6, 7])
assert arr_pooled[1, 0] == func([8, 9, 12, 13])
assert arr_pooled[1, 1] == func([10, 11, 14, 15])
arr = np.array([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
], dtype=dtype)
arr = np.tile(arr[:, :, np.newaxis], (1, 1, 3))
arr[..., 1] += 1
arr[..., 2] += 2
arr_pooled = ia.pool(arr, 2, func)
assert arr_pooled.shape == (2, 2, 3)
assert arr_pooled.dtype == np.dtype(dtype)
for c in sm.xrange(3):
assert arr_pooled[0, 0, c] == func([0, 1, 4, 5]) + c
assert arr_pooled[0, 1, c] == func([2, 3, 6, 7]) + c
assert arr_pooled[1, 0, c] == func([8, 9, 12, 13]) + c
assert arr_pooled[1, 1, c] == func([10, 11, 14, 15]) + c
isize = np.dtype(dtype).itemsize
for value in [(-1) * (1000 ** (isize-1)), -50.0, 0.0, 50.0, 1000 ** (isize-1)]:
arr = np.full((4, 4), value, dtype=dtype)
arr_pooled = ia.pool(arr, 2, func)
dt = np.result_type(arr_pooled, 1.)
y = np.array(arr_pooled, dtype=dt, copy=False, subok=True)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == np.dtype(dtype)
assert _allclose(arr_pooled, high_res_dt(value))
arr = np.full((4, 4, 3), value, dtype=dtype)
arr_pooled = ia.pool(arr, 2, func)
assert arr_pooled.shape == (2, 2, 3)
assert arr_pooled.dtype == np.dtype(dtype)
assert _allclose(arr_pooled, high_res_dt(value))
# ----
# bool
# ----
arr = np.zeros((4, 4), dtype=bool)
arr[0, 0] = True
arr[0, 1] = True
arr[1, 0] = True
arr_pooled = ia.pool(arr, 2, np.min)
assert arr_pooled.dtype == arr.dtype
assert np.all(arr_pooled == 0)
arr_pooled = ia.pool(arr, 2, np.average)
assert arr_pooled.dtype == arr.dtype
assert np.all(arr_pooled[0, 0] == 1)
assert np.all(arr_pooled[:, 1] == 0)
assert np.all(arr_pooled[1, :] == 0)
arr_pooled = ia.pool(arr, 2, np.max)
assert arr_pooled.dtype == arr.dtype
assert np.all(arr_pooled[0, 0] == 1)
assert np.all(arr_pooled[:, 1] == 0)
assert np.all(arr_pooled[1, :] == 0)
# preserve_dtype off
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.pool(arr, 2, np.average, preserve_dtype=False)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == np.float64
assert np.allclose(arr_pooled[0, 0], np.average([0, 1, 4, 5]))
assert np.allclose(arr_pooled[0, 1], np.average([2, 3, 6, 7]))
assert np.allclose(arr_pooled[1, 0], np.average([8, 9, 12, 13]))
assert np.allclose(arr_pooled[1, 1], np.average([10, 11, 14, 15]))
# maximum function
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.pool(arr, 2, np.max)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.max([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(np.max([2, 3, 6, 7]))
assert arr_pooled[1, 0] == int(np.max([8, 9, 12, 13]))
assert arr_pooled[1, 1] == int(np.max([10, 11, 14, 15]))
# 3d array
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr = np.tile(arr[..., np.newaxis], (1, 1, 3))
arr_pooled = ia.pool(arr, 2, np.average)
assert arr_pooled.shape == (2, 2, 3)
assert np.array_equal(arr_pooled[..., 0], arr_pooled[..., 1])
assert np.array_equal(arr_pooled[..., 1], arr_pooled[..., 2])
arr_pooled = arr_pooled[..., 0]
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.average([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(np.average([2, 3, 6, 7]))
assert arr_pooled[1, 0] == int(np.average([8, 9, 12, 13]))
assert arr_pooled[1, 1] == int(np.average([10, 11, 14, 15]))
# block_size per axis
arr = np.float32([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.pool(arr, (2, 1), np.average)
assert arr_pooled.shape == (2, 4)
assert arr_pooled.dtype == arr.dtype.type
assert np.allclose(arr_pooled[0, 0], np.average([0, 4]))
assert np.allclose(arr_pooled[0, 1], np.average([1, 5]))
assert np.allclose(arr_pooled[0, 2], np.average([2, 6]))
assert np.allclose(arr_pooled[0, 3], np.average([3, 7]))
assert np.allclose(arr_pooled[1, 0], np.average([8, 12]))
assert np.allclose(arr_pooled[1, 1], np.average([9, 13]))
assert np.allclose(arr_pooled[1, 2], np.average([10, 14]))
assert np.allclose(arr_pooled[1, 3], np.average([11, 15]))
# cval
arr = np.uint8([
[0, 1, 2],
[4, 5, 6],
[8, 9, 10]
])
arr_pooled = ia.pool(arr, 2, np.average)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.average([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(np.average([2, 0, 6, 0]))
assert arr_pooled[1, 0] == int(np.average([8, 9, 0, 0]))
assert arr_pooled[1, 1] == int(np.average([10, 0, 0, 0]))
arr = np.uint8([
[0, 1],
[4, 5]
])
arr_pooled = ia.pool(arr, (4, 1), np.average)
assert arr_pooled.shape == (1, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.average([0, 4, 0, 0]))
assert arr_pooled[0, 1] == int(np.average([1, 5, 0, 0]))
arr = np.uint8([
[0, 1, 2],
[4, 5, 6],
[8, 9, 10]
])
arr_pooled = ia.pool(arr, 2, np.average, pad_cval=22)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.average([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(np.average([2, 22, 6, 22]))
assert arr_pooled[1, 0] == int(np.average([8, 9, 22, 22]))
assert arr_pooled[1, 1] == int(np.average([10, 22, 22, 22]))
# padding mode
arr = np.uint8([
[0, 1, 2],
[4, 5, 6],
[8, 9, 10]
])
arr_pooled = ia.pool(arr, 2, np.average, pad_mode="edge")
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.average([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(np.average([2, 2, 6, 6]))
assert arr_pooled[1, 0] == int(np.average([8, 9, 8, 9]))
assert arr_pooled[1, 1] == int(np.average([10, 10, 10, 10]))
# same as above, but with float32 to make averages more accurate
arr = np.float32([
[0, 1, 2],
[4, 5, 6],
[8, 9, 10]
])
arr_pooled = ia.pool(arr, 2, np.average, pad_mode="edge")
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert np.isclose(arr_pooled[0, 0], np.average([0, 1, 4, 5]))
assert np.isclose(arr_pooled[0, 1], np.average([2, 2, 6, 6]))
assert np.isclose(arr_pooled[1, 0], np.average([8, 9, 8, 9]))
assert np.isclose(arr_pooled[1, 1], np.average([10, 10, 10, 10]))
# TODO add test that verifies the default padding mode
def test_avg_pool():
# very basic test, as avg_pool() just calls pool(), which is tested in test_pool()
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.avg_pool(arr, 2)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
# add 1e-4 here to force 0.5 to be rounded up, as that's how OpenCV
# handles it
assert arr_pooled[0, 0] == int(np.round(1e-4 + np.average([0, 1, 4, 5])))
assert arr_pooled[0, 1] == int(np.round(1e-4 + np.average([2, 3, 6, 7])))
assert arr_pooled[1, 0] == int(np.round(1e-4 + np.average([8, 9, 12, 13])))
assert arr_pooled[1, 1] == int(np.round(1e-4 + np.average([10, 11, 14, 15])))
# TODO add test that verifies the default padding mode
def test_max_pool():
# very basic test, as max_pool() just calls pool(), which is tested in
# test_pool()
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.max_pool(arr, 2)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.max([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(np.max([2, 3, 6, 7]))
assert arr_pooled[1, 0] == int(np.max([8, 9, 12, 13]))
assert arr_pooled[1, 1] == int(np.max([10, 11, 14, 15]))
# TODO add test that verifies the default padding mode
def test_min_pool():
# very basic test, as min_pool() just calls pool(), which is tested in
# test_pool()
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.min_pool(arr, 2)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.min([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(np.min([2, 3, 6, 7]))
assert arr_pooled[1, 0] == int(np.min([8, 9, 12, 13]))
assert arr_pooled[1, 1] == int(np.min([10, 11, 14, 15]))
# TODO add test that verifies the default padding mode
def test_median_pool():
# very basic test, as median_pool() just calls pool(), which is tested in
# test_pool()
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.median_pool(arr, 2)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.median([0, 1, 4, 5]))
assert arr_pooled[0, 1] == int(np.median([2, 3, 6, 7]))
assert arr_pooled[1, 0] == int(np.median([8, 9, 12, 13]))
assert arr_pooled[1, 1] == int(np.median([10, 11, 14, 15]))
# TODO add test that verifies the default padding mode
def test_median_pool_ksize_1_3():
# very basic test, as median_pool() just calls pool(), which is tested in
# test_pool()
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.median_pool(arr, (1, 3))
assert arr_pooled.shape == (4, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.median([0, 1, 2]))
assert arr_pooled[0, 1] == int(np.median([3, 2, 1]))
assert arr_pooled[1, 0] == int(np.median([4, 5, 6]))
assert arr_pooled[1, 1] == int(np.median([7, 6, 5]))
assert arr_pooled[2, 0] == int(np.median([8, 9, 10]))
assert arr_pooled[2, 1] == int(np.median([11, 10, 9]))
assert arr_pooled[3, 0] == int(np.median([12, 13, 14]))
assert arr_pooled[3, 1] == int(np.median([15, 14, 13]))
def test_median_pool_ksize_3():
# After padding:
# [5, 4, 5, 6, 7, 6],
# [1, 0, 1, 2, 3, 2],
# [5, 4, 5, 6, 7, 6],
# [9, 8, 9, 10, 11, 10],
# [13, 12, 13, 14, 15, 14],
# [9, 8, 9, 10, 11, 10]
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
])
arr_pooled = ia.median_pool(arr, 3)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.median([5, 4, 5, 1, 0, 1, 5, 4, 5]))
assert arr_pooled[0, 1] == int(np.median([6, 7, 6, 2, 3, 2, 6, 7, 6]))
assert arr_pooled[1, 0] == int(np.median([9, 8, 9, 13, 12, 13, 9, 8, 9]))
assert arr_pooled[1, 1] == int(np.median([10, 11, 10, 14, 15, 13, 10, 11,
10]))
def test_median_pool_ksize_3_view():
# After padding:
# [5, 4, 5, 6, 7, 6],
# [1, 0, 1, 2, 3, 2],
# [5, 4, 5, 6, 7, 6],
# [9, 8, 9, 10, 11, 10],
# [13, 12, 13, 14, 15, 14],
# [9, 8, 9, 10, 11, 10]
arr = np.uint8([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15],
[0, 0, 0, 0]
])
arr_in = arr[0:4, :]
assert arr_in.flags["OWNDATA"] is False
assert arr_in.flags["C_CONTIGUOUS"] is True
arr_pooled = ia.median_pool(arr_in, 3)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.median([5, 4, 5, 1, 0, 1, 5, 4, 5]))
assert arr_pooled[0, 1] == int(np.median([6, 7, 6, 2, 3, 2, 6, 7, 6]))
assert arr_pooled[1, 0] == int(np.median([9, 8, 9, 13, 12, 13, 9, 8, 9]))
assert arr_pooled[1, 1] == int(np.median([10, 11, 10, 14, 15, 13, 10, 11,
10]))
def test_median_pool_ksize_3_non_contiguous():
# After padding:
# [5, 4, 5, 6, 7, 6],
# [1, 0, 1, 2, 3, 2],
# [5, 4, 5, 6, 7, 6],
# [9, 8, 9, 10, 11, 10],
# [13, 12, 13, 14, 15, 14],
# [9, 8, 9, 10, 11, 10]
arr = np.array([
[0, 1, 2, 3],
[4, 5, 6, 7],
[8, 9, 10, 11],
[12, 13, 14, 15]
], dtype=np.uint8, order="F")
assert arr.flags["OWNDATA"] is True
assert arr.flags["C_CONTIGUOUS"] is False
arr_pooled = ia.median_pool(arr, 3)
assert arr_pooled.shape == (2, 2)
assert arr_pooled.dtype == arr.dtype.type
assert arr_pooled[0, 0] == int(np.median([5, 4, 5, 1, 0, 1, 5, 4, 5]))
assert arr_pooled[0, 1] == int(np.median([6, 7, 6, 2, 3, 2, 6, 7, 6]))
assert arr_pooled[1, 0] == int(np.median([9, 8, 9, 13, 12, 13, 9, 8, 9]))
assert arr_pooled[1, 1] == int(np.median([10, 11, 10, 14, 15, 13, 10, 11,
10]))
def test_draw_grid():
# bool
dtype = bool
image = np.zeros((2, 2, 3), dtype=dtype)
image[0, 0] = False
image[0, 1] = True
image[1, 0] = True
image[1, 1] = False
grid = ia.draw_grid([image], rows=1, cols=1)
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, image)
grid = ia.draw_grid(np.array([image], dtype=dtype), rows=1, cols=1)
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, image)
grid = ia.draw_grid([image, image, image, image], rows=2, cols=2)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
grid = ia.draw_grid([image, image], rows=1, cols=2)
expected = np.hstack([image, image])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=2, cols=None)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=None, cols=2)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=None, cols=None)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
# int, uint
for dtype in [np.uint8, np.uint16, np.uint32, np.uint64, np.int8, np.int16, np.int32, np.int64]:
min_value, center_value, max_value = iadt.get_value_range_of_dtype(dtype)
image = np.zeros((2, 2, 3), dtype=dtype)
image[0, 0] = min_value
image[0, 1] = center_value
image[1, 0] = center_value + int(0.3 * max_value)
image[1, 1] = max_value
grid = ia.draw_grid([image], rows=1, cols=1)
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, image)
grid = ia.draw_grid(np.array([image], dtype=dtype), rows=1, cols=1)
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, image)
grid = ia.draw_grid([image, image, image, image], rows=2, cols=2)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert np.array_equal(grid, expected)
grid = ia.draw_grid([image, image], rows=1, cols=2)
expected = np.hstack([image, image])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=2, cols=None)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=None, cols=2)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=None, cols=None)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert np.array_equal(grid, expected)
# float
try:
_high_res_dt = np.float128
dtypes = ["float16", "float32", "float64", "float128"]
except AttributeError:
_high_res_dt = np.float64
dtypes = ["float16", "float32", "float64"]
for dtype in dtypes:
dtype = np.dtype(dtype)
def _allclose(a, b):
atol = 1e-4 if dtype == np.float16 else 1e-8
return np.allclose(a, b, atol=atol, rtol=0)
image = np.zeros((2, 2, 3), dtype=dtype)
isize = np.dtype(dtype).itemsize
image[0, 0] = (-1) * (1000 ** (isize-1))
image[0, 1] = -10.0
image[1, 0] = 10.0
image[1, 1] = 1000 ** (isize-1)
grid = ia.draw_grid([image], rows=1, cols=1)
assert grid.dtype == np.dtype(dtype)
assert _allclose(grid, image)
grid = ia.draw_grid(np.array([image], dtype=dtype), rows=1, cols=1)
assert grid.dtype == np.dtype(dtype)
assert _allclose(grid, image)
grid = ia.draw_grid([image, image, image, image], rows=2, cols=2)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert _allclose(grid, expected)
grid = ia.draw_grid([image, image], rows=1, cols=2)
expected = np.hstack([image, image])
assert grid.dtype == np.dtype(dtype)
assert _allclose(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=2, cols=None)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert _allclose(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=None, cols=2)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert _allclose(grid, expected)
grid = ia.draw_grid([image, image, image, image], rows=None, cols=None)
expected = np.vstack([
np.hstack([image, image]),
np.hstack([image, image])
])
assert grid.dtype == np.dtype(dtype)
assert _allclose(grid, expected)
def test_classes_and_functions_marked_deprecated():
import imgaug.imgaug as iia
# class
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_kp = iia.Keypoint(x=1, y=2)
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
# function
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_result = iia.compute_geometric_median(np.float32([[0, 0]]))
assert len(caught_warnings) == 1
assert "is deprecated" in str(caught_warnings[-1].message)
# no deprecated warning for calls to imgaug.
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
_kp = ia.Keypoint(x=1, y=2)
assert len(caught_warnings) == 0
class Test_apply_lut(unittest.TestCase):
def test_2d_image(self):
table = np.mod(np.arange(256) + 10, 256).astype(np.uint8)
image = np.uint8([
[0, 50, 100, 245, 254, 255],
[1, 51, 101, 246, 255, 0]
])
image_aug = ia.apply_lut(image, table)
expected = np.uint8([
[10, 60, 110, 255, 8, 9],
[11, 61, 111, 0, 9, 10]
])
assert np.array_equal(image_aug, expected)
assert image_aug is not image
assert image_aug.shape == (2, 6)
assert image_aug.dtype.name == "uint8"
class Test_apply_lut_(unittest.TestCase):
def test_2d_image(self):
table = np.mod(np.arange(256) + 10, 256).astype(np.uint8)
tables = [
("array-1d", table),
("array-2d", table[:, np.newaxis]),
("array-3d", table[np.newaxis, :, np.newaxis]),
("list", [table])
]
for subtable_descr, subtable in tables:
with self.subTest(table_type=subtable_descr):
image = np.uint8([
[0, 50, 100, 245, 254, 255],
[1, 51, 101, 246, 255, 0]
])
image_aug = ia.apply_lut_(image, subtable)
expected = np.uint8([
[10, 60, 110, 255, 8, 9],
[11, 61, 111, 0, 9, 10]
])
assert np.array_equal(image_aug, expected)
assert image_aug is image
assert image_aug.shape == (2, 6)
assert image_aug.dtype.name == "uint8"
def test_HW1_image(self):
table = np.mod(np.arange(256) + 10, 256).astype(np.uint8)
tables = [
("array-1d", table),
("array-2d", table[:, np.newaxis]),
("array-3d", table[np.newaxis, :, np.newaxis]),
("list", [table])
]
for subtable_descr, subtable in tables:
with self.subTest(table_type=subtable_descr):
image = np.uint8([
[0, 50, 100, 245, 254, 255],
[1, 51, 101, 246, 255, 0]
])
image = image[:, :, np.newaxis]
image_aug = ia.apply_lut_(image, subtable)
expected = np.uint8([
[10, 60, 110, 255, 8, 9],
[11, 61, 111, 0, 9, 10]
])
expected = expected[:, :, np.newaxis]
assert np.array_equal(image_aug, expected)
# (H,W,1) images always lead to a copy
assert image_aug is not image
assert image_aug.shape == (2, 6, 1)
assert image_aug.dtype.name == "uint8"
def test_HWC_image(self):
# Base table, mapping all values to value+10.
# For channels C>0 we additionally add +C below.
table_base = np.mod(np.arange(256) + 10, 256).astype(np.int32)
nb_channels_lst = [2, 3, 4, 5, 511, 512, 513, 512*2-1, 512*2, 512*2+1]
for nb_channels in nb_channels_lst:
# Create channelwise LUT.
tables = []
for c in np.arange(nb_channels):
tables.append(np.mod(table_base + c, 256).astype(np.uint8))
tables_by_type = [
("array-1d", table_base.astype(np.uint8)),
("array-2d", np.stack(tables, axis=-1)),
("array-3d", np.stack(tables, axis=-1).reshape((1, 256, -1))),
("list", tables)
]
for subtable_descr, subtable in tables_by_type:
with self.subTest(nb_channels=nb_channels,
table_type=subtable_descr):
# Create a normalized lut table, so that we can easily
# find the projected value via x,y,c coordinates.
# In case of array-1d, all channels are treated the same
# way.
if subtable_descr == "array-1d":
tables_3d = np.stack([table_base] * nb_channels,
axis=-1)
else:
tables_3d = np.stack(tables, axis=-1).reshape(
(256, -1))
image = np.int32([
[0, 50, 100, 245, 254, 255],
[1, 51, 101, 246, 255, 0]
])
image = image[:, :, np.newaxis]
image = np.tile(image, (1, 1, nb_channels))
for c in np.arange(nb_channels):
image[:, :, c] += c
image = np.mod(image, 256).astype(np.uint8)
image_orig = np.copy(image)
image_aug = ia.apply_lut_(image, subtable)
# Reproduce effect of a LUT mapping on the input
# image.
expected = np.zeros_like(image_orig)
for c in np.arange(nb_channels):
for x in np.arange(image.shape[1]):
for y in np.arange(image.shape[0]):
v = image_orig[y, x, c]
v_proj = tables_3d[v, c]
expected[y, x, c] = v_proj
assert np.array_equal(image_aug, expected)
if nb_channels < 512:
assert image_aug is image
assert image_aug.shape == (2, 6, nb_channels)
assert image_aug.dtype.name == "uint8"
def test_image_is_noncontiguous(self):
table = np.mod(np.arange(256) + 10, 256).astype(np.uint8)
image = np.uint8([
[0, 50, 100, 245, 254, 255],
[1, 51, 101, 246, 255, 0]
])
image = np.fliplr(image)
assert image.flags["C_CONTIGUOUS"] is False
image_aug = ia.apply_lut_(image, table)
expected = np.uint8([
[10, 60, 110, 255, 8, 9],
[11, 61, 111, 0, 9, 10]
])
assert np.array_equal(np.fliplr(image_aug), expected)
assert image_aug is not image # non-contiguous should lead to copy
assert image_aug.shape == (2, 6)
assert image_aug.dtype.name == "uint8"
def test_image_is_view(self):
table = np.mod(np.arange(256) + 10, 256).astype(np.uint8)
image = np.uint8([
[0, 50, 100, 245, 254, 255],
[1, 51, 101, 246, 255, 0]
])
image = image[:, 1:4]
assert image.flags["OWNDATA"] is False
image_aug = ia.apply_lut_(image, table)
expected = np.uint8([
[60, 110, 255],
[61, 111, 0]
])
assert np.array_equal(image_aug, expected)
assert image_aug is not image # non-owndata should lead to copy
assert image_aug.shape == (2, 3)
assert image_aug.dtype.name == "uint8"
def test_zero_sized_axes(self):
table = np.mod(np.arange(256) + 10, 256).astype(np.uint8)
shapes = [
(0, 0),
(0, 1),
(1, 0),
(0, 1, 0),
(1, 0, 0),
(0, 1, 1),
(1, 0, 1)
]
for shape in shapes:
with self.subTest(shape=shape):
image = np.zeros(shape, dtype=np.uint8)
image_aug = ia.apply_lut_(image, table)
assert image_aug.shape == shape
assert image_aug.dtype.name == "uint8"
if __name__ == "__main__":
main()
================================================
FILE: test/test_multicore.py
================================================
from __future__ import print_function, division, absolute_import
import time
import multiprocessing
import pickle
from collections import defaultdict
import warnings
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import six.moves as sm
import imgaug as ia
import imgaug.multicore as multicore
import imgaug.random as iarandom
from imgaug import augmenters as iaa
from imgaug.testutils import reseed
from imgaug.augmentables.batches import Batch, UnnormalizedBatch
IS_SUPPORTING_CONTEXTS = (sys.version_info[0] == 3
and sys.version_info[1] >= 4)
class clean_context():
def __init__(self):
self.old_context = None
def __enter__(self):
self.old_context = multicore._CONTEXT
multicore._CONTEXT = None
def __exit__(self, exc_type, exc_val, exc_tb):
multicore._CONTEXT = self.old_context
class Test__get_context(unittest.TestCase):
@unittest.skipUnless(not IS_SUPPORTING_CONTEXTS,
"Behaviour happens only in python <=3.3")
@mock.patch("imgaug.imgaug.warn")
@mock.patch("platform.version")
def test_mocked_nixos_python2(self, mock_version, mock_warn):
with clean_context():
mock_version.return_value = "NixOS"
_ctx = multicore._get_context()
assert mock_warn.call_count == 1
@unittest.skipUnless(IS_SUPPORTING_CONTEXTS,
"Behaviour is only supported in python 3.4+")
@mock.patch("platform.version")
@mock.patch("multiprocessing.get_context")
def test_mocked_nixos_python3(self, mock_gctx, mock_version):
with clean_context():
mock_version.return_value = "NixOS"
_ctx = multicore._get_context()
mock_gctx.assert_called_once_with("spawn")
@unittest.skipUnless(not IS_SUPPORTING_CONTEXTS,
"Behaviour happens only in python <=3.3")
@mock.patch("platform.version")
def test_mocked_no_nixos_python2(self, mock_version):
with clean_context():
mock_version.return_value = "Ubuntu"
ctx = multicore._get_context()
assert ctx is multiprocessing
@unittest.skipUnless(IS_SUPPORTING_CONTEXTS,
"Behaviour is only supported in python 3.4+")
@mock.patch("platform.system")
@mock.patch("multiprocessing.get_context")
@mock.patch("platform.version")
def test_mocked_no_nixos_python3(self, mock_version, mock_gctx, mock_system):
with clean_context():
mock_version.return_value = "Ubuntu"
mock_system.return_value = "Linux"
_ctx = multicore._get_context()
assert mock_gctx.call_count == 1
assert mock_gctx.call_args_list[0][0][0] is None
@unittest.skipUnless(IS_SUPPORTING_CONTEXTS,
"Behaviour is only supported in python 3.4+")
@mock.patch.object(sys, "version_info")
@mock.patch("platform.system")
@mock.patch("multiprocessing.get_context")
@mock.patch("platform.version")
def test_mocked_mac_and_37_cause_spawn(
self,
mock_version,
mock_gctx,
mock_system,
mock_vi
):
with clean_context():
def version_info(index):
if isinstance(index, slice):
return 3, 7
return 3 if index == 0 else 7
mock_vi.__getitem__.side_effect = version_info
mock_version.return_value = "foo"
mock_system.return_value = "Darwin"
_ctx = multicore._get_context()
mock_gctx.assert_called_once_with("spawn")
class TestPool(unittest.TestCase):
def setUp(self):
reseed()
def test___init___seed_out_of_bounds(self):
augseq = iaa.Identity()
with self.assertRaises(AssertionError) as context:
_ = multicore.Pool(augseq, seed=iarandom.SEED_MAX_VALUE + 100)
assert "Expected `seed` to be" in str(context.exception)
def test_property_pool(self):
mock_Pool = mock.MagicMock()
mock_Pool.return_value = mock_Pool
mock_Pool.close.return_value = None
mock_Pool.join.return_value = None
# We cannot just mock multiprocessing.Pool here, because of using
# a custom context. We would have to mock each possible context's
# Pool() method or overwrite here the Pool() method of the
# actually used context.
with mock.patch("multiprocessing.pool.Pool", mock_Pool):
augseq = iaa.Identity()
pool_config = multicore.Pool(
augseq, processes=1, maxtasksperchild=4, seed=123)
with pool_config as pool:
assert pool.processes == 1
assert pool._pool is None
assert mock_Pool.call_count == 1
assert mock_Pool.close.call_count == 1
assert mock_Pool.join.call_count == 1
# see
# https://github.com/
# python/cpython/blob/master/Lib/multiprocessing/context.py
# L119 (method Pool()) for an example of how Pool() is called
# internally.
assert mock_Pool.call_args[0][0] == 1 # processes
assert mock_Pool.call_args[0][1] is multicore._Pool_initialize_worker
assert mock_Pool.call_args[0][2] == (augseq, 123)
assert mock_Pool.call_args[0][3] == 4
def test_processes(self):
augseq = iaa.Identity()
mock_Pool = mock.MagicMock()
mock_cpu_count = mock.Mock()
# We cannot just mock multiprocessing.Pool here, because of using
# a custom context. We would have to mock each possible context's
# Pool() method or overwrite here the Pool() method of the
# actually used context.
patch_pool = mock.patch("multiprocessing.pool.Pool", mock_Pool)
# Multiprocessing seems to always access os.cpu_count to get the
# current count of cpu cores.
# See
# https://github.com/
# python/cpython/blob/master/Lib/multiprocessing/context.py
# L41.
fname = ("os.cpu_count" if IS_SUPPORTING_CONTEXTS
else "multiprocessing.cpu_count")
patch_cpu_count = mock.patch(fname, mock_cpu_count)
with patch_pool, patch_cpu_count:
# (cpu cores available, processes requested, processes started)
combos = [
(1, 1, 1),
(2, 1, 1),
(3, 1, 1),
(1, 2, 2),
(3, 2, 2),
(1, None, None),
(2, None, None),
(3, None, None),
(1, -1, 1),
(2, -1, 1),
(3, -1, 2),
(4, -2, 2)
]
for cores_available, processes_req, expected in combos:
with self.subTest(cpu_count_available=cores_available,
processes_requested=processes_req):
mock_cpu_count.return_value = cores_available
with multicore.Pool(augseq,
processes=processes_req) as _pool:
pass
if expected is None:
assert mock_Pool.call_args[0][0] is None
else:
assert mock_Pool.call_args[0][0] == expected
@mock.patch("multiprocessing.pool.Pool")
def test_cpu_count_does_not_exist(self, mock_pool):
def _side_effect():
raise NotImplementedError
old_method = multicore._get_context().cpu_count
mock_cpu_count = mock.Mock()
mock_cpu_count.side_effect = _side_effect
multicore._get_context().cpu_count = mock_cpu_count
augseq = iaa.Identity()
with warnings.catch_warnings(record=True) as caught_warnings:
warnings.simplefilter("always")
with multicore.Pool(augseq, processes=-1):
pass
assert mock_cpu_count.call_count == 1
assert mock_pool.call_count == 1
# 'processes' arg to Pool was expected to be set to None as cpu_count
# produced an error
assert mock_pool.call_args_list[0][0][0] is None
assert len(caught_warnings) == 1
assert (
"Could not find method multiprocessing.cpu_count(). "
in str(caught_warnings[-1].message))
multicore._get_context().cpu_count = old_method
@classmethod
def _test_map_batches_both(cls, call_async):
for clazz in [Batch, UnnormalizedBatch]:
augseq = iaa.Identity()
mock_Pool = mock.MagicMock()
mock_Pool.return_value = mock_Pool
mock_Pool.map.return_value = "X"
mock_Pool.map_async.return_value = "X"
with mock.patch("multiprocessing.pool.Pool", mock_Pool):
batches = [
clazz(images=[ia.data.quokka()]),
clazz(images=[ia.data.quokka()+1])
]
with multicore.Pool(augseq, processes=1) as pool:
if call_async:
_ = pool.map_batches_async(batches)
else:
_ = pool.map_batches(batches)
if call_async:
to_check = mock_Pool.map_async
else:
to_check = mock_Pool.map
assert to_check.call_count == 1
# args, arg 0
assert to_check.call_args[0][0] == multicore._Pool_starworker
# args, arg 1 (batches with ids), tuple 0,
# entry 0 in tuple (=> batch id)
assert to_check.call_args[0][1][0][0] == 0
# args, arg 1 (batches with ids), tuple 0,
# entry 1 in tuple (=> batch)
assert np.array_equal(
to_check.call_args[0][1][0][1].images_unaug,
batches[0].images_unaug)
# args, arg 1 (batches with ids), tuple 1,
# entry 0 in tuple (=> batch id)
assert to_check.call_args[0][1][1][0] == 1
# args, arg 1 (batches with ids), tuple 1,
# entry 1 in tuple (=> batch)
assert np.array_equal(
to_check.call_args[0][1][1][1].images_unaug,
batches[1].images_unaug)
def test_map_batches(self):
self._test_map_batches_both(call_async=False)
def test_map_batches_async(self):
self._test_map_batches_both(call_async=True)
@classmethod
def _test_imap_batches_both(cls, call_unordered):
for clazz in [Batch, UnnormalizedBatch]:
batches = [clazz(images=[ia.data.quokka()]),
clazz(images=[ia.data.quokka()+1])]
def _generate_batches():
for batch in batches:
yield batch
augseq = iaa.Identity()
mock_Pool = mock.MagicMock()
mock_Pool.return_value = mock_Pool
mock_Pool.imap.return_value = batches
mock_Pool.imap_unordered.return_value = batches
with mock.patch("multiprocessing.pool.Pool", mock_Pool):
with multicore.Pool(augseq, processes=1) as pool:
gen = _generate_batches()
if call_unordered:
_ = list(pool.imap_batches_unordered(gen))
else:
_ = list(pool.imap_batches(gen))
if call_unordered:
to_check = mock_Pool.imap_unordered
else:
to_check = mock_Pool.imap
assert to_check.call_count == 1
assert to_check.call_args[0][0] == multicore._Pool_starworker
# convert generator to list, make it subscriptable
arg_batches = list(to_check.call_args[0][1])
# args, arg 1 (batches with ids), tuple 0,
# entry 0 in tuple (=> batch id)
assert arg_batches[0][0] == 0
# tuple 0, entry 1 in tuple (=> batch)
assert np.array_equal(
arg_batches[0][1].images_unaug,
batches[0].images_unaug)
# tuple 1, entry 0 in tuple (=> batch id)
assert arg_batches[1][0] == 1
# tuple 1, entry 1 in tuple (=> batch)
assert np.array_equal(
arg_batches[1][1].images_unaug,
batches[1].images_unaug)
@classmethod
def _test_imap_batches_both_output_buffer_size(cls, call_unordered,
timeout=0.075):
batches = [
ia.Batch(images=[np.full((1, 1), i, dtype=np.uint8)])
for i in range(8)]
def _generate_batches(times):
for batch in batches:
yield batch
times.append(time.time())
def callfunc(pool, gen, output_buffer_size):
func = (
pool.imap_batches_unordered
if call_unordered
else pool.imap_batches
)
for v in func(gen, output_buffer_size=output_buffer_size):
yield v
def contains_all_ids(inputs):
arrs = np.uint8([batch.images_aug for batch in inputs])
ids_uq = np.unique(arrs)
return (
len(ids_uq) == len(batches)
and np.all(0 <= ids_uq)
and np.all(ids_uq < len(batches))
)
augseq = iaa.Identity()
with multicore.Pool(augseq, processes=1) as pool:
# no output buffer limit, there should be no noteworthy lag
# for any batch requested from _generate_batches()
times = []
gen = callfunc(pool, _generate_batches(times), None)
result = next(gen)
time.sleep(timeout)
result = [result] + list(gen)
times = np.float64(times)
times_diffs = times[1:] - times[0:-1]
assert np.all(times_diffs < timeout * 1.01)
assert contains_all_ids(result)
# with output buffer limit, but set to the number of batches,
# i.e. should again not lead to any lag
times = []
gen = callfunc(pool, _generate_batches(times), len(batches))
result = next(gen)
time.sleep(timeout)
result = [result] + list(gen)
times = np.float64(times)
times_diffs = times[1:] - times[0:-1]
assert np.all(times_diffs < timeout * 1.01)
assert contains_all_ids(result)
# With output buffer limit of #batches/2 (=4), followed by a
# timeout after starting the loading process. This should quickly
# load batches until the buffer is full, then wait until the
# batches are requested from the buffer (i.e. after the timeout
# ended) and then proceed to produce batches at the speed at which
# they are requested. This should lead to a measureable lag between
# batch 4 and 5 (matching the timeout).
times = []
gen = callfunc(pool, _generate_batches(times), 4)
result = next(gen)
time.sleep(timeout)
result = [result] + list(gen)
times = np.float64(times)
times_diffs = times[1:] - times[0:-1]
# use -1 here because we have N-1 times for N batches as
# diffs denote diffs between Nth and N+1th batch
assert np.all(times_diffs[0:4-1] < timeout * 1.01)
assert np.all(times_diffs[4-1:4-1+1] >= timeout * 0.99)
assert np.all(times_diffs[4-1+1:] < timeout * 1.01)
assert contains_all_ids(result)
def test_imap_batches(self):
self._test_imap_batches_both(call_unordered=False)
def test_imap_batches_unordered(self):
self._test_imap_batches_both(call_unordered=True)
def test_imap_batches_output_buffer_size(self):
self._test_imap_batches_both_output_buffer_size(call_unordered=False)
def test_imap_batches_unordered_output_buffer_size(self):
self._test_imap_batches_both_output_buffer_size(call_unordered=True)
@classmethod
def _assert_each_augmentation_not_more_than_once(cls, batches_aug):
sum_to_vecs = defaultdict(list)
for batch in batches_aug:
assert not np.array_equal(batch.images_aug[0], batch.images_aug[1])
vec = batch.images_aug.flatten()
vecsum = int(np.sum(vec))
if vecsum in sum_to_vecs:
for other_vec in sum_to_vecs[vecsum]:
assert not np.array_equal(vec, other_vec)
else:
sum_to_vecs[vecsum].append(vec)
def test_augmentations_with_seed_match(self):
nb_batches = 60
augseq = iaa.AddElementwise((0, 255))
image = np.zeros((10, 10, 1), dtype=np.uint8)
batch = ia.Batch(images=np.uint8([image, image]))
batches = [batch.deepcopy() for _ in sm.xrange(nb_batches)]
# seed=1
with multicore.Pool(augseq, processes=2, maxtasksperchild=30,
seed=1) as pool:
batches_aug1 = pool.map_batches(batches, chunksize=2)
# seed=1
with multicore.Pool(augseq, processes=2, seed=1) as pool:
batches_aug2 = pool.map_batches(batches, chunksize=1)
# seed=2
with multicore.Pool(augseq, processes=2, seed=2) as pool:
batches_aug3 = pool.map_batches(batches, chunksize=1)
assert len(batches_aug1) == nb_batches
assert len(batches_aug2) == nb_batches
assert len(batches_aug3) == nb_batches
for b1, b2, b3 in zip(batches_aug1, batches_aug2, batches_aug3):
# images were augmented
assert not np.array_equal(b1.images_unaug, b1.images_aug)
assert not np.array_equal(b2.images_unaug, b2.images_aug)
assert not np.array_equal(b3.images_unaug, b3.images_aug)
# original images still the same
assert np.array_equal(b1.images_unaug, batch.images_unaug)
assert np.array_equal(b2.images_unaug, batch.images_unaug)
assert np.array_equal(b3.images_unaug, batch.images_unaug)
# augmentations for same seed are the same
assert np.array_equal(b1.images_aug, b2.images_aug)
# augmentations for different seeds are different
assert not np.array_equal(b1.images_aug, b3.images_aug)
# make sure that batches for the two pools with same seed did not
# repeat within results (only between the results of the two pools)
for batches_aug in [batches_aug1, batches_aug2, batches_aug3]:
self._assert_each_augmentation_not_more_than_once(batches_aug)
def test_augmentations_with_seed_match_for_images_and_keypoints(self):
augseq = iaa.AddElementwise((0, 255))
image = np.zeros((10, 10, 1), dtype=np.uint8)
# keypoints here will not be changed by augseq, but they will induce
# deterministic mode to start in augment_batches() as each batch
# contains images AND keypoints
kps = ia.KeypointsOnImage([ia.Keypoint(x=2, y=0)], shape=(10, 10, 1))
batch = ia.Batch(images=np.uint8([image, image]), keypoints=[kps, kps])
batches = [batch.deepcopy() for _ in sm.xrange(60)]
# seed=1
with multicore.Pool(augseq, processes=2, maxtasksperchild=30,
seed=1) as pool:
batches_aug1 = pool.map_batches(batches, chunksize=2)
# seed=1
with multicore.Pool(augseq, processes=2, seed=1) as pool:
batches_aug2 = pool.map_batches(batches, chunksize=1)
# seed=2
with multicore.Pool(augseq, processes=2, seed=2) as pool:
batches_aug3 = pool.map_batches(batches, chunksize=1)
assert len(batches_aug1) == 60
assert len(batches_aug2) == 60
assert len(batches_aug3) == 60
for batches_aug in [batches_aug1, batches_aug2, batches_aug3]:
for batch in batches_aug:
for keypoints_aug in batch.keypoints_aug:
assert keypoints_aug.keypoints[0].x == 2
assert keypoints_aug.keypoints[0].y == 0
for b1, b2, b3 in zip(batches_aug1, batches_aug2, batches_aug3):
# images were augmented
assert not np.array_equal(b1.images_unaug, b1.images_aug)
assert not np.array_equal(b2.images_unaug, b2.images_aug)
assert not np.array_equal(b3.images_unaug, b3.images_aug)
# original images still the same
assert np.array_equal(b1.images_unaug, batch.images_unaug)
assert np.array_equal(b2.images_unaug, batch.images_unaug)
assert np.array_equal(b3.images_unaug, batch.images_unaug)
# augmentations for same seed are the same
assert np.array_equal(b1.images_aug, b2.images_aug)
# augmentations for different seeds are different
assert not np.array_equal(b1.images_aug, b3.images_aug)
# make sure that batches for the two pools with same seed did not
# repeat within results (only between the results of the two pools)
for batches_aug in [batches_aug1, batches_aug2, batches_aug3]:
self._assert_each_augmentation_not_more_than_once(batches_aug)
def test_augmentations_without_seed_differ(self):
augseq = iaa.AddElementwise((0, 255))
image = np.zeros((10, 10, 1), dtype=np.uint8)
batch = ia.Batch(images=np.uint8([image, image]))
batches = [batch.deepcopy() for _ in sm.xrange(20)]
with multicore.Pool(augseq, processes=2, maxtasksperchild=5) as pool:
batches_aug = pool.map_batches(batches, chunksize=2)
with multicore.Pool(augseq, processes=2) as pool:
batches_aug.extend(pool.map_batches(batches, chunksize=1))
assert len(batches_aug) == 2*20
self._assert_each_augmentation_not_more_than_once(batches_aug)
def test_augmentations_without_seed_differ_for_images_and_keypoints(self):
augseq = iaa.AddElementwise((0, 255))
image = np.zeros((10, 10, 1), dtype=np.uint8)
# keypoints here will not be changed by augseq, but they will
# induce deterministic mode to start in augment_batches() as each
# batch contains images AND keypoints
kps = ia.KeypointsOnImage([ia.Keypoint(x=2, y=0)], shape=(10, 10, 1))
batch = ia.Batch(images=np.uint8([image, image]), keypoints=[kps, kps])
batches = [batch.deepcopy() for _ in sm.xrange(20)]
with multicore.Pool(augseq, processes=2, maxtasksperchild=5) as pool:
batches_aug = pool.map_batches(batches, chunksize=2)
with multicore.Pool(augseq, processes=2) as pool:
batches_aug.extend(pool.map_batches(batches, chunksize=1))
assert len(batches_aug) == 2*20
for batch in batches_aug:
for keypoints_aug in batch.keypoints_aug:
assert keypoints_aug.keypoints[0].x == 2
assert keypoints_aug.keypoints[0].y == 0
self._assert_each_augmentation_not_more_than_once(batches_aug)
def test_inputs_not_lost(self):
"""Test to make sure that inputs (e.g. images) are never lost."""
def _assert_contains_all_ids(batches_aug):
# batch.images_unaug
ids = set()
for batch_aug in batches_aug:
ids.add(int(batch_aug.images_unaug.flat[0]))
ids.add(int(batch_aug.images_unaug.flat[1]))
for idx in sm.xrange(2*100):
assert idx in ids
assert len(ids) == 200
# batch.images_aug
ids = set()
for batch_aug in batches_aug:
ids.add(int(batch_aug.images_aug.flat[0]))
ids.add(int(batch_aug.images_aug.flat[1]))
for idx in sm.xrange(2*100):
assert idx in ids
assert len(ids) == 200
augseq = iaa.Identity()
image = np.zeros((1, 1, 1), dtype=np.uint8)
# creates batches containing images with ids from 0 to 199 (one pair
# of consecutive ids per batch)
batches = [
ia.Batch(images=np.uint8([image + b_idx*2, image + b_idx*2+1]))
for b_idx
in sm.xrange(100)]
with multicore.Pool(augseq, processes=2, maxtasksperchild=25) as pool:
batches_aug = pool.map_batches(batches)
_assert_contains_all_ids(batches_aug)
with multicore.Pool(augseq, processes=2, maxtasksperchild=25,
seed=1) as pool:
batches_aug = pool.map_batches(batches)
_assert_contains_all_ids(batches_aug)
with multicore.Pool(augseq, processes=3, seed=2) as pool:
batches_aug = pool.map_batches(batches)
_assert_contains_all_ids(batches_aug)
with multicore.Pool(augseq, processes=2, seed=None) as pool:
batches_aug = pool.map_batches(batches)
_assert_contains_all_ids(batches_aug)
batches_aug = pool.map_batches(batches)
_assert_contains_all_ids(batches_aug)
def test_close(self):
augseq = iaa.Identity()
with multicore.Pool(augseq, processes=2) as pool:
pool.close()
def test_terminate(self):
augseq = iaa.Identity()
with multicore.Pool(augseq, processes=2) as pool:
pool.terminate()
def test_join(self):
augseq = iaa.Identity()
with multicore.Pool(augseq, processes=2) as pool:
pool.close()
pool.join()
@mock.patch("multiprocessing.pool.Pool")
def test_join_via_mock(self, mock_pool):
# According to codecov, the join() does not get beyond its initial
# if statement in the test_join() test, even though it should be.
# Might be a simple travis multicore problem?
# It is tested here again via some mocking.
mock_pool.return_value = mock_pool
mock_pool.join.return_value = True
with multicore.Pool(iaa.Identity(), processes=2) as pool:
pool.join()
# Make sure that __exit__ does not call close(), which would then
# call join() again and we would get a call_count of 2
pool._pool = None
assert mock_pool.join.call_count == 1
# This should already be part of the Pool tests, but according to codecov
# it is not tested. Likely some travis error related to running multiple
# python processes.
class Test_Pool_initialize_worker(unittest.TestCase):
def tearDown(self):
# without this, other tests can break as e.g. the functions in
# multicore assert that _WORKER_AUGSEQ is None
multicore.Pool._WORKER_AUGSEQ = None
multicore.Pool._WORKER_SEED_START = None
@mock.patch("imgaug.multicore.Pool")
def test_with_seed_start(self, mock_ia_pool):
augseq = mock.MagicMock()
multicore._Pool_initialize_worker(augseq, 1)
assert mock_ia_pool._WORKER_SEED_START == 1
assert mock_ia_pool._WORKER_AUGSEQ is augseq
assert augseq.localize_random_state_.call_count == 1
@mock.patch.object(sys, 'version_info')
@mock.patch("time.time_ns", create=True) # doesnt exist in <=3.6
@mock.patch("imgaug.random.seed")
@mock.patch("multiprocessing.current_process")
def test_without_seed_start_simulate_py37_or_higher(self,
mock_cp,
mock_ia_seed,
mock_time_ns,
mock_vi):
def version_info(index):
return 3 if index == 0 else 7
mock_vi.__getitem__.side_effect = version_info
mock_time_ns.return_value = 1
mock_cp.return_value = mock.MagicMock()
mock_cp.return_value.name = "foo"
augseq = mock.MagicMock()
multicore._Pool_initialize_worker(augseq, None)
assert mock_time_ns.call_count == 1
assert mock_ia_seed.call_count == 1
assert augseq.seed_.call_count == 1
seed_global = mock_ia_seed.call_args_list[0][0][0]
seed_local = augseq.seed_.call_args_list[0][0][0]
assert seed_global != seed_local
@mock.patch.object(sys, 'version_info')
@mock.patch("time.time")
@mock.patch("imgaug.random.seed")
@mock.patch("multiprocessing.current_process")
def test_without_seed_start_simulate_py36_or_lower(self,
mock_cp,
mock_ia_seed,
mock_time,
mock_vi):
def version_info(index):
return 3 if index == 0 else 6
mock_vi.__getitem__.side_effect = version_info
mock_time.return_value = 1
mock_cp.return_value = mock.MagicMock()
mock_cp.return_value.name = "foo"
augseq = mock.MagicMock()
multicore._Pool_initialize_worker(augseq, None)
assert mock_time.call_count == 1
assert mock_ia_seed.call_count == 1
assert augseq.seed_.call_count == 1
seed_global = mock_ia_seed.call_args_list[0][0][0]
seed_local = augseq.seed_.call_args_list[0][0][0]
assert seed_global != seed_local
@mock.patch("imgaug.random.seed")
def test_without_seed_start(self, mock_ia_seed):
augseq = mock.MagicMock()
multicore._Pool_initialize_worker(augseq, None)
time.sleep(0.01)
multicore._Pool_initialize_worker(augseq, None)
seed_global_call_1 = mock_ia_seed.call_args_list[0][0][0]
seed_local_call_1 = augseq.seed_.call_args_list[0][0][0]
seed_global_call_2 = mock_ia_seed.call_args_list[0][0][0]
seed_local_call_2 = augseq.seed_.call_args_list[0][0][0]
assert (
seed_global_call_1
!= seed_local_call_1
!= seed_global_call_2
!= seed_local_call_2
), "Got seeds: %d, %d, %d, %d" % (
seed_global_call_1, seed_local_call_1,
seed_global_call_2, seed_local_call_2)
assert mock_ia_seed.call_count == 2
assert augseq.seed_.call_count == 2
# This should already be part of the Pool tests, but according to codecov
# it is not tested. Likely some travis error related to running multiple
# python processes.
class Test_Pool_worker(unittest.TestCase):
def tearDown(self):
# without this, other tests can break as e.g. the functions in
# multicore assert that _WORKER_AUGSEQ is None
multicore.Pool._WORKER_AUGSEQ = None
multicore.Pool._WORKER_SEED_START = None
def test_without_seed_start(self):
augseq = mock.MagicMock()
augseq.augment_batch_.return_value = "augmented_batch_"
image = np.zeros((1, 1, 3), dtype=np.uint8)
batch = UnnormalizedBatch(images=[image])
multicore.Pool._WORKER_AUGSEQ = augseq
result = multicore._Pool_worker(1, batch)
assert result == "augmented_batch_"
assert augseq.augment_batch_.call_count == 1
augseq.augment_batch_.assert_called_once_with(batch)
@mock.patch("imgaug.random.seed")
def test_with_seed_start(self, mock_ia_seed):
augseq = mock.MagicMock()
augseq.augment_batch_.return_value = "augmented_batch_"
image = np.zeros((1, 1, 3), dtype=np.uint8)
batch = UnnormalizedBatch(images=[image])
batch_idx = 1
seed_start = 10
multicore.Pool._WORKER_AUGSEQ = augseq
multicore.Pool._WORKER_SEED_START = seed_start
result = multicore._Pool_worker(batch_idx, batch)
# expected seeds used
seed = seed_start + batch_idx
seed_global_expected = (
iarandom.SEED_MIN_VALUE
+ (seed - 10**9)
% (iarandom.SEED_MAX_VALUE - iarandom.SEED_MIN_VALUE)
)
seed_local_expected = (
iarandom.SEED_MIN_VALUE
+ seed
% (iarandom.SEED_MAX_VALUE - iarandom.SEED_MIN_VALUE)
)
assert result == "augmented_batch_"
assert augseq.augment_batch_.call_count == 1
augseq.augment_batch_.assert_called_once_with(batch)
mock_ia_seed.assert_called_once_with(seed_global_expected)
augseq.seed_.assert_called_once_with(seed_local_expected)
# This should already be part of the Pool tests, but according to codecov
# it is not tested. Likely some travis error related to running multiple
# python processes.
class Test_Pool_starworker(unittest.TestCase):
def tearDown(self):
# without this, other tests can break as e.g. the functions in
# multicore assert that _WORKER_AUGSEQ is None
multicore.Pool._WORKER_AUGSEQ = None
multicore.Pool._WORKER_SEED_START = None
@mock.patch("imgaug.multicore._Pool_worker")
def test_simple_call(self, mock_worker):
image = np.zeros((1, 1, 3), dtype=np.uint8)
batch = UnnormalizedBatch(images=[image])
batch_idx = 1
mock_worker.return_value = "returned_batch"
result = multicore._Pool_starworker((batch_idx, batch))
assert result == "returned_batch"
mock_worker.assert_called_once_with(batch_idx, batch)
# ---------
# loading function used in TestBatchLoader.test_basic_functionality()
# it is outside of the test as putting it inside of it caused issues
# with spawn mode not being able to pickle this method, see issue #414.
def _batch_loader_load_func():
for _ in sm.xrange(20):
yield ia.Batch(images=np.zeros((2, 4, 4, 3), dtype=np.uint8))
# ---------
# Note that BatchLoader is deprecated
class TestBatchLoader(unittest.TestCase):
def setUp(self):
reseed()
def test_basic_functionality(self):
warnings.simplefilter("always")
with warnings.catch_warnings(record=True) as caught_warnings:
for nb_workers in [1, 2]:
# repeat these tests many times to catch rarer race conditions
for _ in sm.xrange(5):
loader = multicore.BatchLoader(
_batch_loader_load_func, queue_size=2,
nb_workers=nb_workers, threaded=True)
loaded = []
counter = 0
while ((not loader.all_finished()
or not loader.queue.empty())
and counter < 1000):
try:
batch = loader.queue.get(timeout=0.001)
loaded.append(batch)
except:
pass
counter += 1
assert len(loaded) == 20*nb_workers, \
"Expected %d to be loaded by threads, got %d for %d " \
"workers at counter %d." % (
20*nb_workers, len(loaded), nb_workers, counter
)
loader = multicore.BatchLoader(
_batch_loader_load_func, queue_size=200,
nb_workers=nb_workers, threaded=True)
loader.terminate()
assert loader.all_finished()
loader = multicore.BatchLoader(
_batch_loader_load_func, queue_size=2,
nb_workers=nb_workers, threaded=False)
loaded = []
counter = 0
while ((not loader.all_finished()
or not loader.queue.empty())
and counter < 1000):
try:
batch = loader.queue.get(timeout=0.001)
loaded.append(batch)
except:
pass
counter += 1
assert len(loaded) == 20*nb_workers, \
"Expected %d to be loaded by background processes, " \
"got %d for %d workers at counter %d." % (
20*nb_workers, len(loaded), nb_workers, counter
)
loader = multicore.BatchLoader(
_batch_loader_load_func, queue_size=200,
nb_workers=nb_workers, threaded=False)
loader.terminate()
assert loader.all_finished()
assert len(caught_warnings) > 0
for warning in caught_warnings:
assert "is deprecated" in str(warning.message)
# Note that BackgroundAugmenter is deprecated
class TestBackgroundAugmenter(unittest.TestCase):
def setUp(self):
reseed()
def test_augment_images_worker(self):
warnings.simplefilter("always")
with warnings.catch_warnings(record=True) as caught_warnings:
def gen():
yield ia.Batch(images=np.zeros((1, 4, 4, 3), dtype=np.uint8))
bl = multicore.BatchLoader(gen(), queue_size=2)
bgaug = multicore.BackgroundAugmenter(bl, iaa.Identity(),
queue_size=1, nb_workers=1)
queue_source = multiprocessing.Queue(2)
queue_target = multiprocessing.Queue(2)
queue_source.put(
pickle.dumps(
ia.Batch(images=np.zeros((1, 4, 8, 3), dtype=np.uint8)),
protocol=-1
)
)
queue_source.put(pickle.dumps(None, protocol=-1))
bgaug._augment_images_worker(iaa.Add(1), queue_source,
queue_target, 1)
batch_aug = pickle.loads(queue_target.get())
assert isinstance(batch_aug, ia.Batch)
assert batch_aug.images_unaug is not None
assert batch_aug.images_unaug.dtype == np.uint8
assert batch_aug.images_unaug.shape == (1, 4, 8, 3)
assert np.array_equal(
batch_aug.images_unaug,
np.zeros((1, 4, 8, 3), dtype=np.uint8))
assert batch_aug.images_aug is not None
assert batch_aug.images_aug.dtype == np.uint8
assert batch_aug.images_aug.shape == (1, 4, 8, 3)
assert np.array_equal(
batch_aug.images_aug,
np.zeros((1, 4, 8, 3), dtype=np.uint8) + 1)
finished_signal = pickle.loads(queue_target.get())
assert finished_signal is None
source_finished_signal = pickle.loads(queue_source.get())
assert source_finished_signal is None
assert queue_source.empty()
assert queue_target.empty()
queue_source.close()
queue_target.close()
queue_source.join_thread()
queue_target.join_thread()
bl.terminate()
bgaug.terminate()
assert len(caught_warnings) > 0
for warning in caught_warnings:
assert "is deprecated" in str(warning.message)
================================================
FILE: test/test_parameters.py
================================================
from __future__ import print_function, division, absolute_import
import itertools
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import matplotlib
matplotlib.use('Agg') # fix execution of tests involving matplotlib on travis
import numpy as np
import six.moves as sm
import skimage
import skimage.data
import skimage.morphology
import scipy
import scipy.special
import imgaug as ia
import imgaug.random as iarandom
from imgaug import parameters as iap
from imgaug.testutils import reseed, is_parameter_instance
def _eps(arr):
if ia.is_np_array(arr) and arr.dtype.kind == "f":
return np.finfo(arr.dtype).eps
return 1e-4
class Test_handle_continuous_param(unittest.TestCase):
def test_value_range_is_none(self):
result = iap.handle_continuous_param(
1, "[test1]",
value_range=None, tuple_to_uniform=True, list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_value_range_is_tuple_of_nones(self):
result = iap.handle_continuous_param(
1, "[test1b]",
value_range=(None, None),
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_param_is_stochastic_parameter(self):
result = iap.handle_continuous_param(
iap.Deterministic(1), "[test2]",
value_range=None, tuple_to_uniform=True, list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_value_range_is_tuple_of_integers(self):
result = iap.handle_continuous_param(
1, "[test3]",
value_range=(0, 10),
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_param_is_outside_of_value_range(self):
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
1, "[test4]",
value_range=(2, 12),
tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue("[test4]" in str(context.exception))
def test_param_is_inside_value_range_and_no_lower_bound(self):
# value within value range (without lower bound)
result = iap.handle_continuous_param(
1, "[test5]",
value_range=(None, 12),
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_param_is_outside_of_value_range_and_no_lower_bound(self):
# value outside of value range (without lower bound)
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
1, "[test6]",
value_range=(None, 0),
tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue("[test6]" in str(context.exception))
def test_param_is_inside_value_range_and_no_upper_bound(self):
# value within value range (without upper bound)
result = iap.handle_continuous_param(
1, "[test7]",
value_range=(-1, None),
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_param_is_outside_of_value_range_and_no_upper_bound(self):
# value outside of value range (without upper bound)
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
1, "[test8]",
value_range=(2, None),
tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue("[test8]" in str(context.exception))
def test_tuple_as_value_but_no_tuples_allowed(self):
# tuple as value, but no tuples allowed
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
(1, 2), "[test9]",
value_range=None,
tuple_to_uniform=False,
list_to_choice=True)
self.assertTrue("[test9]" in str(context.exception))
def test_tuple_as_value_and_tuples_allowed(self):
# tuple as value and tuple allowed
result = iap.handle_continuous_param(
(1, 2), "[test10]",
value_range=None,
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Uniform))
def test_tuple_as_value_and_tuples_allowed_and_inside_value_range(self):
# tuple as value and tuple allowed and tuple within value range
result = iap.handle_continuous_param(
(1, 2), "[test11]",
value_range=(0, 10),
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Uniform))
def test_tuple_value_and_allowed_and_partially_outside_value_range(self):
# tuple as value and tuple allowed and tuple partially outside of
# value range
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
(1, 2), "[test12]",
value_range=(1.5, 13),
tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue("[test12]" in str(context.exception))
def test_tuple_value_and_allowed_and_fully_outside_value_range(self):
# tuple as value and tuple allowed and tuple fully outside of value
# range
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
(1, 2), "[test13]",
value_range=(3, 13),
tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue("[test13]" in str(context.exception))
def test_list_as_value_but_no_lists_allowed(self):
# list as value, but no list allowed
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
[1, 2, 3], "[test14]",
value_range=None,
tuple_to_uniform=True,
list_to_choice=False)
self.assertTrue("[test14]" in str(context.exception))
def test_list_as_value_and_lists_allowed(self):
# list as value and list allowed
result = iap.handle_continuous_param(
[1, 2, 3], "[test15]",
value_range=None,
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Choice))
def test_list_value_and_allowed_and_partially_outside_value_range(self):
# list as value and list allowed and list partially outside of value
# range
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
[1, 2], "[test16]",
value_range=(1.5, 13),
tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue("[test16]" in str(context.exception))
def test_list_value_and_allowed_and_fully_outside_of_value_range(self):
# list as value and list allowed and list fully outside of value range
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
[1, 2], "[test17]",
value_range=(3, 13),
tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue("[test17]" in str(context.exception))
def test_value_inside_value_range_and_value_range_given_as_callable(self):
# single value within value range given as callable
def _value_range(x):
return -1 < x < 1
result = iap.handle_continuous_param(
1, "[test18]",
value_range=_value_range,
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_bad_datatype_as_value_range(self):
# bad datatype for value range
with self.assertRaises(Exception) as context:
_ = iap.handle_continuous_param(
1, "[test19]",
value_range=False,
tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue(
"Unexpected input for value_range" in str(context.exception))
class Test_handle_discrete_param(unittest.TestCase):
def test_float_value_inside_value_range_but_no_floats_allowed(self):
# float value without value range when no float value is allowed
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
1.5, "[test0]",
value_range=None,
tuple_to_uniform=True,
list_to_choice=True, allow_floats=False)
self.assertTrue("[test0]" in str(context.exception))
def test_value_range_is_none(self):
# value without value range
result = iap.handle_discrete_param(
1, "[test1]", value_range=None, tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_value_range_is_tuple_of_nones(self):
# value without value range as (None, None)
result = iap.handle_discrete_param(
1, "[test1b]", value_range=(None, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_value_is_stochastic_parameter(self):
# stochastic parameter
result = iap.handle_discrete_param(
iap.Deterministic(1), "[test2]", value_range=None,
tuple_to_uniform=True, list_to_choice=True, allow_floats=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_value_inside_value_range(self):
# value within value range
result = iap.handle_discrete_param(
1, "[test3]", value_range=(0, 10), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_value_outside_value_range(self):
# value outside of value range
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
1, "[test4]", value_range=(2, 12), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue("[test4]" in str(context.exception))
def test_value_inside_value_range_no_lower_bound(self):
# value within value range (without lower bound)
result = iap.handle_discrete_param(
1, "[test5]", value_range=(None, 12), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_value_outside_value_range_no_lower_bound(self):
# value outside of value range (without lower bound)
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
1, "[test6]", value_range=(None, 0), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue("[test6]" in str(context.exception))
def test_value_inside_value_range_no_upper_bound(self):
# value within value range (without upper bound)
result = iap.handle_discrete_param(
1, "[test7]", value_range=(-1, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_value_outside_value_range_no_upper_bound(self):
# value outside of value range (without upper bound)
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
1, "[test8]", value_range=(2, None), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True)
self.assertTrue("[test8]" in str(context.exception))
def test_value_is_tuple_but_no_tuples_allowed(self):
# tuple as value, but no tuples allowed
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
(1, 2), "[test9]", value_range=None, tuple_to_uniform=False,
list_to_choice=True, allow_floats=True)
self.assertTrue("[test9]" in str(context.exception))
def test_value_is_tuple_and_tuples_allowed(self):
# tuple as value and tuple allowed
result = iap.handle_discrete_param(
(1, 2), "[test10]", value_range=None, tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue(isinstance(result, iap.DiscreteUniform))
def test_value_tuple_and_allowed_and_inside_value_range(self):
# tuple as value and tuple allowed and tuple within value range
result = iap.handle_discrete_param(
(1, 2), "[test11]", value_range=(0, 10), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue(isinstance(result, iap.DiscreteUniform))
def test_value_tuple_and_allowed_and_inside_vr_allow_floats_false(self):
# tuple as value and tuple allowed and tuple within value range with
# allow_floats=False
result = iap.handle_discrete_param(
(1, 2), "[test11b]", value_range=(0, 10),
tuple_to_uniform=True, list_to_choice=True, allow_floats=False,
prefetch=False)
self.assertTrue(isinstance(result, iap.DiscreteUniform))
def test_value_tuple_and_allowed_and_partially_outside_value_range(self):
# tuple as value and tuple allowed and tuple partially outside of
# value range
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
(1, 3), "[test12]", value_range=(2, 13), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True)
self.assertTrue("[test12]" in str(context.exception))
def test_value_tuple_and_allowed_and_fully_outside_value_range(self):
# tuple as value and tuple allowed and tuple fully outside of value
# range
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
(1, 2), "[test13]", value_range=(3, 13), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True)
self.assertTrue("[test13]" in str(context.exception))
def test_value_list_but_not_allowed(self):
# list as value, but no list allowed
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
[1, 2, 3], "[test14]", value_range=None, tuple_to_uniform=True,
list_to_choice=False, allow_floats=True)
self.assertTrue("[test14]" in str(context.exception))
def test_value_list_and_allowed(self):
# list as value and list allowed
result = iap.handle_discrete_param(
[1, 2, 3], "[test15]", value_range=None, tuple_to_uniform=True,
list_to_choice=True, allow_floats=True, prefetch=False)
self.assertTrue(isinstance(result, iap.Choice))
def test_value_list_and_allowed_and_partially_outside_value_range(self):
# list as value and list allowed and list partially outside of value range
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
[1, 3], "[test16]", value_range=(2, 13), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True)
self.assertTrue("[test16]" in str(context.exception))
def test_value_list_and_allowed_and_fully_outside_value_range(self):
# list as value and list allowed and list fully outside of value range
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
[1, 2], "[test17]", value_range=(3, 13), tuple_to_uniform=True,
list_to_choice=True, allow_floats=True)
self.assertTrue("[test17]" in str(context.exception))
def test_value_inside_value_range_given_as_callable(self):
# single value within value range given as callable
def _value_range(x):
return -1 < x < 1
result = iap.handle_discrete_param(
1, "[test18]",
value_range=_value_range,
tuple_to_uniform=True,
list_to_choice=True,
prefetch=False)
self.assertTrue(isinstance(result, iap.Deterministic))
def test_bad_datatype_as_value_range(self):
# bad datatype for value range
with self.assertRaises(Exception) as context:
_ = iap.handle_discrete_param(
1, "[test19]", value_range=False, tuple_to_uniform=True,
list_to_choice=True)
self.assertTrue(
"Unexpected input for value_range" in str(context.exception))
class Test_handle_categorical_string_param(unittest.TestCase):
def test_arg_is_all(self):
valid_values = ["class1", "class2"]
param = iap.handle_categorical_string_param(
ia.ALL, "foo", valid_values)
assert is_parameter_instance(param, iap.Choice)
assert param.a == valid_values
def test_arg_is_str(self):
param = iap.handle_categorical_string_param("class1", "foo")
assert is_parameter_instance(param, iap.Deterministic)
assert param.value == "class1"
def test_arg_is_valid_str(self):
valid_values = ["class1", "class2"]
param = iap.handle_categorical_string_param(
"class1", "foo", valid_values)
assert is_parameter_instance(param, iap.Deterministic)
assert param.value == "class1"
def test_arg_is_invalid_str(self):
valid_values = ["class1", "class2"]
with self.assertRaises(AssertionError) as ctx:
_param = iap.handle_categorical_string_param(
"class3", "foo", valid_values)
expected = (
"Expected parameter 'foo' to be one of: class1, class2. "
"Got: class3.")
assert expected == str(ctx.exception)
def test_arg_is_list(self):
param = iap.handle_categorical_string_param(["class1", "class3"],
"foo")
assert is_parameter_instance(param, iap.Choice)
assert param.a == ["class1", "class3"]
def test_arg_is_valid_list(self):
valid_values = ["class1", "class2", "class3"]
param = iap.handle_categorical_string_param(
["class1", "class3"], "foo", valid_values)
assert is_parameter_instance(param, iap.Choice)
assert param.a == ["class1", "class3"]
def test_arg_is_list_with_invalid_types(self):
valid_values = ["class1", "class2", "class3"]
with self.assertRaises(AssertionError) as ctx:
_param = iap.handle_categorical_string_param(
["class1", False], "foo", valid_values)
expected = (
"Expected list provided for parameter 'foo' to only contain "
"strings, got types: str, bool."
)
assert expected in str(ctx.exception)
def test_arg_is_invalid_list(self):
valid_values = ["class1", "class2", "class3"]
with self.assertRaises(AssertionError) as ctx:
_param = iap.handle_categorical_string_param(
["class1", "class4"], "foo", valid_values)
expected = (
"Expected list provided for parameter 'foo' to only contain "
"the following allowed strings: class1, class2, class3. "
"Got strings: class1, class4."
)
assert expected in str(ctx.exception)
def test_arg_is_stochastic_param(self):
param = iap.Deterministic("class1")
param_out = iap.handle_categorical_string_param(
param, "foo", ["class1"], prefetch=False)
assert param_out is param
def test_arg_is_invalid_datatype(self):
with self.assertRaises(Exception) as ctx:
_ = iap.handle_categorical_string_param(
False, "foo", ["class1"])
expected = "Expected parameter 'foo' to be imgaug.ALL"
assert expected in str(ctx.exception)
class Test_handle_probability_param(unittest.TestCase):
def test_bool_like_values(self):
for val in [True, False, 0, 1, 0.0, 1.0]:
with self.subTest(param=val):
p = iap.handle_probability_param(val, "[test1]", prefetch=False)
assert isinstance(p, iap.Deterministic)
assert p.value == int(val)
def test_float_probabilities(self):
for val in [0.0001, 0.001, 0.01, 0.1, 0.9, 0.99, 0.999, 0.9999]:
with self.subTest(param=val):
p = iap.handle_probability_param(val, "[test2]", prefetch=False)
assert is_parameter_instance(p, iap.Binomial)
assert is_parameter_instance(p.p, iap.Deterministic)
assert val-1e-8 < p.p.value < val+1e-8
def test_probability_is_stochastic_parameter(self):
det = iap.Deterministic(1)
p = iap.handle_probability_param(det, "[test3]", prefetch=False)
assert p == det
def test_probability_has_bad_datatype(self):
with self.assertRaises(Exception) as context:
_p = iap.handle_probability_param("test", "[test4]")
self.assertTrue("Expected " in str(context.exception))
def test_probability_is_negative(self):
with self.assertRaises(AssertionError):
_p = iap.handle_probability_param(-0.01, "[test5]")
def test_probability_is_above_100_percent(self):
with self.assertRaises(AssertionError):
_p = iap.handle_probability_param(1.01, "[test6]")
class Test_force_np_float_dtype(unittest.TestCase):
def test_common_dtypes(self):
dtypes = [
("float16", "float16"),
("float32", "float32"),
("float64", "float64"),
("uint8", "float64"),
("int32", "float64")
]
for dtype_in, expected in dtypes:
with self.subTest(dtype_in=dtype_in):
arr = np.zeros((1,), dtype=dtype_in)
observed = iap.force_np_float_dtype(arr).dtype
assert observed.name == expected
class Test_both_np_float_if_one_is_float(unittest.TestCase):
def test_float16_float32(self):
a1 = np.zeros((1,), dtype=np.float16)
b1 = np.zeros((1,), dtype=np.float32)
a2, b2 = iap.both_np_float_if_one_is_float(a1, b1)
assert a2.dtype.name == "float16"
assert b2.dtype.name == "float32"
def test_float16_int32(self):
a1 = np.zeros((1,), dtype=np.float16)
b1 = np.zeros((1,), dtype=np.int32)
a2, b2 = iap.both_np_float_if_one_is_float(a1, b1)
assert a2.dtype.name == "float16"
assert b2.dtype.name == "float64"
def test_int32_float16(self):
a1 = np.zeros((1,), dtype=np.int32)
b1 = np.zeros((1,), dtype=np.float16)
a2, b2 = iap.both_np_float_if_one_is_float(a1, b1)
assert a2.dtype.name == "float64"
assert b2.dtype.name == "float16"
def test_int32_uint8(self):
a1 = np.zeros((1,), dtype=np.int32)
b1 = np.zeros((1,), dtype=np.uint8)
a2, b2 = iap.both_np_float_if_one_is_float(a1, b1)
assert a2.dtype.name == "float64"
assert b2.dtype.name == "float64"
class Test_draw_distributions_grid(unittest.TestCase):
def setUp(self):
reseed()
def test_basic_functionality(self):
params = [mock.Mock(), mock.Mock()]
params[0].draw_distribution_graph.return_value = \
np.zeros((1, 1, 3), dtype=np.uint8)
params[1].draw_distribution_graph.return_value = \
np.zeros((1, 1, 3), dtype=np.uint8)
draw_grid_mock = mock.Mock()
draw_grid_mock.return_value = np.zeros((4, 3, 2), dtype=np.uint8)
with mock.patch('imgaug.imgaug.draw_grid', draw_grid_mock):
grid_observed = iap.draw_distributions_grid(
params, rows=2, cols=3, graph_sizes=(20, 21),
sample_sizes=[(1, 2), (3, 4)], titles=["A", "B"])
assert grid_observed.shape == (4, 3, 2)
assert params[0].draw_distribution_graph.call_count == 1
assert params[1].draw_distribution_graph.call_count == 1
assert params[0].draw_distribution_graph.call_args[1]["size"] == (1, 2)
assert params[0].draw_distribution_graph.call_args[1]["title"] == "A"
assert params[1].draw_distribution_graph.call_args[1]["size"] == (3, 4)
assert params[1].draw_distribution_graph.call_args[1]["title"] == "B"
assert draw_grid_mock.call_count == 1
assert draw_grid_mock.call_args[0][0][0].shape == (20, 21, 3)
assert draw_grid_mock.call_args[0][0][1].shape == (20, 21, 3)
assert draw_grid_mock.call_args[1]["rows"] == 2
assert draw_grid_mock.call_args[1]["cols"] == 3
class Test_draw_distributions_graph(unittest.TestCase):
def test_basic_functionality(self):
# this test is very rough as we get a not-very-well-defined image out
# of the function
param = iap.Uniform(0.0, 1.0)
graph_img = param.draw_distribution_graph(title=None, size=(10000,),
bins=100)
# at least 10% of the image should be white-ish (background)
nb_white = np.sum(graph_img[..., :] > [200, 200, 200])
nb_all = np.prod(graph_img.shape)
graph_img_title = param.draw_distribution_graph(title="test",
size=(10000,),
bins=100)
assert graph_img.ndim == 3
assert graph_img.shape[2] == 3
assert nb_white > 0.1 * nb_all
assert graph_img_title.ndim == 3
assert graph_img_title.shape[2] == 3
assert not np.array_equal(graph_img_title, graph_img)
class TestStochasticParameter(unittest.TestCase):
def setUp(self):
reseed()
def test_copy(self):
other_param = iap.Uniform(1.0, 10.0)
param = iap.Discretize(other_param)
other_param.a = [1.0]
param_copy = param.copy()
param.other_param.a[0] += 1
assert isinstance(param_copy, iap.Discretize)
assert isinstance(param_copy.other_param, iap.Uniform)
assert param_copy.other_param.a[0] == param.other_param.a[0]
def test_deepcopy(self):
other_param = iap.Uniform(1.0, 10.0)
param = iap.Discretize(other_param)
other_param.a = [1.0]
param_copy = param.deepcopy()
param.other_param.a[0] += 1
assert isinstance(param_copy, iap.Discretize)
assert isinstance(param_copy.other_param, iap.Uniform)
assert param_copy.other_param.a[0] != param.other_param.a[0]
class TestStochasticParameterOperators(unittest.TestCase):
def setUp(self):
reseed()
def test_multiply_stochasic_params(self):
param1 = iap.Normal(0, 1)
param2 = iap.Uniform(-1.0, 1.0)
param3 = param1 * param2
assert isinstance(param3, iap.Multiply)
assert param3.other_param == param1
assert param3.val == param2
def test_multiply_stochastic_param_with_integer(self):
param1 = iap.Normal(0, 1)
param3 = param1 * 2
assert isinstance(param3, iap.Multiply)
assert param3.other_param == param1
assert isinstance(param3.val, iap.Deterministic)
assert param3.val.value == 2
def test_multiply_integer_with_stochastic_param(self):
param1 = iap.Normal(0, 1)
param3 = 2 * param1
assert isinstance(param3, iap.Multiply)
assert isinstance(param3.other_param, iap.Deterministic)
assert param3.other_param.value == 2
assert param3.val == param1
def test_multiply_string_with_stochastic_param_should_fail(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = "test" * param1
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_multiply_stochastic_param_with_string_should_fail(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = param1 * "test"
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_divide_stochastic_params(self):
# Divide (__truediv__)
param1 = iap.Normal(0, 1)
param2 = iap.Uniform(-1.0, 1.0)
param3 = param1 / param2
assert isinstance(param3, iap.Divide)
assert param3.other_param == param1
assert param3.val == param2
def test_divide_stochastic_param_by_integer(self):
param1 = iap.Normal(0, 1)
param3 = param1 / 2
assert isinstance(param3, iap.Divide)
assert param3.other_param == param1
assert isinstance(param3.val, iap.Deterministic)
assert param3.val.value == 2
def test_divide_integer_by_stochastic_param(self):
param1 = iap.Normal(0, 1)
param3 = 2 / param1
assert isinstance(param3, iap.Divide)
assert isinstance(param3.other_param, iap.Deterministic)
assert param3.other_param.value == 2
assert param3.val == param1
def test_divide_string_by_stochastic_param_should_fail(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = "test" / param1
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_divide_stochastic_param_by_string_should_fail(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = param1 / "test"
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_div_stochastic_params(self):
# Divide (__div__)
param1 = iap.Normal(0, 1)
param2 = iap.Uniform(-1.0, 1.0)
param3 = param1.__div__(param2)
assert isinstance(param3, iap.Divide)
assert param3.other_param == param1
assert param3.val == param2
def test_div_stochastic_param_by_integer(self):
param1 = iap.Normal(0, 1)
param3 = param1.__div__(2)
assert isinstance(param3, iap.Divide)
assert param3.other_param == param1
assert isinstance(param3.val, iap.Deterministic)
assert param3.val.value == 2
def test_div_stochastic_param_by_string_should_fail(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = param1.__div__("test")
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_rdiv_stochastic_param_by_integer(self):
# Divide (__rdiv__)
param1 = iap.Normal(0, 1)
param3 = param1.__rdiv__(2)
assert isinstance(param3, iap.Divide)
assert isinstance(param3.other_param, iap.Deterministic)
assert param3.other_param.value == 2
assert param3.val == param1
def test_rdiv_stochastic_param_by_string_should_fail(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = param1.__rdiv__("test")
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_floordiv_stochastic_params(self):
# Divide (__floordiv__)
param1_int = iap.DiscreteUniform(0, 10)
param2_int = iap.Choice([1, 2])
param3 = param1_int // param2_int
assert isinstance(param3, iap.Discretize)
assert isinstance(param3.other_param, iap.Divide)
assert param3.other_param.other_param == param1_int
assert param3.other_param.val == param2_int
def test_floordiv_symbol_stochastic_param_by_integer(self):
param1_int = iap.DiscreteUniform(0, 10)
param3 = param1_int // 2
assert isinstance(param3, iap.Discretize)
assert isinstance(param3.other_param, iap.Divide)
assert param3.other_param.other_param == param1_int
assert isinstance(param3.other_param.val, iap.Deterministic)
assert param3.other_param.val.value == 2
def test_floordiv_symbol_integer_by_stochastic_param(self):
param1_int = iap.DiscreteUniform(0, 10)
param3 = 2 // param1_int
assert isinstance(param3, iap.Discretize)
assert isinstance(param3.other_param, iap.Divide)
assert isinstance(param3.other_param.other_param, iap.Deterministic)
assert param3.other_param.other_param.value == 2
assert param3.other_param.val == param1_int
def test_floordiv_symbol_string_by_stochastic_should_fail(self):
param1_int = iap.DiscreteUniform(0, 10)
with self.assertRaises(Exception) as context:
_ = "test" // param1_int
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_floordiv_symbol_stochastic_param_by_string_should_fail(self):
param1_int = iap.DiscreteUniform(0, 10)
with self.assertRaises(Exception) as context:
_ = param1_int // "test"
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_add_stochastic_params(self):
param1 = iap.Normal(0, 1)
param2 = iap.Uniform(-1.0, 1.0)
param3 = param1 + param2
assert isinstance(param3, iap.Add)
assert param3.other_param == param1
assert param3.val == param2
def test_add_integer_to_stochastic_param(self):
param1 = iap.Normal(0, 1)
param3 = param1 + 2
assert isinstance(param3, iap.Add)
assert param3.other_param == param1
assert isinstance(param3.val, iap.Deterministic)
assert param3.val.value == 2
def test_add_stochastic_param_to_integer(self):
param1 = iap.Normal(0, 1)
param3 = 2 + param1
assert isinstance(param3, iap.Add)
assert isinstance(param3.other_param, iap.Deterministic)
assert param3.other_param.value == 2
assert param3.val == param1
def test_add_stochastic_param_to_string(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = "test" + param1
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_add_string_to_stochastic_param(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = param1 + "test"
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_subtract_stochastic_params(self):
param1 = iap.Normal(0, 1)
param2 = iap.Uniform(-1.0, 1.0)
param3 = param1 - param2
assert isinstance(param3, iap.Subtract)
assert param3.other_param == param1
assert param3.val == param2
def test_subtract_integer_from_stochastic_param(self):
param1 = iap.Normal(0, 1)
param3 = param1 - 2
assert isinstance(param3, iap.Subtract)
assert param3.other_param == param1
assert isinstance(param3.val, iap.Deterministic)
assert param3.val.value == 2
def test_subtract_stochastic_param_from_integer(self):
param1 = iap.Normal(0, 1)
param3 = 2 - param1
assert isinstance(param3, iap.Subtract)
assert isinstance(param3.other_param, iap.Deterministic)
assert param3.other_param.value == 2
assert param3.val == param1
def test_subtract_stochastic_param_from_string_should_fail(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = "test" - param1
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_subtract_string_from_stochastic_param_should_fail(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = param1 - "test"
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_exponentiate_stochastic_params(self):
param1 = iap.Normal(0, 1)
param2 = iap.Uniform(-1.0, 1.0)
param3 = param1 ** param2
assert isinstance(param3, iap.Power)
assert param3.other_param == param1
assert param3.val == param2
def test_exponentiate_stochastic_param_by_integer(self):
param1 = iap.Normal(0, 1)
param3 = param1 ** 2
assert isinstance(param3, iap.Power)
assert param3.other_param == param1
assert isinstance(param3.val, iap.Deterministic)
assert param3.val.value == 2
def test_exponentiate_integer_by_stochastic_param(self):
param1 = iap.Normal(0, 1)
param3 = 2 ** param1
assert isinstance(param3, iap.Power)
assert isinstance(param3.other_param, iap.Deterministic)
assert param3.other_param.value == 2
assert param3.val == param1
def test_exponentiate_string_by_stochastic_param(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = "test" ** param1
self.assertTrue("Invalid datatypes" in str(context.exception))
def test_exponentiate_stochastic_param_by_string(self):
param1 = iap.Normal(0, 1)
with self.assertRaises(Exception) as context:
_ = param1 ** "test"
self.assertTrue("Invalid datatypes" in str(context.exception))
class TestAutoPrefetcher(unittest.TestCase):
def setUp(self):
reseed()
def test_does_not_prefetch_at_first_call(self):
other_param = mock.Mock()
other_param.draw_samples.return_value = np.zeros((100,), dtype=np.uint8)
param = iap.AutoPrefetcher(other_param, 10)
rng = iarandom.RNG(0)
_samples = param.draw_samples((1,), rng)
# rng is currently not identical in call args,
# because draw_samples() creates a new one with same state
assert other_param.draw_samples.call_count == 1
assert other_param.draw_samples.call_args_list[0][0][0] == (1,)
assert other_param.draw_samples.call_args_list[0][0][1].equals(rng)
assert param.samples is None
assert param.index == 0
assert param.last_rng_idx == rng._idx
def test_prefetches_at_second_call(self):
other_param = mock.Mock()
other_param.draw_samples.return_value = np.zeros((100,), dtype=np.uint8)
param = iap.AutoPrefetcher(other_param, 10)
rng = iarandom.RNG(0)
_samples = param.draw_samples((1,), rng)
_samples = param.draw_samples((1,), rng)
# rng is currently not identical in call args,
# because draw_samples() creates a new one with same state
assert other_param.draw_samples.call_count == 2
assert other_param.draw_samples.call_args_list[0][0][0] == (1,)
assert other_param.draw_samples.call_args_list[0][0][1].equals(rng)
assert other_param.draw_samples.call_args_list[1][0][0] == (10,)
assert other_param.draw_samples.call_args_list[1][0][1].equals(rng)
# (100,) because that's what the mock always returns
assert param.samples.shape == (100,)
assert param.index == 1
assert param.last_rng_idx == rng._idx
def test_nb_prefetch_is_evenly_divisible_by_requested_sizes(self):
other_param = iap.DeterministicList(np.arange(200))
param = iap.AutoPrefetcher(other_param, 100)
rng = iarandom.RNG(0)
samples1 = param.draw_samples((50,), rng)
samples2 = param.draw_samples((50,), rng)
samples3 = param.draw_samples((50,), rng)
samples4 = param.draw_samples((50,), rng)
# first call is not prefetched, second+ is, so first and second are
# here identical
assert np.array_equal(samples1, np.arange(50))
assert np.array_equal(samples2, np.arange(50))
assert np.array_equal(samples3, 50 + np.arange(50))
assert np.array_equal(samples4, np.arange(50))
def test_nb_prefetch_is_not_evenly_divisible_by_requested_sizes(self):
other_param = iap.DeterministicList(np.arange(200))
param = iap.AutoPrefetcher(other_param, 100)
rng = iarandom.RNG(0)
samples1 = param.draw_samples((40,), rng)
samples2 = param.draw_samples((40,), rng)
samples3 = param.draw_samples((40,), rng)
samples4 = param.draw_samples((40,), rng)
# first call is not prefetched, second+ is, so first and second are
# here identical
assert np.array_equal(samples1, np.arange(40))
assert np.array_equal(samples2, np.arange(40))
assert np.array_equal(samples3, 40 + np.arange(40))
assert np.array_equal(
samples4,
np.concatenate([
80 + np.arange(20),
np.arange(20)
], axis=0)
)
def test_exactly_as_many_components_requested_as_nb_prefetch_allows(self):
other_param = iap.DeterministicList(np.arange(200))
param = iap.AutoPrefetcher(other_param, 40)
rng = iarandom.RNG(0)
samples1 = param.draw_samples((40,), rng)
samples2 = param.draw_samples((40,), rng)
samples3 = param.draw_samples((40,), rng)
assert np.array_equal(samples1, np.arange(40))
assert np.array_equal(samples2, np.arange(40))
assert np.array_equal(samples3, np.arange(40))
def test_more_components_requested_than_nb_prefetch_allows(self):
other_param = iap.DeterministicList(np.arange(200))
param = iap.AutoPrefetcher(other_param, 10)
rng = iarandom.RNG(0)
samples1 = param.draw_samples((40,), rng)
samples2 = param.draw_samples((40,), rng)
samples3 = param.draw_samples((40,), rng)
assert np.array_equal(samples1, np.arange(40))
assert np.array_equal(samples2, np.arange(40))
assert np.array_equal(samples3, np.arange(40))
def test_size_is_tuple(self):
other_param = iap.DeterministicList(np.arange(200))
param = iap.AutoPrefetcher(other_param, 50)
rng = iarandom.RNG(0)
samples1 = param.draw_samples((2, 3, 4), rng) # 24 samples
samples2 = param.draw_samples((1, 5, 2), rng) # 10 samples
samples3 = param.draw_samples((10, 2), rng) # 20 samples
assert np.array_equal(samples1, np.arange(2*3*4).reshape((2, 3, 4)))
assert np.array_equal(samples2, np.arange(1*5*2).reshape((1, 5, 2)))
assert np.array_equal(samples3,
(1*5*2) + np.arange(10*2).reshape((10, 2)))
def test_to_string_first_call(self):
other_param = iap.DeterministicList(np.arange(200))
param = iap.AutoPrefetcher(other_param, 10)
other_param_str = str(other_param)
expected = (
"AutoPrefetcher("
"nb_prefetch=10, "
"samples=None (dtype None), "
"index=0, "
"last_rng_idx=None, "
"other_param=%s"
")" % (other_param_str,)
)
assert str(param) == repr(param) == expected
def test_to_string_second_call(self):
# use astype(int64) here, because otherwise in windows the array
# seems to become int32, causing the assertion below to fail
other_param = iap.DeterministicList(np.arange(200).astype(np.int64))
param = iap.AutoPrefetcher(other_param, 10)
other_param_str = str(other_param)
rng = iarandom.RNG(0)
_ = param.draw_samples((2,), rng)
_ = param.draw_samples((2,), rng)
expected = (
"AutoPrefetcher("
"nb_prefetch=10, "
"samples=(10,) (dtype int64), "
"index=2, "
"last_rng_idx=%d, "
"other_param=%s"
")" % (rng._idx, other_param_str)
)
assert str(param) == repr(param) == expected
class TestBinomial(unittest.TestCase):
def setUp(self):
reseed()
def test___init___p_is_zero(self):
param = iap.Binomial(0)
expected = "Binomial(%s)" % (str(param.p),)
assert "Deterministic(int 0)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test___init___p_is_one(self):
param = iap.Binomial(1.0)
expected = "Binomial(%s)" % (str(param.p),)
assert "Deterministic(float 1.00000000)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_p_is_zero(self):
param = iap.Binomial(0)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample == 0
assert np.all(samples == 0)
def test_p_is_one(self):
param = iap.Binomial(1.0)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample == 1
assert np.all(samples == 1)
def test_p_is_50_percent(self):
param = iap.Binomial(0.5)
sample = param.draw_sample()
samples = param.draw_samples((10000,))
unique, counts = np.unique(samples, return_counts=True)
assert sample.shape == tuple()
assert samples.shape == (10000,)
assert sample in [0, 1]
assert len(unique) == 2
for val, count in zip(unique, counts):
if val == 0:
assert 5000 - 500 < count < 5000 + 500
elif val == 1:
assert 5000 - 500 < count < 5000 + 500
else:
assert False
def test_p_is_list(self):
param = iap.Binomial(iap.Choice([0.25, 0.75]))
for _ in sm.xrange(10):
samples = param.draw_samples((1000,))
p = np.sum(samples) / samples.size
assert (
(0.25 - 0.05 < p < 0.25 + 0.05)
or (0.75 - 0.05 < p < 0.75 + 0.05)
)
def test_p_is_tuple(self):
param = iap.Binomial((0.0, 1.0))
last_p = 0.5
diffs = []
for _ in sm.xrange(30):
samples = param.draw_samples((1000,))
p = np.sum(samples).astype(np.float32) / samples.size
diffs.append(abs(p - last_p))
last_p = p
nb_p_changed = sum([diff > 0.05 for diff in diffs])
assert nb_p_changed > 15
def test_samples_same_values_for_same_seeds(self):
param = iap.Binomial(0.5)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.array_equal(samples1, samples2)
class TestChoice(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Choice([0, 1, 2])
assert (
param.__str__()
== param.__repr__()
== "Choice(a=[0, 1, 2], replace=True, p=None)"
)
def test_value_is_list(self):
param = iap.Choice([0, 1, 2])
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in [0, 1, 2]
assert np.all(
np.logical_or(
np.logical_or(samples == 0, samples == 1),
samples == 2
)
)
def test_sampled_values_match_expected_counts(self):
param = iap.Choice([0, 1, 2])
samples = param.draw_samples((10000,))
expected = 10000/3
expected_tolerance = expected * 0.05
for v in [0, 1, 2]:
count = np.sum(samples == v)
assert (
expected - expected_tolerance
< count <
expected + expected_tolerance
)
def test_value_is_list_containing_negative_number(self):
param = iap.Choice([-1, 1])
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in [-1, 1]
assert np.all(np.logical_or(samples == -1, samples == 1))
def test_value_is_list_of_floats(self):
param = iap.Choice([-1.2, 1.7])
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert (
(
-1.2 - _eps(sample)
< sample <
-1.2 + _eps(sample)
)
or
(
1.7 - _eps(sample)
< sample <
1.7 + _eps(sample)
)
)
assert np.all(
np.logical_or(
np.logical_and(
-1.2 - _eps(sample) < samples,
samples < -1.2 + _eps(sample)
),
np.logical_and(
1.7 - _eps(sample) < samples,
samples < 1.7 + _eps(sample)
)
)
)
def test_value_is_list_of_strings(self):
param = iap.Choice(["first", "second", "third"])
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in ["first", "second", "third"]
assert np.all(
np.logical_or(
np.logical_or(
samples == "first",
samples == "second"
),
samples == "third"
)
)
def test_sample_without_replacing(self):
param = iap.Choice([1+i for i in sm.xrange(100)], replace=False)
samples = param.draw_samples((50,))
seen = [0 for _ in sm.xrange(100)]
for sample in samples:
seen[sample-1] += 1
assert all([count in [0, 1] for count in seen])
def test_non_uniform_probabilities_over_elements(self):
param = iap.Choice([0, 1], p=[0.25, 0.75])
samples = param.draw_samples((10000,))
unique, counts = np.unique(samples, return_counts=True)
assert len(unique) == 2
for val, count in zip(unique, counts):
if val == 0:
assert 2500 - 500 < count < 2500 + 500
elif val == 1:
assert 7500 - 500 < count < 7500 + 500
else:
assert False
def test_list_contains_stochastic_parameter(self):
param = iap.Choice([iap.Choice([0, 1]), 2])
samples = param.draw_samples((10000,))
unique, counts = np.unique(samples, return_counts=True)
assert len(unique) == 3
for val, count in zip(unique, counts):
if val in [0, 1]:
assert 2500 - 500 < count < 2500 + 500
elif val == 2:
assert 5000 - 500 < count < 5000 + 500
else:
assert False
def test_samples_same_values_for_same_seeds(self):
param = iap.Choice([-1, 0, 1, 2, 3])
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.array_equal(samples1, samples2)
def test_value_is_bad_datatype(self):
with self.assertRaises(Exception) as context:
_ = iap.Choice(123)
self.assertTrue(
"Expected a to be an iterable" in str(context.exception))
def test_p_is_bad_datatype(self):
with self.assertRaises(Exception) as context:
_ = iap.Choice([1, 2], p=123)
self.assertTrue("Expected p to be" in str(context.exception))
def test_value_and_p_have_unequal_lengths(self):
with self.assertRaises(Exception) as context:
_ = iap.Choice([1, 2], p=[1])
self.assertTrue("Expected lengths of" in str(context.exception))
class TestDiscreteUniform(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.DiscreteUniform(0, 2)
expected = "DiscreteUniform(%s, %s)" % (str(param.a), str(param.b))
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int 2)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_bounds_are_ints(self):
param = iap.DiscreteUniform(0, 2)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in [0, 1, 2]
assert np.all(
np.logical_or(
np.logical_or(samples == 0, samples == 1),
samples == 2
)
)
def test_samples_match_expected_counts(self):
param = iap.DiscreteUniform(0, 2)
samples = param.draw_samples((10000,))
expected = 10000/3
expected_tolerance = expected * 0.05
for v in [0, 1, 2]:
count = np.sum(samples == v)
assert (
expected - expected_tolerance
< count <
expected + expected_tolerance
)
def test_lower_bound_is_negative(self):
param = iap.DiscreteUniform(-1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in [-1, 0, 1]
assert np.all(
np.logical_or(
np.logical_or(samples == -1, samples == 0),
samples == 1
)
)
def test_bounds_are_floats(self):
param = iap.DiscreteUniform(-1.2, 1.2)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in [-1, 0, 1]
assert np.all(
np.logical_or(
np.logical_or(
samples == -1, samples == 0
),
samples == 1
)
)
def test_lower_and_upper_bound_have_wrong_order(self):
param = iap.DiscreteUniform(1, -1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in [-1, 0, 1]
assert np.all(
np.logical_or(
np.logical_or(
samples == -1, samples == 0
),
samples == 1
)
)
def test_lower_and_upper_bound_are_the_same(self):
param = iap.DiscreteUniform(1, 1)
sample = param.draw_sample()
samples = param.draw_samples((100,))
assert sample == 1
assert np.all(samples == 1)
def test_samples_same_values_for_same_seeds(self):
param = iap.Uniform(-1, 1)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.array_equal(samples1, samples2)
class TestPoisson(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Poisson(1)
expected = "Poisson(%s)" % (str(param.lam),)
assert "Deterministic(int 1)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_draw_sample(self):
param = iap.Poisson(1)
sample = param.draw_sample()
assert sample.shape == tuple()
assert 0 <= sample
def test_via_comparison_to_np_poisson(self):
param = iap.Poisson(1)
samples = param.draw_samples((100, 1000))
samples_direct = iarandom.RNG(1234).poisson(
lam=1, size=(100, 1000))
assert samples.shape == (100, 1000)
for i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]:
count_direct = int(np.sum(samples_direct == i))
count = np.sum(samples == i)
tolerance = max(count_direct * 0.1, 250)
assert count_direct - tolerance < count < count_direct + tolerance
def test_samples_same_values_for_same_seeds(self):
param = iap.Poisson(1)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.array_equal(samples1, samples2)
class TestNormal(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Normal(0, 1)
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int 1)" in str(param)
assert (
param.__str__()
== param.__repr__()
== "Normal(loc=%s, scale=%s)" % (str(param.loc), str(param.scale))
)
def test_draw_sample(self):
param = iap.Normal(0, 1)
sample = param.draw_sample()
assert sample.shape == tuple()
def test_via_comparison_to_np_normal(self):
param = iap.Normal(0, 1)
samples = param.draw_samples((100, 1000))
samples_direct = iarandom.RNG(1234).normal(loc=0, scale=1,
size=(100, 1000))
samples = np.clip(samples, -1, 1)
samples_direct = np.clip(samples_direct, -1, 1)
nb_bins = 10
hist, _ = np.histogram(samples, bins=nb_bins, range=(-1.0, 1.0),
density=False)
hist_direct, _ = np.histogram(samples_direct, bins=nb_bins,
range=(-1.0, 1.0), density=False)
tolerance = 0.05
for nb_samples, nb_samples_direct in zip(hist, hist_direct):
density = nb_samples / samples.size
density_direct = nb_samples_direct / samples_direct.size
assert (
density_direct - tolerance
< density <
density_direct + tolerance
)
def test_loc_is_stochastic_parameter(self):
param = iap.Normal(iap.Choice([-100, 100]), 1)
seen = [0, 0]
for _ in sm.xrange(1000):
samples = param.draw_samples((100,))
exp = np.mean(samples)
if -100 - 10 < exp < -100 + 10:
seen[0] += 1
elif 100 - 10 < exp < 100 + 10:
seen[1] += 1
else:
assert False
assert 500 - 100 < seen[0] < 500 + 100
assert 500 - 100 < seen[1] < 500 + 100
def test_scale(self):
param1 = iap.Normal(0, 1)
param2 = iap.Normal(0, 100)
samples1 = param1.draw_samples((1000,))
samples2 = param2.draw_samples((1000,))
assert np.std(samples1) < np.std(samples2)
assert 100 - 10 < np.std(samples2) < 100 + 10
def test_samples_same_values_for_same_seeds(self):
param = iap.Normal(0, 1)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.allclose(samples1, samples2)
class TestTruncatedNormal(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.TruncatedNormal(0, 1)
expected = (
"TruncatedNormal("
"loc=%s, "
"scale=%s, "
"low=%s, "
"high=%s"
")" % (
str(param.loc),
str(param.scale),
str(param.low),
str(param.high)
)
)
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int 1)" in str(param)
assert "Deterministic(float -inf)" in str(param)
assert "Deterministic(float inf)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test___init___custom_range(self):
param = iap.TruncatedNormal(0, 1, low=-100, high=50.0)
expected = (
"TruncatedNormal("
"loc=%s, "
"scale=%s, "
"low=%s, "
"high=%s"
")" % (
str(param.loc),
str(param.scale),
str(param.low),
str(param.high)
)
)
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int 1)" in str(param)
assert "Deterministic(int -100)" in str(param)
assert "Deterministic(float 50.00000000)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_scale_is_zero(self):
param = iap.TruncatedNormal(0.5, 0, low=-10, high=10)
samples = param.draw_samples((100,))
assert np.allclose(samples, 0.5)
def test_scale(self):
param1 = iap.TruncatedNormal(0.0, 0.1, low=-100, high=100)
param2 = iap.TruncatedNormal(0.0, 5.0, low=-100, high=100)
samples1 = param1.draw_samples((1000,))
samples2 = param2.draw_samples((1000,))
assert np.std(samples1) < np.std(samples2)
assert np.isclose(np.std(samples1), 0.1, rtol=0, atol=0.20)
assert np.isclose(np.std(samples2), 5.0, rtol=0, atol=0.40)
def test_loc_is_stochastic_parameter(self):
param = iap.TruncatedNormal(iap.Choice([-100, 100]), 0.01,
low=-1000, high=1000)
seen = [0, 0]
for _ in sm.xrange(200):
samples = param.draw_samples((5,))
observed = np.mean(samples)
dist1 = np.abs(-100 - observed)
dist2 = np.abs(100 - observed)
if dist1 < 1:
seen[0] += 1
elif dist2 < 1:
seen[1] += 1
else:
assert False
assert np.isclose(seen[0], 100, rtol=0, atol=20)
assert np.isclose(seen[1], 100, rtol=0, atol=20)
def test_samples_are_within_bounds(self):
param = iap.TruncatedNormal(0, 10.0, low=-5, high=7.5)
samples = param.draw_samples((1000,))
# are all within bounds
assert np.all(samples >= -5.0 - 1e-4)
assert np.all(samples <= 7.5 + 1e-4)
# at least some samples close to bounds
assert np.any(samples <= -4.5)
assert np.any(samples >= 7.0)
# at least some samples close to loc
assert np.any(np.abs(samples) < 0.5)
def test_samples_same_values_for_same_seeds(self):
param = iap.TruncatedNormal(0, 1)
samples1 = param.draw_samples((10, 5), random_state=1234)
samples2 = param.draw_samples((10, 5), random_state=1234)
assert np.allclose(samples1, samples2)
def test_samples_different_values_for_different_seeds(self):
param = iap.TruncatedNormal(0, 1)
samples1 = param.draw_samples((10, 5), random_state=1234)
samples2 = param.draw_samples((10, 5), random_state=2345)
assert not np.allclose(samples1, samples2)
class TestLaplace(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Laplace(0, 1)
expected = "Laplace(loc=%s, scale=%s)" % (
str(param.loc),
str(param.scale)
)
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int 1)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_draw_sample(self):
param = iap.Laplace(0, 1)
sample = param.draw_sample()
assert sample.shape == tuple()
def test_via_comparison_to_np_laplace(self):
param = iap.Laplace(0, 1)
samples = param.draw_samples((100, 1000))
samples_direct = iarandom.RNG(1234).laplace(loc=0, scale=1,
size=(100, 1000))
assert samples.shape == (100, 1000)
samples = np.clip(samples, -1, 1)
samples_direct = np.clip(samples_direct, -1, 1)
nb_bins = 10
hist, _ = np.histogram(samples, bins=nb_bins, range=(-1.0, 1.0),
density=False)
hist_direct, _ = np.histogram(samples_direct, bins=nb_bins,
range=(-1.0, 1.0), density=False)
tolerance = 0.05
for nb_samples, nb_samples_direct in zip(hist, hist_direct):
density = nb_samples / samples.size
density_direct = nb_samples_direct / samples_direct.size
assert (
density_direct - tolerance
< density <
density_direct + tolerance
)
def test_loc_is_stochastic_parameter(self):
param = iap.Laplace(iap.Choice([-100, 100]), 1)
seen = [0, 0]
for _ in sm.xrange(1000):
samples = param.draw_samples((100,))
exp = np.mean(samples)
if -100 - 10 < exp < -100 + 10:
seen[0] += 1
elif 100 - 10 < exp < 100 + 10:
seen[1] += 1
else:
assert False
assert 500 - 100 < seen[0] < 500 + 100
assert 500 - 100 < seen[1] < 500 + 100
def test_scale(self):
param1 = iap.Laplace(0, 1)
param2 = iap.Laplace(0, 100)
samples1 = param1.draw_samples((1000,))
samples2 = param2.draw_samples((1000,))
assert np.var(samples1) < np.var(samples2)
def test_scale_is_zero(self):
param1 = iap.Laplace(1, 0)
samples = param1.draw_samples((100,))
assert np.all(np.logical_and(
samples > 1 - _eps(samples),
samples < 1 + _eps(samples)
))
def test_samples_same_values_for_same_seeds(self):
param = iap.Laplace(0, 1)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.allclose(samples1, samples2)
class TestChiSquare(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.ChiSquare(1)
expected = "ChiSquare(df=%s)" % (str(param.df),)
assert "Deterministic(int 1)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_draw_sample(self):
param = iap.ChiSquare(1)
sample = param.draw_sample()
assert sample.shape == tuple()
assert 0 <= sample
def test_via_comparison_to_np_chisquare(self):
param = iap.ChiSquare(1)
samples = param.draw_samples((100, 1000))
samples_direct = iarandom.RNG(1234).chisquare(df=1,
size=(100, 1000))
assert samples.shape == (100, 1000)
assert np.all(0 <= samples)
samples = np.clip(samples, 0, 3)
samples_direct = np.clip(samples_direct, 0, 3)
nb_bins = 10
hist, _ = np.histogram(samples, bins=nb_bins, range=(0, 3.0),
density=False)
hist_direct, _ = np.histogram(samples_direct, bins=nb_bins,
range=(0, 3.0), density=False)
tolerance = 0.05
for nb_samples, nb_samples_direct in zip(hist, hist_direct):
density = nb_samples / samples.size
density_direct = nb_samples_direct / samples_direct.size
assert (
density_direct - tolerance
< density <
density_direct + tolerance
)
def test_df_is_stochastic_parameter(self):
param = iap.ChiSquare(iap.Choice([1, 10]))
seen = [0, 0]
for _ in sm.xrange(1000):
samples = param.draw_samples((100,))
exp = np.mean(samples)
if 1 - 1.0 < exp < 1 + 1.0:
seen[0] += 1
elif 10 - 4.0 < exp < 10 + 4.0:
seen[1] += 1
else:
assert False
assert 500 - 100 < seen[0] < 500 + 100
assert 500 - 100 < seen[1] < 500 + 100
def test_larger_df_leads_to_more_variance(self):
param1 = iap.ChiSquare(1)
param2 = iap.ChiSquare(10)
samples1 = param1.draw_samples((1000,))
samples2 = param2.draw_samples((1000,))
assert np.var(samples1) < np.var(samples2)
assert 2*1 - 1.0 < np.var(samples1) < 2*1 + 1.0
assert 2*10 - 5.0 < np.var(samples2) < 2*10 + 5.0
def test_samples_same_values_for_same_seeds(self):
param = iap.ChiSquare(1)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.allclose(samples1, samples2)
class TestWeibull(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Weibull(1)
expected = "Weibull(a=%s)" % (str(param.a),)
assert "Deterministic(int 1)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_draw_sample(self):
param = iap.Weibull(1)
sample = param.draw_sample()
assert sample.shape == tuple()
assert 0 <= sample
def test_via_comparison_to_np_weibull(self):
param = iap.Weibull(1)
samples = param.draw_samples((100, 1000))
samples_direct = iarandom.RNG(1234).weibull(a=1, size=(100, 1000))
assert samples.shape == (100, 1000)
assert np.all(0 <= samples)
samples = np.clip(samples, 0, 2)
samples_direct = np.clip(samples_direct, 0, 2)
nb_bins = 10
hist, _ = np.histogram(samples, bins=nb_bins, range=(0, 2.0),
density=False)
hist_direct, _ = np.histogram(samples_direct, bins=nb_bins,
range=(0, 2.0), density=False)
tolerance = 0.05
for nb_samples, nb_samples_direct in zip(hist, hist_direct):
density = nb_samples / samples.size
density_direct = nb_samples_direct / samples_direct.size
assert (
density_direct - tolerance
< density <
density_direct + tolerance
)
def test_argument_is_stochastic_parameter(self):
param = iap.Weibull(iap.Choice([1, 0.5]))
expected_first = scipy.special.gamma(1 + 1/1)
expected_second = scipy.special.gamma(1 + 1/0.5)
seen = [0, 0]
for _ in sm.xrange(100):
samples = param.draw_samples((50000,))
observed = np.mean(samples)
matches_first = (
expected_first - 0.2 * expected_first
< observed <
expected_first + 0.2 * expected_first
)
matches_second = (
expected_second - 0.2 * expected_second
< observed <
expected_second + 0.2 * expected_second
)
if matches_first:
seen[0] += 1
elif matches_second:
seen[1] += 1
else:
assert False
assert 50 - 25 < seen[0] < 50 + 25
assert 50 - 25 < seen[1] < 50 + 25
def test_different_strengths(self):
param1 = iap.Weibull(1)
param2 = iap.Weibull(0.5)
samples1 = param1.draw_samples((10000,))
samples2 = param2.draw_samples((10000,))
expected_first = (
scipy.special.gamma(1 + 2/1)
- (scipy.special.gamma(1 + 1/1))**2
)
expected_second = (
scipy.special.gamma(1 + 2/0.5)
- (scipy.special.gamma(1 + 1/0.5))**2
)
assert np.var(samples1) < np.var(samples2)
assert (
expected_first - 0.2 * expected_first
< np.var(samples1) <
expected_first + 0.2 * expected_first
)
assert (
expected_second - 0.2 * expected_second
< np.var(samples2) <
expected_second + 0.2 * expected_second
)
def test_samples_same_values_for_same_seeds(self):
param = iap.Weibull(1)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.allclose(samples1, samples2)
class TestUniform(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Uniform(0, 1.0)
expected = "Uniform(%s, %s)" % (str(param.a), str(param.b))
assert "Deterministic(int 0)" in str(param.a)
assert "Deterministic(float 1.00000000)" in str(param.b)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_draw_sample(self):
param = iap.Uniform(0, 1.0)
sample = param.draw_sample()
assert sample.shape == tuple()
assert 0 - _eps(sample) < sample < 1.0 + _eps(sample)
def test_draw_samples(self):
param = iap.Uniform(0, 1.0)
samples = param.draw_samples((10, 5))
assert samples.shape == (10, 5)
assert np.all(
np.logical_and(
0 - _eps(samples) < samples,
samples < 1.0 + _eps(samples)
)
)
def test_via_density_histogram(self):
param = iap.Uniform(0, 1.0)
samples = param.draw_samples((10000,))
nb_bins = 10
hist, _ = np.histogram(samples, bins=nb_bins, range=(0.0, 1.0),
density=False)
density_expected = 1.0/nb_bins
density_tolerance = 0.05
for nb_samples in hist:
density = nb_samples / samples.size
assert (
density_expected - density_tolerance
< density <
density_expected + density_tolerance
)
def test_negative_value(self):
param = iap.Uniform(-1.0, 1.0)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert -1.0 - _eps(sample) < sample < 1.0 + _eps(sample)
assert np.all(
np.logical_and(
-1.0 - _eps(samples) < samples,
samples < 1.0 + _eps(samples)
)
)
def test_wrong_argument_order(self):
param = iap.Uniform(1.0, -1.0)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert -1.0 - _eps(sample) < sample < 1.0 + _eps(sample)
assert np.all(
np.logical_and(
-1.0 - _eps(samples) < samples,
samples < 1.0 + _eps(samples)
)
)
def test_arguments_are_integers(self):
param = iap.Uniform(-1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert -1.0 - _eps(sample) < sample < 1.0 + _eps(sample)
assert np.all(
np.logical_and(
-1.0 - _eps(samples) < samples,
samples < 1.0 + _eps(samples)
)
)
def test_arguments_are_identical(self):
param = iap.Uniform(1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert 1.0 - _eps(sample) < sample < 1.0 + _eps(sample)
assert np.all(
np.logical_and(
1.0 - _eps(samples) < samples,
samples < 1.0 + _eps(samples)
)
)
def test_samples_same_values_for_same_seeds(self):
param = iap.Uniform(-1.0, 1.0)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.allclose(samples1, samples2)
class TestBeta(unittest.TestCase):
@classmethod
def _mean(cls, alpha, beta):
return alpha / (alpha + beta)
@classmethod
def _var(cls, alpha, beta):
return (alpha * beta) / ((alpha + beta)**2 * (alpha + beta + 1))
def setUp(self):
reseed()
def test___init__(self):
param = iap.Beta(0.5, 0.5)
expected = "Beta(%s, %s)" % (str(param.alpha), str(param.beta))
assert "Deterministic(float 0.50000000)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_draw_sample(self):
param = iap.Beta(0.5, 0.5)
sample = param.draw_sample()
assert sample.shape == tuple()
assert 0 - _eps(sample) < sample < 1.0 + _eps(sample)
def test_draw_samples(self):
param = iap.Beta(0.5, 0.5)
samples = param.draw_samples((100, 1000))
assert samples.shape == (100, 1000)
assert np.all(
np.logical_and(
0 - _eps(samples) <= samples,
samples <= 1.0 + _eps(samples)
)
)
def test_via_comparison_to_np_beta(self):
param = iap.Beta(0.5, 0.5)
samples = param.draw_samples((100, 1000))
samples_direct = iarandom.RNG(1234).beta(
a=0.5, b=0.5, size=(100, 1000))
nb_bins = 10
hist, _ = np.histogram(samples, bins=nb_bins, range=(0, 1.0),
density=False)
hist_direct, _ = np.histogram(samples_direct, bins=nb_bins,
range=(0, 1.0), density=False)
tolerance = 0.05
for nb_samples, nb_samples_direct in zip(hist, hist_direct):
density = nb_samples / samples.size
density_direct = nb_samples_direct / samples_direct.size
assert (
density_direct - tolerance
< density <
density_direct + tolerance
)
def test_argument_is_stochastic_parameter(self):
param = iap.Beta(iap.Choice([0.5, 2]), 0.5)
expected_first = self._mean(0.5, 0.5)
expected_second = self._mean(2, 0.5)
seen = [0, 0]
for _ in sm.xrange(100):
samples = param.draw_samples((10000,))
observed = np.mean(samples)
if expected_first - 0.05 < observed < expected_first + 0.05:
seen[0] += 1
elif expected_second - 0.05 < observed < expected_second + 0.05:
seen[1] += 1
else:
assert False
assert 50 - 25 < seen[0] < 50 + 25
assert 50 - 25 < seen[1] < 50 + 25
def test_compare_curves_of_different_arguments(self):
param1 = iap.Beta(2, 2)
param2 = iap.Beta(0.5, 0.5)
samples1 = param1.draw_samples((10000,))
samples2 = param2.draw_samples((10000,))
expected_first = self._var(2, 2)
expected_second = self._var(0.5, 0.5)
assert np.var(samples1) < np.var(samples2)
assert (
expected_first - 0.1 * expected_first
< np.var(samples1) <
expected_first + 0.1 * expected_first
)
assert (
expected_second - 0.1 * expected_second
< np.var(samples2) <
expected_second + 0.1 * expected_second
)
def test_samples_same_values_for_same_seeds(self):
param = iap.Beta(0.5, 0.5)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.allclose(samples1, samples2)
class TestDeterministic(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
pairs = [
(0, "Deterministic(int 0)"),
(1.0, "Deterministic(float 1.00000000)"),
("test", "Deterministic(test)")
]
for value, expected in pairs:
with self.subTest(value=value):
param = iap.Deterministic(value)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_samples_same_values_for_same_seeds(self):
values = [
-100, -54, -1, 0, 1, 54, 100,
-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0
]
for value in values:
with self.subTest(value=value):
param = iap.Deterministic(value)
rs1 = iarandom.RNG(123456)
rs2 = iarandom.RNG(123456)
samples1 = param.draw_samples(20, random_state=rs1)
samples2 = param.draw_samples(20, random_state=rs2)
assert np.array_equal(samples1, samples2)
def test_draw_sample_int(self):
values = [-100, -54, -1, 0, 1, 54, 100]
for value in values:
with self.subTest(value=value):
param = iap.Deterministic(value)
sample1 = param.draw_sample()
sample2 = param.draw_sample()
assert sample1.shape == tuple()
assert sample1 == sample2
def test_draw_sample_float(self):
values = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for value in values:
with self.subTest(value=value):
param = iap.Deterministic(value)
sample1 = param.draw_sample()
sample2 = param.draw_sample()
assert sample1.shape == tuple()
assert np.isclose(
sample1, sample2, rtol=0, atol=_eps(sample1))
def test_draw_samples_int(self):
values = [-100, -54, -1, 0, 1, 54, 100]
shapes = [10, 10, (5, 3), (5, 3), (4, 5, 3), (4, 5, 3)]
for value, shape in itertools.product(values, shapes):
with self.subTest(value=value, shape=shape):
param = iap.Deterministic(value)
samples = param.draw_samples(shape)
shape_expected = (
shape
if isinstance(shape, tuple)
else tuple([shape]))
assert samples.shape == shape_expected
assert np.all(samples == value)
def test_draw_samples_float(self):
values = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
shapes = [10, 10, (5, 3), (5, 3), (4, 5, 3), (4, 5, 3)]
for value, shape in itertools.product(values, shapes):
with self.subTest(value=value, shape=shape):
param = iap.Deterministic(value)
samples = param.draw_samples(shape)
shape_expected = (
shape
if isinstance(shape, tuple)
else tuple([shape]))
assert samples.shape == shape_expected
assert np.allclose(samples, value, rtol=0, atol=_eps(samples))
def test_argument_is_stochastic_parameter(self):
seen = [0, 0]
for _ in sm.xrange(200):
param = iap.Deterministic(iap.Choice([0, 1]))
seen[param.value] += 1
assert 100 - 50 < seen[0] < 100 + 50
assert 100 - 50 < seen[1] < 100 + 50
def test_argument_has_invalid_type(self):
with self.assertRaises(Exception) as context:
_ = iap.Deterministic([1, 2, 3])
self.assertTrue(
"Expected StochasticParameter object or number or string"
in str(context.exception))
class TestDeterministicList(unittest.TestCase):
def setUp(self):
reseed()
def test___init___with_array(self):
values = np.arange(1*2*3).reshape((1, 2, 3))
param = iap.DeterministicList(values)
assert np.array_equal(param.values, values.flatten())
def test___init___with_list_int(self):
values = [[1, 2], [3, 4]]
param = iap.DeterministicList(values)
assert np.array_equal(param.values, [1, 2, 3, 4])
assert param.values.dtype.name == "int32"
def test___init___with_list_float(self):
values = [[1.1, 2.2], [3.3, 4.4]]
param = iap.DeterministicList(values)
assert np.allclose(param.values, [1.1, 2.2, 3.3, 4.4])
assert param.values.dtype.name == "float32"
def test_samples_same_values_for_same_seeds(self):
values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110]
param = iap.DeterministicList(values)
rs1 = iarandom.RNG(123456)
rs2 = iarandom.RNG(123456)
samples1 = param.draw_samples(10, random_state=rs1)
samples2 = param.draw_samples(10, random_state=rs2)
assert np.array_equal(samples1, samples2)
def test_draw_sample_int(self):
values = [10, 20, 30, 40, 50]
param = iap.DeterministicList(values)
sample1 = param.draw_sample()
sample2 = param.draw_sample()
assert sample1.shape == tuple()
assert sample1 == sample2
def test_draw_sample_float(self):
values = [10.1, 20.2, 30.3, 40.4, 50.5]
param = iap.DeterministicList(values)
sample1 = param.draw_sample()
sample2 = param.draw_sample()
assert sample1.shape == tuple()
assert np.isclose(
sample1, sample2, rtol=0, atol=_eps(sample1))
def test_draw_samples_int(self):
values = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
shapes = [3, (2, 3), (2, 3, 1)]
expecteds = [
[10, 20, 30],
[[10, 20, 30], [40, 50, 60]],
[[[10], [20], [30]], [[40], [50], [60]]]
]
param = iap.DeterministicList(values)
for shape, expected in zip(shapes, expecteds):
with self.subTest(shape=shape):
samples = param.draw_samples(shape)
shape_expected = (
shape
if isinstance(shape, tuple)
else tuple([shape]))
assert samples.shape == shape_expected
assert np.array_equal(samples, expected)
def test_draw_samples_float(self):
values = [10.1, 20.2, 30.3, 40.4, 50.5, 60.6, 70.7, 80.8, 90.9, 100.10]
shapes = [3, (2, 3), (2, 3, 1)]
expecteds = [
[10.1, 20.2, 30.3],
[[10.1, 20.2, 30.3], [40.4, 50.5, 60.6]],
[[[10.1], [20.2], [30.3]], [[40.4], [50.5], [60.6]]]
]
param = iap.DeterministicList(values)
for shape, expected in zip(shapes, expecteds):
with self.subTest(shape=shape):
samples = param.draw_samples(shape)
shape_expected = (
shape
if isinstance(shape, tuple)
else tuple([shape]))
assert samples.shape == shape_expected
assert np.allclose(samples, expected, rtol=0, atol=1e-5)
def test_draw_samples_cycles_when_shape_too_large(self):
values = [10, 20, 30]
param = iap.DeterministicList(values)
shapes = [(6,), (7,), (8,), (9,), (3, 3)]
expecteds = [
[10, 20, 30, 10, 20, 30],
[10, 20, 30, 10, 20, 30, 10],
[10, 20, 30, 10, 20, 30, 10, 20],
[10, 20, 30, 10, 20, 30, 10, 20, 30],
[[10, 20, 30],
[10, 20, 30],
[10, 20, 30]]
]
for shape, expected in zip(shapes, expecteds):
with self.subTest(shape=shape):
samples = param.draw_samples(shape)
assert np.array_equal(samples, expected)
def test___str___and___repr___float(self):
param = iap.DeterministicList([10.1, 20.2, 30.3])
assert (
param.__str__()
== param.__repr__()
== "DeterministicList([10.1000, 20.2000, 30.3000])"
)
def test___str___and___repr___intlike(self):
param = iap.DeterministicList([10, 20, 30])
assert (
param.__str__()
== param.__repr__()
== "DeterministicList([10, 20, 30])"
)
class TestFromLowerResolution(unittest.TestCase):
def setUp(self):
reseed()
def test___init___size_percent(self):
param = iap.FromLowerResolution(other_param=iap.Deterministic(0),
size_percent=1, method="nearest")
expected = (
"FromLowerResolution(size_percent=%s, method=%s, other_param=%s)"
) % (
str(param.size_percent),
str(param.method),
str(param.other_param)
)
assert "Deterministic(int 1)" in str(param)
assert "Deterministic(nearest)" in str(param)
assert "Deterministic(int 0)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test___init___size_px(self):
param = iap.FromLowerResolution(other_param=iap.Deterministic(0),
size_px=1, method="nearest")
expected = (
"FromLowerResolution(size_px=%s, method=%s, other_param=%s)"
) % (
str(param.size_px),
str(param.method),
str(param.other_param)
)
assert "Deterministic(int 1)" in str(param)
assert "Deterministic(nearest)" in str(param)
assert "Deterministic(int 0)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_binomial_hwc(self):
param = iap.FromLowerResolution(iap.Binomial(0.5), size_px=8)
samples = param.draw_samples((8, 8, 1))
uq = np.unique(samples)
assert samples.shape == (8, 8, 1)
assert len(uq) == 2
assert 0 in uq
assert 1 in uq
def test_binomial_nhwc(self):
param = iap.FromLowerResolution(iap.Binomial(0.5), size_px=8)
samples_nhwc = param.draw_samples((1, 8, 8, 1))
uq = np.unique(samples_nhwc)
assert samples_nhwc.shape == (1, 8, 8, 1)
assert len(uq) == 2
assert 0 in uq
assert 1 in uq
def test_draw_samples_with_too_many_dimensions(self):
# (N, H, W, C, something) causing error
param = iap.FromLowerResolution(iap.Binomial(0.5), size_px=8)
with self.assertRaises(Exception) as context:
_ = param.draw_samples((1, 8, 8, 1, 1))
self.assertTrue(
"FromLowerResolution can only generate samples of shape"
in str(context.exception)
)
def test_binomial_hw3(self):
# C=3
param = iap.FromLowerResolution(iap.Binomial(0.5), size_px=8)
samples = param.draw_samples((8, 8, 3))
uq = np.unique(samples)
assert samples.shape == (8, 8, 3)
assert len(uq) == 2
assert 0 in uq
assert 1 in uq
def test_different_size_px_arguments(self):
# different sizes in px
param1 = iap.FromLowerResolution(iap.Binomial(0.5), size_px=2)
param2 = iap.FromLowerResolution(iap.Binomial(0.5), size_px=16)
seen_components = [0, 0]
seen_pixels = [0, 0]
for _ in sm.xrange(100):
samples1 = param1.draw_samples((16, 16, 1))
samples2 = param2.draw_samples((16, 16, 1))
_, num1 = skimage.morphology.label(samples1, connectivity=1,
background=0, return_num=True)
_, num2 = skimage.morphology.label(samples2, connectivity=1,
background=0, return_num=True)
seen_components[0] += num1
seen_components[1] += num2
seen_pixels[0] += np.sum(samples1 == 1)
seen_pixels[1] += np.sum(samples2 == 1)
assert seen_components[0] < seen_components[1]
assert (
seen_pixels[0] / seen_components[0]
> seen_pixels[1] / seen_components[1]
)
def test_different_size_px_arguments_with_tuple(self):
# different sizes in px, one given as tuple (a, b)
param1 = iap.FromLowerResolution(iap.Binomial(0.5), size_px=2)
param2 = iap.FromLowerResolution(iap.Binomial(0.5), size_px=(2, 16))
seen_components = [0, 0]
seen_pixels = [0, 0]
for _ in sm.xrange(400):
samples1 = param1.draw_samples((16, 16, 1))
samples2 = param2.draw_samples((16, 16, 1))
_, num1 = skimage.morphology.label(samples1, connectivity=1,
background=0, return_num=True)
_, num2 = skimage.morphology.label(samples2, connectivity=1,
background=0, return_num=True)
seen_components[0] += num1
seen_components[1] += num2
seen_pixels[0] += np.sum(samples1 == 1)
seen_pixels[1] += np.sum(samples2 == 1)
assert seen_components[0] < seen_components[1]
assert (
seen_pixels[0] / seen_components[0]
> seen_pixels[1] / seen_components[1]
)
def test_different_size_px_argument_with_stochastic_parameters(self):
# different sizes in px, given as StochasticParameter
param1 = iap.FromLowerResolution(iap.Binomial(0.5),
size_px=iap.Deterministic(1))
param2 = iap.FromLowerResolution(iap.Binomial(0.5),
size_px=iap.Choice([8, 16]))
seen_components = [0, 0]
seen_pixels = [0, 0]
for _ in sm.xrange(100):
samples1 = param1.draw_samples((16, 16, 1))
samples2 = param2.draw_samples((16, 16, 1))
_, num1 = skimage.morphology.label(samples1, connectivity=1,
background=0, return_num=True)
_, num2 = skimage.morphology.label(samples2, connectivity=1,
background=0, return_num=True)
seen_components[0] += num1
seen_components[1] += num2
seen_pixels[0] += np.sum(samples1 == 1)
seen_pixels[1] += np.sum(samples2 == 1)
assert seen_components[0] < seen_components[1]
assert (
seen_pixels[0] / seen_components[0]
> seen_pixels[1] / seen_components[1]
)
def test_size_px_has_invalid_datatype(self):
# bad datatype for size_px
with self.assertRaises(Exception) as context:
_ = iap.FromLowerResolution(iap.Binomial(0.5), size_px=False)
self.assertTrue("Expected " in str(context.exception))
def test_min_size(self):
# min_size
param1 = iap.FromLowerResolution(iap.Binomial(0.5), size_px=2)
param2 = iap.FromLowerResolution(iap.Binomial(0.5), size_px=1,
min_size=16)
seen_components = [0, 0]
seen_pixels = [0, 0]
for _ in sm.xrange(100):
samples1 = param1.draw_samples((16, 16, 1))
samples2 = param2.draw_samples((16, 16, 1))
_, num1 = skimage.morphology.label(samples1, connectivity=1,
background=0, return_num=True)
_, num2 = skimage.morphology.label(samples2, connectivity=1,
background=0, return_num=True)
seen_components[0] += num1
seen_components[1] += num2
seen_pixels[0] += np.sum(samples1 == 1)
seen_pixels[1] += np.sum(samples2 == 1)
assert seen_components[0] < seen_components[1]
assert (
seen_pixels[0] / seen_components[0]
> seen_pixels[1] / seen_components[1]
)
def test_size_percent(self):
# different sizes in percent
param1 = iap.FromLowerResolution(iap.Binomial(0.5), size_percent=0.01)
param2 = iap.FromLowerResolution(iap.Binomial(0.5), size_percent=0.8)
seen_components = [0, 0]
seen_pixels = [0, 0]
for _ in sm.xrange(100):
samples1 = param1.draw_samples((16, 16, 1))
samples2 = param2.draw_samples((16, 16, 1))
_, num1 = skimage.morphology.label(samples1, connectivity=1,
background=0, return_num=True)
_, num2 = skimage.morphology.label(samples2, connectivity=1,
background=0, return_num=True)
seen_components[0] += num1
seen_components[1] += num2
seen_pixels[0] += np.sum(samples1 == 1)
seen_pixels[1] += np.sum(samples2 == 1)
assert seen_components[0] < seen_components[1]
assert (
seen_pixels[0] / seen_components[0]
> seen_pixels[1] / seen_components[1]
)
def test_size_percent_as_stochastic_parameters(self):
# different sizes in percent, given as StochasticParameter
param1 = iap.FromLowerResolution(iap.Binomial(0.5),
size_percent=iap.Deterministic(0.01))
param2 = iap.FromLowerResolution(iap.Binomial(0.5),
size_percent=iap.Choice([0.4, 0.8]))
seen_components = [0, 0]
seen_pixels = [0, 0]
for _ in sm.xrange(100):
samples1 = param1.draw_samples((16, 16, 1))
samples2 = param2.draw_samples((16, 16, 1))
_, num1 = skimage.morphology.label(samples1, connectivity=1,
background=0, return_num=True)
_, num2 = skimage.morphology.label(samples2, connectivity=1,
background=0, return_num=True)
seen_components[0] += num1
seen_components[1] += num2
seen_pixels[0] += np.sum(samples1 == 1)
seen_pixels[1] += np.sum(samples2 == 1)
assert seen_components[0] < seen_components[1]
assert (
seen_pixels[0] / seen_components[0]
> seen_pixels[1] / seen_components[1]
)
def test_size_percent_has_invalid_datatype(self):
# bad datatype for size_percent
with self.assertRaises(Exception) as context:
_ = iap.FromLowerResolution(iap.Binomial(0.5), size_percent=False)
self.assertTrue("Expected " in str(context.exception))
def test_method(self):
# method given as StochasticParameter
param = iap.FromLowerResolution(
iap.Binomial(0.5), size_px=4,
method=iap.Choice(["nearest", "linear"]))
seen = [0, 0]
for _ in sm.xrange(200):
samples = param.draw_samples((16, 16, 1))
nb_in_between = np.sum(
np.logical_and(0.05 < samples, samples < 0.95))
if nb_in_between == 0:
seen[0] += 1
else:
seen[1] += 1
assert 100 - 50 < seen[0] < 100 + 50
assert 100 - 50 < seen[1] < 100 + 50
def test_method_has_invalid_datatype(self):
# bad datatype for method
with self.assertRaises(Exception) as context:
_ = iap.FromLowerResolution(iap.Binomial(0.5), size_px=4,
method=False)
self.assertTrue("Expected " in str(context.exception))
def test_samples_same_values_for_same_seeds(self):
# multiple calls with same random_state
param = iap.FromLowerResolution(iap.Binomial(0.5), size_px=2)
samples1 = param.draw_samples((10, 5, 1),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5, 1),
random_state=iarandom.RNG(1234))
assert np.allclose(samples1, samples2)
class TestClip(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Clip(iap.Deterministic(0), -1, 1)
expected = "Clip(%s, -1.000000, 1.000000)" % (str(param.other_param),)
assert "Deterministic(int 0)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_value_within_bounds(self):
param = iap.Clip(iap.Deterministic(0), -1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample == 0
assert np.all(samples == 0)
def test_value_exactly_at_upper_bound(self):
param = iap.Clip(iap.Deterministic(1), -1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample == 1
assert np.all(samples == 1)
def test_value_exactly_at_lower_bound(self):
param = iap.Clip(iap.Deterministic(-1), -1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample == -1
assert np.all(samples == -1)
def test_value_is_within_bounds_and_float(self):
param = iap.Clip(iap.Deterministic(0.5), -1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert 0.5 - _eps(sample) < sample < 0.5 + _eps(sample)
assert np.all(
np.logical_and(
0.5 - _eps(sample) <= samples,
samples <= 0.5 + _eps(sample)
)
)
def test_value_is_above_upper_bound(self):
param = iap.Clip(iap.Deterministic(2), -1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample == 1
assert np.all(samples == 1)
def test_value_is_below_lower_bound(self):
param = iap.Clip(iap.Deterministic(-2), -1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample == -1
assert np.all(samples == -1)
def test_value_is_sometimes_without_bounds_sometimes_beyond(self):
param = iap.Clip(iap.Choice([0, 2]), -1, 1)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in [0, 1]
assert np.all(np.logical_or(samples == 0, samples == 1))
def test_samples_same_values_for_same_seeds(self):
param = iap.Clip(iap.Choice([0, 2]), -1, 1)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.array_equal(samples1, samples2)
def test_lower_bound_is_none(self):
param = iap.Clip(iap.Deterministic(0), None, 1)
sample = param.draw_sample()
expected = "Clip(%s, None, 1.000000)" % (str(param.other_param),)
assert sample == 0
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_upper_bound_is_none(self):
param = iap.Clip(iap.Deterministic(0), 0, None)
sample = param.draw_sample()
expected = "Clip(%s, 0.000000, None)" % (str(param.other_param),)
assert sample == 0
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_both_bounds_are_none(self):
param = iap.Clip(iap.Deterministic(0), None, None)
sample = param.draw_sample()
expected = "Clip(%s, None, None)" % (str(param.other_param),)
assert sample == 0
assert (
param.__str__()
== param.__repr__()
== expected
)
class TestDiscretize(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Discretize(iap.Deterministic(0))
expected = "Discretize(%s, round=True)" % (param.other_param,)
assert "Deterministic(int 0)" in str(param.other_param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_applied_to_deterministic(self):
values = [-100.2, -54.3, -1.0, -1, -0.7, -0.00043,
0,
0.00043, 0.7, 1.0, 1, 54.3, 100.2]
for value in values:
with self.subTest(value=value):
param = iap.Discretize(iap.Deterministic(value))
value_expected = np.round(
np.float64([value])
).astype(np.int32)[0]
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample == value_expected
assert np.all(samples == value_expected)
# TODO why are these tests applied to DiscreteUniform instead of Uniform?
def test_applied_to_discrete_uniform(self):
param_orig = iap.DiscreteUniform(0, 1)
param = iap.Discretize(param_orig)
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
assert sample in [0, 1]
assert np.all(np.logical_or(samples == 0, samples == 1))
def test_applied_to_discrete_uniform_with_wider_range(self):
param_orig = iap.DiscreteUniform(0, 2)
param = iap.Discretize(param_orig)
samples1 = param_orig.draw_samples((10000,))
samples2 = param.draw_samples((10000,))
assert np.all(np.abs(samples1 - samples2) < 0.2*(10000/3))
def test_round(self):
param_orig = iap.Uniform(0, 1.99)
param_round = iap.Discretize(param_orig)
param_no_round = iap.Discretize(param_orig, round=False)
samples_round = param_round.draw_samples((10000,))
samples_no_round = param_no_round.draw_samples((10000,))
uq_round = np.unique(samples_round)
uq_no_round = np.unique(samples_no_round)
assert np.all([v in uq_round for v in [0, 1, 2]])
assert np.all([v in uq_no_round for v in [0, 1]])
def test_samples_same_values_for_same_seeds(self):
param_orig = iap.DiscreteUniform(0, 2)
param = iap.Discretize(param_orig)
samples1 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((10, 5),
random_state=iarandom.RNG(1234))
assert np.array_equal(samples1, samples2)
class TestMultiply(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Multiply(iap.Deterministic(0), 1, elementwise=False)
expected = "Multiply(%s, %s, False)" % (
str(param.other_param),
str(param.val)
)
assert "Deterministic(int 0)" in str(param.other_param)
assert "Deterministic(int 1)" in str(param.val)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_multiply_example_integer_values(self):
values_int = [-100, -54, -1, 0, 1, 54, 100]
for v1, v2 in itertools.product(values_int, values_int):
with self.subTest(left=v1, right=v2):
p = iap.Multiply(iap.Deterministic(v1), v2)
samples = p.draw_samples((2, 3))
assert p.draw_sample() == v1 * v2
assert samples.dtype.kind == "i"
assert np.array_equal(
samples,
np.zeros((2, 3), dtype=np.int64) + v1 * v2
)
def test_multiply_example_integer_values_both_deterministic(self):
values_int = [-100, -54, -1, 0, 1, 54, 100]
for v1, v2 in itertools.product(values_int, values_int):
with self.subTest(left=v1, right=v2):
p = iap.Multiply(iap.Deterministic(v1), iap.Deterministic(v2))
samples = p.draw_samples((2, 3))
assert p.draw_sample() == v1 * v2
assert samples.dtype.name == "int32"
assert np.array_equal(
samples,
np.zeros((2, 3), dtype=np.int32) + v1 * v2
)
def test_multiply_example_float_values(self):
values_float = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for v1, v2 in itertools.product(values_float, values_float):
with self.subTest(left=v1, right=v2):
p = iap.Multiply(iap.Deterministic(v1), v2)
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert np.isclose(sample, v1 * v2, atol=1e-3, rtol=0)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float32) + v1 * v2
)
def test_multiply_example_float_values_both_deterministic(self):
values_float = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for v1, v2 in itertools.product(values_float, values_float):
with self.subTest(left=v1, right=v2):
p = iap.Multiply(iap.Deterministic(v1), iap.Deterministic(v2))
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert np.isclose(sample, v1 * v2, atol=1e-3, rtol=0)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float32) + v1 * v2
)
def test_multiply_by_stochastic_parameter(self):
param = iap.Multiply(iap.Deterministic(1.0),
(1.0, 2.0),
elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 * 1.0 - _eps(samples))
assert np.all(samples < 1.0 * 2.0 + _eps(samples))
assert (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_multiply_by_stochastic_parameter_elementwise(self):
param = iap.Multiply(iap.Deterministic(1.0),
(1.0, 2.0),
elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 * 1.0 - _eps(samples))
assert np.all(samples < 1.0 * 2.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_multiply_stochastic_parameter_by_fixed_value(self):
param = iap.Multiply(iap.Uniform(1.0, 2.0),
1.0,
elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 * 1.0 - _eps(samples))
assert np.all(samples < 2.0 * 1.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_multiply_stochastic_parameter_by_fixed_value_elementwise(self):
param = iap.Multiply(iap.Uniform(1.0, 2.0), 1.0, elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 * 1.0 - _eps(samples))
assert np.all(samples < 2.0 * 1.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
class TestDivide(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Divide(iap.Deterministic(0), 1, elementwise=False)
expected = "Divide(%s, %s, False)" % (
str(param.other_param), str(param.val)
)
assert "Deterministic(int 0)" in str(param.other_param)
assert "Deterministic(int 1)" in str(param.val)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_divide_integers(self):
values_int = [-100, -54, -1, 0, 1, 54, 100]
for v1, v2 in itertools.product(values_int, values_int):
if v2 == 0:
v2 = 1
with self.subTest(left=v1, right=v2):
p = iap.Divide(iap.Deterministic(v1), v2)
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert sample == (v1 / v2)
assert samples.dtype.kind == "f"
assert np.array_equal(
samples,
np.zeros((2, 3), dtype=np.float64) + (v1 / v2)
)
def test_divide_integers_both_deterministic(self):
values_int = [-100, -54, -1, 0, 1, 54, 100]
for v1, v2 in itertools.product(values_int, values_int):
if v2 == 0:
v2 = 1
with self.subTest(left=v1, right=v2):
p = iap.Divide(iap.Deterministic(v1), iap.Deterministic(v2))
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert sample == (v1 / v2)
assert samples.dtype.kind == "f"
assert np.array_equal(
samples,
np.zeros((2, 3), dtype=np.float64) + (v1 / v2)
)
def test_divide_floats(self):
values_float = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for v1, v2 in itertools.product(values_float, values_float):
if v2 == 0:
v2 = 1
with self.subTest(left=v1, right=v2):
p = iap.Divide(iap.Deterministic(v1), v2)
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert (
(v1 / v2) - _eps(sample)
<= sample <=
(v1 / v2) + _eps(sample)
)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float64) + (v1 / v2)
)
def test_divide_floats_both_deterministic(self):
values_float = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for v1, v2 in itertools.product(values_float, values_float):
if v2 == 0:
v2 = 1
with self.subTest(left=v1, right=v2):
p = iap.Divide(iap.Deterministic(v1), iap.Deterministic(v2))
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert (
(v1 / v2) - _eps(sample)
<= sample <=
(v1 / v2) + _eps(sample)
)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float64) + (v1 / v2)
)
def test_divide_by_stochastic_parameter(self):
param = iap.Divide(iap.Deterministic(1.0),
(1.0, 2.0),
elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > (1.0 / 2.0) - _eps(samples))
assert np.all(samples < (1.0 / 1.0) + _eps(samples))
assert (
samples_sorted[0] - _eps(samples)
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples)
)
def test_divide_by_stochastic_parameter_elementwise(self):
param = iap.Divide(iap.Deterministic(1.0),
(1.0, 2.0),
elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > (1.0 / 2.0) - _eps(samples))
assert np.all(samples < (1.0 / 1.0) + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples)
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples)
)
def test_divide_stochastic_parameter_by_float(self):
param = iap.Divide(iap.Uniform(1.0, 2.0),
1.0,
elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > (1.0 / 1.0) - _eps(samples))
assert np.all(samples < (2.0 / 1.0) + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples)
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples)
)
def test_divide_stochastic_parameter_by_float_elementwise(self):
param = iap.Divide(iap.Uniform(1.0, 2.0),
1.0,
elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > (1.0 / 1.0) - _eps(samples))
assert np.all(samples < (2.0 / 1.0) + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted)
< samples_sorted[-1]
< samples_sorted[-1]
< samples_sorted[0] + _eps(samples_sorted)
)
def test_divide_by_stochastic_parameter_that_can_by_zero(self):
# test division by zero automatically being converted to division by 1
param = iap.Divide(2,
iap.Choice([0, 2]),
elementwise=True)
samples = param.draw_samples((10, 20))
samples_unique = np.sort(np.unique(samples.flatten()))
assert samples_unique[0] == 1 and samples_unique[1] == 2
def test_divide_by_zero(self):
param = iap.Divide(iap.Deterministic(1), 0, elementwise=False)
sample = param.draw_sample()
assert sample == 1
class TestAdd(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Add(iap.Deterministic(0), 1, elementwise=False)
expected = "Add(%s, %s, False)" % (
str(param.other_param),
str(param.val)
)
assert "Deterministic(int 0)" in str(param.other_param)
assert "Deterministic(int 1)" in str(param.val)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_add_integers(self):
values_int = [-100, -54, -1, 0, 1, 54, 100]
for v1, v2 in itertools.product(values_int, values_int):
with self.subTest(left=v1, right=v2):
p = iap.Add(iap.Deterministic(v1), v2)
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert sample == v1 + v2
assert samples.dtype.kind == "i"
assert np.array_equal(
samples,
np.zeros((2, 3), dtype=np.int32) + v1 + v2
)
def test_add_integers_both_deterministic(self):
values_int = [-100, -54, -1, 0, 1, 54, 100]
for v1, v2 in itertools.product(values_int, values_int):
with self.subTest(left=v1, right=v2):
p = iap.Add(iap.Deterministic(v1), iap.Deterministic(v2))
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert sample == v1 + v2
assert samples.dtype.kind == "i"
assert np.array_equal(
samples,
np.zeros((2, 3), dtype=np.int32) + v1 + v2
)
def test_add_floats(self):
values_float = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for v1, v2 in itertools.product(values_float, values_float):
with self.subTest(left=v1, right=v2):
p = iap.Add(iap.Deterministic(v1), v2)
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert np.isclose(sample, v1 + v2, atol=1e-3, rtol=0)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float32) + v1 + v2
)
def test_add_floats_both_deterministic(self):
values_float = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for v1, v2 in itertools.product(values_float, values_float):
with self.subTest(left=v1, right=v2):
p = iap.Add(iap.Deterministic(v1), iap.Deterministic(v2))
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert np.isclose(sample, v1 + v2, atol=1e-3, rtol=0)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float32) + v1 + v2
)
def test_add_stochastic_parameter(self):
param = iap.Add(iap.Deterministic(1.0), (1.0, 2.0), elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples >= 1.0 + 1.0 - _eps(samples))
assert np.all(samples <= 1.0 + 2.0 + _eps(samples))
assert (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1]
< samples_sorted[0] + _eps(samples_sorted[0])
)
def test_add_stochastic_parameter_elementwise(self):
param = iap.Add(iap.Deterministic(1.0), (1.0, 2.0), elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples >= 1.0 + 1.0 - _eps(samples))
assert np.all(samples <= 1.0 + 2.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1]
< samples_sorted[0] + _eps(samples_sorted[0])
)
def test_add_to_stochastic_parameter(self):
param = iap.Add(iap.Uniform(1.0, 2.0), 1.0, elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples >= 1.0 + 1.0 - _eps(samples))
assert np.all(samples <= 2.0 + 1.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1]
< samples_sorted[0] + _eps(samples_sorted[0])
)
def test_add_to_stochastic_parameter_elementwise(self):
param = iap.Add(iap.Uniform(1.0, 2.0), 1.0, elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples >= 1.0 + 1.0 - _eps(samples))
assert np.all(samples <= 2.0 + 1.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1]
< samples_sorted[0] + _eps(samples_sorted[0])
)
class TestSubtract(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Subtract(iap.Deterministic(0), 1, elementwise=False)
expected = "Subtract(%s, %s, False)" % (
str(param.other_param),
str(param.val)
)
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int 1)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_subtract_integers(self):
values_int = [-100, -54, -1, 0, 1, 54, 100]
for v1, v2 in itertools.product(values_int, values_int):
with self.subTest(left=v1, right=v2):
p = iap.Subtract(iap.Deterministic(v1), v2)
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert sample == v1 - v2
assert samples.dtype.kind == "i"
assert np.array_equal(
samples,
np.zeros((2, 3), dtype=np.int64) + v1 - v2
)
def test_subtract_integers_both_deterministic(self):
values_int = [-100, -54, -1, 0, 1, 54, 100]
for v1, v2 in itertools.product(values_int, values_int):
with self.subTest(left=v1, right=v2):
p = iap.Subtract(iap.Deterministic(v1), iap.Deterministic(v2))
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert sample == v1 - v2
assert samples.dtype.kind == "i"
assert np.array_equal(
samples,
np.zeros((2, 3), dtype=np.int64) + v1 - v2
)
def test_subtract_floats(self):
values_float = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for v1, v2 in itertools.product(values_float, values_float):
with self.subTest(left=v1, right=v2):
p = iap.Subtract(iap.Deterministic(v1), v2)
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert v1 - v2 - _eps(sample) < sample < v1 - v2 + _eps(sample)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float64) + v1 - v2
)
def test_subtract_floats_both_deterministic(self):
values_float = [-100.0, -54.3, -1.0, 0.1, 0.0, 0.1, 1.0, 54.4, 100.0]
for v1, v2 in itertools.product(values_float, values_float):
with self.subTest(left=v1, right=v2):
p = iap.Subtract(iap.Deterministic(v1), iap.Deterministic(v2))
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert v1 - v2 - _eps(sample) < sample < v1 - v2 + _eps(sample)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float64) + v1 - v2
)
def test_subtract_stochastic_parameter(self):
param = iap.Subtract(iap.Deterministic(1.0),
(1.0, 2.0),
elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 - 2.0 - _eps(samples))
assert np.all(samples < 1.0 - 1.0 + _eps(samples))
assert (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_subtract_stochastic_parameter_elementwise(self):
param = iap.Subtract(iap.Deterministic(1.0),
(1.0, 2.0),
elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 - 2.0 - _eps(samples))
assert np.all(samples < 1.0 - 1.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_subtract_from_stochastic_parameter(self):
param = iap.Subtract(iap.Uniform(1.0, 2.0), 1.0, elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 - 1.0 - _eps(samples))
assert np.all(samples < 2.0 - 1.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_subtract_from_stochastic_parameter_elementwise(self):
param = iap.Subtract(iap.Uniform(1.0, 2.0), 1.0, elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 - 1.0 - _eps(samples))
assert np.all(samples < 2.0 - 1.0 + _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
class TestPower(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Power(iap.Deterministic(0), 1, elementwise=False)
expected = "Power(%s, %s, False)" % (
str(param.other_param),
str(param.val)
)
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int 1)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_pairs(self):
values = [
-100, -54, -1, 0, 1, 54, 100,
-100.0, -54.0, -1.0, 0.0, 1.0, 54.0, 100.0
]
exponents = [-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2]
for base, exponent in itertools.product(values, exponents):
if base < 0 and ia.is_single_float(exponent):
continue
if base == 0 and exponent < 0:
continue
with self.subTest(base=base, exponent=exponent):
p = iap.Power(iap.Deterministic(base), exponent)
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert (
base ** exponent - _eps(sample)
< sample <
base ** exponent + _eps(sample)
)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float64) + base ** exponent
)
def test_pairs_both_deterministic(self):
values = [
-100, -54, -1, 0, 1, 54, 100,
-100.0, -54.0, -1.0, 0.0, 1.0, 54.0, 100.0
]
exponents = [-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2]
for base, exponent in itertools.product(values, exponents):
if base < 0 and ia.is_single_float(exponent):
continue
if base == 0 and exponent < 0:
continue
with self.subTest(base=base, exponent=exponent):
p = iap.Power(iap.Deterministic(base), iap.Deterministic(exponent))
sample = p.draw_sample()
samples = p.draw_samples((2, 3))
assert (
base ** exponent - _eps(sample)
< sample <
base ** exponent + _eps(sample)
)
assert samples.dtype.kind == "f"
assert np.allclose(
samples,
np.zeros((2, 3), dtype=np.float64) + base ** exponent
)
def test_exponent_is_stochastic_parameter(self):
param = iap.Power(iap.Deterministic(1.5),
(1.0, 2.0),
elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.5 ** 1.0 - 2 * _eps(samples))
assert np.all(samples < 1.5 ** 2.0 + 2 * _eps(samples))
assert (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_exponent_is_stochastic_parameter_elementwise(self):
param = iap.Power(iap.Deterministic(1.5),
(1.0, 2.0),
elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.5 ** 1.0 - 2 * _eps(samples))
assert np.all(samples < 1.5 ** 2.0 + 2 * _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_value_is_uniform(self):
param = iap.Power(iap.Uniform(1.0, 2.0), 1.0, elementwise=False)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 ** 1.0 - 2 * _eps(samples))
assert np.all(samples < 2.0 ** 1.0 + 2 * _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
def test_value_is_uniform_elementwise(self):
param = iap.Power(iap.Uniform(1.0, 2.0), 1.0, elementwise=True)
samples = param.draw_samples((10, 20))
samples_sorted = np.sort(samples.flatten())
assert samples.shape == (10, 20)
assert np.all(samples > 1.0 ** 1.0 - 2 * _eps(samples))
assert np.all(samples < 2.0 ** 1.0 + 2 * _eps(samples))
assert not (
samples_sorted[0] - _eps(samples_sorted[0])
< samples_sorted[-1] <
samples_sorted[0] + _eps(samples_sorted[0])
)
class TestAbsolute(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Absolute(iap.Deterministic(0))
expected = "Absolute(%s)" % (str(param.other_param),)
assert "Deterministic(int 0)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_fixed_values(self):
simple_values = [-1.5, -1, -1.0, -0.1, 0, 0.0, 0.1, 1, 1.0, 1.5]
for value in simple_values:
with self.subTest(value=value):
param = iap.Absolute(iap.Deterministic(value))
sample = param.draw_sample()
samples = param.draw_samples((10, 5))
assert sample.shape == tuple()
assert samples.shape == (10, 5)
if ia.is_single_float(value):
assert (
abs(value) - _eps(sample)
< sample <
abs(value) + _eps(sample)
)
assert np.all(abs(value) - _eps(samples) < samples)
assert np.all(samples < abs(value) + _eps(samples))
else:
assert sample == abs(value)
assert np.all(samples == abs(value))
def test_value_is_stochastic_parameter(self):
param = iap.Absolute(iap.Choice([-3, -1, 1, 3]))
sample = param.draw_sample()
samples = param.draw_samples((10, 10))
samples_uq = np.sort(np.unique(samples))
assert sample.shape == tuple()
assert sample in [3, 1]
assert samples.shape == (10, 10)
assert len(samples_uq) == 2
assert samples_uq[0] == 1 and samples_uq[1] == 3
class TestRandomSign(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.RandomSign(iap.Deterministic(0), 0.5)
expected = "RandomSign(%s, 0.50)" % (str(param.other_param),)
assert "Deterministic(int 0)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_value_is_deterministic(self):
param = iap.RandomSign(iap.Deterministic(1))
samples = param.draw_samples((1000,))
n_positive = np.sum(samples == 1)
n_negative = np.sum(samples == -1)
assert samples.shape == (1000,)
assert n_positive + n_negative == 1000
assert 350 < n_positive < 750
def test_value_is_deterministic_many_samples(self):
param = iap.RandomSign(iap.Deterministic(1))
seen = [0, 0]
for _ in sm.xrange(1000):
sample = param.draw_sample()
assert sample.shape == tuple()
if sample == 1:
seen[1] += 1
else:
seen[0] += 1
n_negative, n_positive = seen
assert n_positive + n_negative == 1000
assert 350 < n_positive < 750
def test_value_is_stochastic_parameter(self):
param = iap.RandomSign(iap.Choice([1, 2]))
samples = param.draw_samples((4000,))
seen = [0, 0, 0, 0]
seen[0] = np.sum(samples == -2)
seen[1] = np.sum(samples == -1)
seen[2] = np.sum(samples == 1)
seen[3] = np.sum(samples == 2)
assert np.sum(seen) == 4000
assert all([700 < v < 1300 for v in seen])
def test_samples_same_values_for_same_seeds(self):
param = iap.RandomSign(iap.Choice([1, 2]))
samples1 = param.draw_samples((100, 10),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((100, 10),
random_state=iarandom.RNG(1234))
assert samples1.shape == (100, 10)
assert samples2.shape == (100, 10)
assert np.array_equal(samples1, samples2)
assert np.sum(samples1 == -2) > 50
assert np.sum(samples1 == -1) > 50
assert np.sum(samples1 == 1) > 50
assert np.sum(samples1 == 2) > 50
class TestForceSign(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.ForceSign(iap.Deterministic(0), True, "invert", 1)
expected = "ForceSign(%s, True, invert, 1)" % (str(param.other_param),)
assert "Deterministic(int 0)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_single_sample_positive(self):
param = iap.ForceSign(iap.Deterministic(1), positive=True,
mode="invert")
sample = param.draw_sample()
assert sample.shape == tuple()
assert sample == 1
def test_single_sample_negative(self):
param = iap.ForceSign(iap.Deterministic(1), positive=False,
mode="invert")
sample = param.draw_sample()
assert sample.shape == tuple()
assert sample == -1
def test_many_samples_positive(self):
param = iap.ForceSign(iap.Deterministic(1), positive=True,
mode="invert")
samples = param.draw_samples(100)
assert samples.shape == (100,)
assert np.all(samples == 1)
def test_many_samples_negative(self):
param = iap.ForceSign(iap.Deterministic(1), positive=False,
mode="invert")
samples = param.draw_samples(100)
assert samples.shape == (100,)
assert np.all(samples == -1)
def test_many_samples_negative_value_to_positive(self):
param = iap.ForceSign(iap.Deterministic(-1), positive=True,
mode="invert")
samples = param.draw_samples(100)
assert samples.shape == (100,)
assert np.all(samples == 1)
def test_many_samples_negative_value_to_negative(self):
param = iap.ForceSign(iap.Deterministic(-1), positive=False,
mode="invert")
samples = param.draw_samples(100)
assert samples.shape == (100,)
assert np.all(samples == -1)
def test_many_samples_stochastic_value_to_positive(self):
param = iap.ForceSign(iap.Choice([-2, 1]), positive=True,
mode="invert")
samples = param.draw_samples(1000)
n_twos = np.sum(samples == 2)
n_ones = np.sum(samples == 1)
assert samples.shape == (1000,)
assert n_twos + n_ones == 1000
assert 200 < n_twos < 700
assert 200 < n_ones < 700
def test_many_samples_stochastic_value_to_positive_reroll(self):
param = iap.ForceSign(iap.Choice([-2, 1]), positive=True,
mode="reroll")
samples = param.draw_samples(1000)
n_twos = np.sum(samples == 2)
n_ones = np.sum(samples == 1)
assert samples.shape == (1000,)
assert n_twos + n_ones == 1000
assert n_twos > 0
assert n_ones > 0
def test_many_samples_stochastic_value_to_positive_reroll_max_count(self):
param = iap.ForceSign(iap.Choice([-2, 1]), positive=True,
mode="reroll", reroll_count_max=100)
samples = param.draw_samples(100)
n_twos = np.sum(samples == 2)
n_ones = np.sum(samples == 1)
assert samples.shape == (100,)
assert n_twos + n_ones == 100
assert n_twos < 5
def test_samples_same_values_for_same_seeds(self):
param = iap.ForceSign(iap.Choice([-2, 1]),
positive=True,
mode="invert")
samples1 = param.draw_samples((100, 10),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((100, 10),
random_state=iarandom.RNG(1234))
assert samples1.shape == (100, 10)
assert samples2.shape == (100, 10)
assert np.array_equal(samples1, samples2)
class TestPositive(unittest.TestCase):
def setUp(self):
reseed()
def test_many_samples_reroll(self):
param = iap.Positive(iap.Deterministic(-1),
mode="reroll",
reroll_count_max=1)
samples = param.draw_samples((100,))
assert samples.shape == (100,)
assert np.all(samples == 1)
class TestNegative(unittest.TestCase):
def setUp(self):
reseed()
def test_many_samples_reroll(self):
param = iap.Negative(iap.Deterministic(1),
mode="reroll",
reroll_count_max=1)
samples = param.draw_samples((100,))
assert samples.shape == (100,)
assert np.all(samples == -1)
class TestIterativeNoiseAggregator(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.IterativeNoiseAggregator(iap.Deterministic(0),
iterations=(1, 3),
aggregation_method="max")
expected = "IterativeNoiseAggregator(%s, %s, %s)" % (
str(param.other_param),
str(param.iterations),
str(param.aggregation_method)
)
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int 1)" in str(param)
assert "Deterministic(int 3)" in str(param)
assert "Deterministic(max)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_value_is_deterministic_max_1_iter(self):
param = iap.IterativeNoiseAggregator(iap.Deterministic(1),
iterations=1,
aggregation_method="max")
sample = param.draw_sample()
samples = param.draw_samples((2, 4))
assert sample.shape == tuple()
assert samples.shape == (2, 4)
assert sample == 1
assert np.all(samples == 1)
def test_value_is_stochastic_avg_200_iter(self):
param = iap.IterativeNoiseAggregator(iap.Choice([0, 50]),
iterations=200,
aggregation_method="avg")
sample = param.draw_sample()
samples = param.draw_samples((2, 4))
assert sample.shape == tuple()
assert samples.shape == (2, 4)
assert 25 - 10 < sample < 25 + 10
assert np.all(np.logical_and(25 - 10 < samples, samples < 25 + 10))
def test_value_is_stochastic_max_100_iter(self):
param = iap.IterativeNoiseAggregator(iap.Choice([0, 50]),
iterations=100,
aggregation_method="max")
sample = param.draw_sample()
samples = param.draw_samples((2, 4))
assert sample.shape == tuple()
assert samples.shape == (2, 4)
assert sample == 50
assert np.all(samples == 50)
def test_value_is_stochastic_min_100_iter(self):
param = iap.IterativeNoiseAggregator(iap.Choice([0, 50]),
iterations=100,
aggregation_method="min")
sample = param.draw_sample()
samples = param.draw_samples((2, 4))
assert sample.shape == tuple()
assert samples.shape == (2, 4)
assert sample == 0
assert np.all(samples == 0)
def test_value_is_stochastic_avg_or_max_100_iter_evaluate_counts(self):
seen = [0, 0, 0, 0]
for _ in sm.xrange(100):
param = iap.IterativeNoiseAggregator(
iap.Choice([0, 50]),
iterations=100,
aggregation_method=["avg", "max"])
samples = param.draw_samples((1, 1))
diff_0 = abs(0 - samples[0, 0])
diff_25 = abs(25 - samples[0, 0])
diff_50 = abs(50 - samples[0, 0])
if diff_25 < 10.0:
seen[0] += 1
elif diff_50 < _eps(samples):
seen[1] += 1
elif diff_0 < _eps(samples):
seen[2] += 1
else:
seen[3] += 1
assert seen[2] <= 2 # around 0.0
assert seen[3] <= 2 # 0.0+eps <= x < 15.0 or 35.0 < x < 50.0 or >50.0
assert 50 - 20 < seen[0] < 50 + 20
assert 50 - 20 < seen[1] < 50 + 20
def test_value_is_stochastic_avg_tuple_as_iter_evaluate_histograms(self):
# iterations as tuple
param = iap.IterativeNoiseAggregator(
iap.Uniform(-1.0, 1.0),
iterations=(1, 100),
aggregation_method="avg")
diffs = []
for _ in sm.xrange(100):
samples = param.draw_samples((1, 1))
diff = abs(samples[0, 0] - 0.0)
diffs.append(diff)
nb_bins = 3
hist, _ = np.histogram(diffs, bins=nb_bins, range=(-1.0, 1.0),
density=False)
assert hist[1] > hist[0]
assert hist[1] > hist[2]
def test_value_is_stochastic_max_list_as_iter_evaluate_counts(self):
# iterations as list
seen = [0, 0]
for _ in sm.xrange(400):
param = iap.IterativeNoiseAggregator(
iap.Choice([0, 50]),
iterations=[1, 100],
aggregation_method=["max"])
samples = param.draw_samples((1, 1))
diff_0 = abs(0 - samples[0, 0])
diff_50 = abs(50 - samples[0, 0])
if diff_50 < _eps(samples):
seen[0] += 1
elif diff_0 < _eps(samples):
seen[1] += 1
else:
assert False
assert 300 - 50 < seen[0] < 300 + 50
assert 100 - 50 < seen[1] < 100 + 50
def test_value_is_stochastic_all_100_iter(self):
# test ia.ALL as aggregation_method
# note that each method individually and list of methods are already
# tested, so no in depth test is needed here
param = iap.IterativeNoiseAggregator(
iap.Choice([0, 50]), iterations=100, aggregation_method=ia.ALL)
assert isinstance(param.aggregation_method, iap.Choice)
assert len(param.aggregation_method.a) == 3
assert [v in param.aggregation_method.a for v in ["min", "avg", "max"]]
def test_value_is_stochastic_max_2_iter(self):
param = iap.IterativeNoiseAggregator(
iap.Choice([0, 50]), iterations=2, aggregation_method="max")
samples = param.draw_samples((2, 1000))
nb_0 = np.sum(samples == 0)
nb_50 = np.sum(samples == 50)
assert nb_0 + nb_50 == 2 * 1000
assert 0.25 - 0.05 < nb_0 / (2 * 1000) < 0.25 + 0.05
def test_samples_same_values_for_same_seeds(self):
param = iap.IterativeNoiseAggregator(
iap.Choice([0, 50]), iterations=5, aggregation_method="avg")
samples1 = param.draw_samples((100, 10),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((100, 10),
random_state=iarandom.RNG(1234))
assert samples1.shape == (100, 10)
assert samples2.shape == (100, 10)
assert np.allclose(samples1, samples2)
def test_stochastic_param_as_aggregation_method(self):
param = iap.IterativeNoiseAggregator(
iap.Choice([0, 50]),
iterations=5,
aggregation_method=iap.Deterministic("max"))
assert isinstance(param.aggregation_method, iap.Deterministic)
assert param.aggregation_method.value == "max"
def test_bad_datatype_for_aggregation_method(self):
with self.assertRaises(Exception) as context:
_ = iap.IterativeNoiseAggregator(
iap.Choice([0, 50]), iterations=5, aggregation_method=False)
self.assertTrue(
"Expected aggregation_method to be" in str(context.exception))
def test_bad_datatype_for_iterations(self):
with self.assertRaises(Exception) as context:
_ = iap.IterativeNoiseAggregator(
iap.Choice([0, 50]),
iterations=False,
aggregation_method="max")
self.assertTrue("Expected iterations to be" in str(context.exception))
class TestSigmoid(unittest.TestCase):
def setUp(self):
reseed()
def test___init__(self):
param = iap.Sigmoid(
iap.Deterministic(0),
threshold=(-10, 10),
activated=True,
mul=1,
add=0
)
expected = "Sigmoid(%s, %s, %s, 1, 0)" % (
str(param.other_param),
str(param.threshold),
str(param.activated)
)
assert "Deterministic(int 0)" in str(param)
assert "Deterministic(int -10)" in str(param)
assert "Deterministic(int 1)" in str(param)
assert (
param.__str__()
== param.__repr__()
== expected
)
def test_activated_is_true(self):
param = iap.Sigmoid(
iap.Deterministic(5),
add=0,
mul=1,
threshold=0.5,
activated=True)
expected = 1 / (1 + np.exp(-(5 * 1 + 0 - 0.5)))
sample = param.draw_sample()
samples = param.draw_samples((5, 10))
assert sample.shape == tuple()
assert samples.shape == (5, 10)
assert expected - _eps(sample) < sample < expected + _eps(sample)
assert np.all(
np.logical_and(
expected - _eps(samples) < samples,
samples < expected + _eps(samples)
)
)
def test_activated_is_false(self):
param = iap.Sigmoid(
iap.Deterministic(5),
add=0,
mul=1,
threshold=0.5,
activated=False)
expected = 5
sample = param.draw_sample()
samples = param.draw_samples((5, 10))
assert sample.shape == tuple()
assert samples.shape == (5, 10)
assert expected - _eps(sample) < sample < expected + _eps(sample)
assert np.all(
np.logical_and(
expected - _eps(sample) < samples,
samples < expected + _eps(sample)
)
)
def test_activated_is_probabilistic(self):
param = iap.Sigmoid(
iap.Deterministic(5),
add=0,
mul=1,
threshold=0.5,
activated=0.5)
expected_first = 5
expected_second = 1 / (1 + np.exp(-(5 * 1 + 0 - 0.5)))
seen = [0, 0]
for _ in sm.xrange(1000):
sample = param.draw_sample()
diff_first = abs(sample - expected_first)
diff_second = abs(sample - expected_second)
if diff_first < _eps(sample):
seen[0] += 1
elif diff_second < _eps(sample):
seen[1] += 1
else:
assert False
assert 500 - 150 < seen[0] < 500 + 150
assert 500 - 150 < seen[1] < 500 + 150
def test_value_is_stochastic_param(self):
param = iap.Sigmoid(
iap.Choice([1, 10]),
add=0,
mul=1,
threshold=0.5,
activated=True)
expected_first = 1 / (1 + np.exp(-(1 * 1 + 0 - 0.5)))
expected_second = 1 / (1 + np.exp(-(10 * 1 + 0 - 0.5)))
seen = [0, 0]
for _ in sm.xrange(1000):
sample = param.draw_sample()
diff_first = abs(sample - expected_first)
diff_second = abs(sample - expected_second)
if diff_first < _eps(sample):
seen[0] += 1
elif diff_second < _eps(sample):
seen[1] += 1
else:
assert False
assert 500 - 150 < seen[0] < 500 + 150
assert 500 - 150 < seen[1] < 500 + 150
def test_mul_add_threshold_with_various_fixed_values(self):
muls = [0.1, 1, 10.3]
adds = [-5.7, -0.0734, 0, 0.0734, 5.7]
vals = [-1, -0.7, 0, 0.7, 1]
threshs = [-5.7, -0.0734, 0, 0.0734, 5.7]
for mul, add, val, thresh in itertools.product(muls, adds, vals,
threshs):
with self.subTest(mul=mul, add=add, val=val, threshold=thresh):
param = iap.Sigmoid(
iap.Deterministic(val),
add=add,
mul=mul,
threshold=thresh)
sample = param.draw_sample()
samples = param.draw_samples((2, 3))
dt = sample.dtype
val_ = np.array([val], dtype=dt)
mul_ = np.array([mul], dtype=dt)
add_ = np.array([add], dtype=dt)
thresh_ = np.array([thresh], dtype=dt)
expected = (
1 / (
1 + np.exp(
-(val_ * mul_ + add_ - thresh_)
)
)
)
assert sample.shape == tuple()
assert samples.shape == (2, 3)
assert (
expected - 5*_eps(sample)
< sample <
expected + 5*_eps(sample)
)
assert np.all(
np.logical_and(
expected - 5*_eps(sample) < samples,
samples < expected + 5*_eps(sample)
)
)
def test_samples_same_values_for_same_seeds(self):
param = iap.Sigmoid(
iap.Choice([1, 10]),
add=0,
mul=1,
threshold=0.5,
activated=True)
samples1 = param.draw_samples((100, 10),
random_state=iarandom.RNG(1234))
samples2 = param.draw_samples((100, 10),
random_state=iarandom.RNG(1234))
assert samples1.shape == (100, 10)
assert samples2.shape == (100, 10)
assert np.array_equal(samples1, samples2)
================================================
FILE: test/test_random.py
================================================
from __future__ import print_function, division, absolute_import
import copy as copylib
import sys
# unittest only added in 3.4 self.subTest()
if sys.version_info[0] < 3 or sys.version_info[1] < 4:
import unittest2 as unittest
else:
import unittest
# unittest.mock is not available in 2.7 (though unittest2 might contain it?)
try:
import unittest.mock as mock
except ImportError:
import mock
import numpy as np
import imgaug as ia
import imgaug.augmenters as iaa
from imgaug.testutils import reseed
import imgaug.random as iarandom
NP_VERSION = np.__version__
IS_NP_117_OR_HIGHER = (
NP_VERSION.startswith("2.")
or NP_VERSION.startswith("1.25")
or NP_VERSION.startswith("1.24")
or NP_VERSION.startswith("1.23")
or NP_VERSION.startswith("1.22")
or NP_VERSION.startswith("1.21")
or NP_VERSION.startswith("1.20")
or NP_VERSION.startswith("1.19")
or NP_VERSION.startswith("1.18")
or NP_VERSION.startswith("1.17")
)
class _Base(unittest.TestCase):
def setUp(self):
reseed()
class TestConstants(_Base):
def test_supports_new_np_rng_style_is_true(self):
assert iarandom.SUPPORTS_NEW_NP_RNG_STYLE is IS_NP_117_OR_HIGHER
def test_global_rng(self):
iarandom.get_global_rng() # creates global RNG upon first call
assert iarandom.GLOBAL_RNG is not None
class TestRNG(_Base):
@mock.patch("imgaug.random.normalize_generator_")
def test___init___calls_normalize_mocked(self, mock_norm):
_ = iarandom.RNG(0)
mock_norm.assert_called_once_with(0)
def test___init___with_rng(self):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(rng1)
assert rng2.generator is rng1.generator
@mock.patch("imgaug.random.get_generator_state")
def test_state_getter_mocked(self, mock_get):
mock_get.return_value = "mock"
rng = iarandom.RNG(0)
result = rng.state
assert result == "mock"
mock_get.assert_called_once_with(rng.generator)
@mock.patch("imgaug.random.RNG.set_state_")
def test_state_setter_mocked(self, mock_set):
rng = iarandom.RNG(0)
state = {"foo"}
rng.state = state
mock_set.assert_called_once_with(state)
@mock.patch("imgaug.random.set_generator_state_")
def test_set_state__mocked(self, mock_set):
rng = iarandom.RNG(0)
state = {"foo"}
result = rng.set_state_(state)
assert result is rng
mock_set.assert_called_once_with(rng.generator, state)
@mock.patch("imgaug.random.set_generator_state_")
def test_use_state_of__mocked(self, mock_set):
rng1 = iarandom.RNG(0)
rng2 = mock.MagicMock()
state = {"foo"}
rng2.state = state
result = rng1.use_state_of_(rng2)
assert result == rng1
mock_set.assert_called_once_with(rng1.generator, state)
@mock.patch("imgaug.random.get_global_rng")
def test_is_global__is_global__rng_mocked(self, mock_get):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(rng1.generator)
mock_get.return_value = rng2
assert rng1.is_global_rng() is True
@mock.patch("imgaug.random.get_global_rng")
def test_is_global_rng__is_not_global__mocked(self, mock_get):
rng1 = iarandom.RNG(0)
# different instance with same state/seed should still be viewed as
# different by the method
rng2 = iarandom.RNG(0)
mock_get.return_value = rng2
assert rng1.is_global_rng() is False
@mock.patch("imgaug.random.get_global_rng")
def test_equals_global_rng__is_global__mocked(self, mock_get):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(0)
mock_get.return_value = rng2
assert rng1.equals_global_rng() is True
@mock.patch("imgaug.random.get_global_rng")
def test_equals_global_rng__is_not_global__mocked(self, mock_get):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(1)
mock_get.return_value = rng2
assert rng1.equals_global_rng() is False
@mock.patch("imgaug.random.generate_seed_")
def test_generate_seed__mocked(self, mock_gen):
rng = iarandom.RNG(0)
mock_gen.return_value = -1
seed = rng.generate_seed_()
assert seed == -1
mock_gen.assert_called_once_with(rng.generator)
@mock.patch("imgaug.random.generate_seeds_")
def test_generate_seeds__mocked(self, mock_gen):
rng = iarandom.RNG(0)
mock_gen.return_value = [-1, -2]
seeds = rng.generate_seeds_(2)
assert seeds == [-1, -2]
mock_gen.assert_called_once_with(rng.generator, 2)
@mock.patch("imgaug.random.reset_generator_cache_")
def test_reset_cache__mocked(self, mock_reset):
rng = iarandom.RNG(0)
result = rng.reset_cache_()
assert result is rng
mock_reset.assert_called_once_with(rng.generator)
@mock.patch("imgaug.random.derive_generators_")
def test_derive_rng__mocked(self, mock_derive):
gen = iarandom.convert_seed_to_generator(0)
mock_derive.return_value = [gen]
rng = iarandom.RNG(0)
result = rng.derive_rng_()
assert result.generator is gen
mock_derive.assert_called_once_with(rng.generator, 1)
@mock.patch("imgaug.random.derive_generators_")
def test_derive_rngs__mocked(self, mock_derive):
gen1 = iarandom.convert_seed_to_generator(0)
gen2 = iarandom.convert_seed_to_generator(1)
mock_derive.return_value = [gen1, gen2]
rng = iarandom.RNG(0)
result = rng.derive_rngs_(2)
assert result[0].generator is gen1
assert result[1].generator is gen2
mock_derive.assert_called_once_with(rng.generator, 2)
@mock.patch("imgaug.random.is_generator_equal_to")
def test_equals_mocked(self, mock_equal):
mock_equal.return_value = "foo"
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(1)
result = rng1.equals(rng2)
assert result == "foo"
mock_equal.assert_called_once_with(rng1.generator, rng2.generator)
def test_equals_identical_generators(self):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(rng1)
assert rng1.equals(rng2)
def test_equals_with_similar_generators(self):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(0)
assert rng1.equals(rng2)
def test_equals_with_different_generators(self):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(1)
assert not rng1.equals(rng2)
def test_equals_with_advanced_generator(self):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(0)
rng2.advance_()
assert not rng1.equals(rng2)
@mock.patch("imgaug.random.advance_generator_")
def test_advance__mocked(self, mock_advance):
rng = iarandom.RNG(0)
result = rng.advance_()
assert result is rng
mock_advance.assert_called_once_with(rng.generator)
@mock.patch("imgaug.random.copy_generator")
def test_copy_mocked(self, mock_copy):
rng1 = iarandom.RNG(0)
rng2 = iarandom.RNG(1)
mock_copy.return_value = rng2.generator
result = rng1.copy()
assert result.generator is rng2.generator
mock_copy.assert_called_once_with(rng1.generator)
@mock.patch("imgaug.random.RNG.copy")
@mock.patch("imgaug.random.RNG.is_global_rng")
def test_copy_unless_global_rng__is_global__mocked(self, mock_is_global,
mock_copy):
rng = iarandom.RNG(0)
mock_is_global.return_value = True
mock_copy.return_value = "foo"
result = rng.copy_unless_global_rng()
assert result is rng
mock_is_global.assert_called_once_with()
assert mock_copy.call_count == 0
@mock.patch("imgaug.random.RNG.copy")
@mock.patch("imgaug.random.RNG.is_global_rng")
def test_copy_unless_global_rng__is_not_global__mocked(self, mock_is_global,
mock_copy):
rng = iarandom.RNG(0)
mock_is_global.return_value = False
mock_copy.return_value = "foo"
result = rng.copy_unless_global_rng()
assert result is "foo"
mock_is_global.assert_called_once_with()
mock_copy.assert_called_once_with()
def test_duplicate(self):
rng = iarandom.RNG(0)
rngs = rng.duplicate(1)
assert rngs == [rng]
def test_duplicate_two_entries(self):
rng = iarandom.RNG(0)
rngs = rng.duplicate(2)
assert rngs == [rng, rng]
@mock.patch("imgaug.random.create_fully_random_generator")
def test_create_fully_random_mocked(self, mock_create):
gen = iarandom.convert_seed_to_generator(0)
mock_create.return_value = gen
rng = iarandom.RNG.create_fully_random()
mock_create.assert_called_once_with()
assert rng.generator is gen
@mock.patch("imgaug.random.derive_generators_")
def test_create_pseudo_random__mocked(self, mock_get):
rng_glob = iarandom.get_global_rng()
rng = iarandom.RNG(0)
mock_get.return_value = [rng.generator]
result = iarandom.RNG.create_pseudo_random_()
assert result.generator is rng.generator
mock_get.assert_called_once_with(rng_glob.generator, 1)
@mock.patch("imgaug.random.polyfill_integers")
def test_integers_mocked(self, mock_func):
mock_func.return_value = "foo"
rng = iarandom.RNG(0)
result = rng.integers(low=0, high=1, size=(1,), dtype="int64",
endpoint=True)
assert result == "foo"
mock_func.assert_called_once_with(
rng.generator, low=0, high=1, size=(1,), dtype="int64",
endpoint=True)
@mock.patch("imgaug.random.polyfill_random")
def test_random_mocked(self, mock_func):
mock_func.return_value = "foo"
rng = iarandom.RNG(0)
out = np.zeros((1,), dtype="float64")
result = rng.random(size=(1,), dtype="float64", out=out)
assert result == "foo"
mock_func.assert_called_once_with(
rng.generator, size=(1,), dtype="float64", out=out)
# TODO below test for generator methods are all just mock-based, add
# non-mocked versions
def test_choice_mocked(self):
self._test_sampling_func("choice", a=[1, 2, 3], size=(1,),
replace=False, p=[0.1, 0.2, 0.7])
def test_bytes_mocked(self):
self._test_sampling_func("bytes", length=[10])
def test_shuffle_mocked(self):
mock_gen = mock.MagicMock()
rng = iarandom.RNG(0)
rng.generator = mock_gen
rng.shuffle([1, 2, 3])
mock_gen.shuffle.assert_called_once_with([1, 2, 3])
def test_permutation_mocked(self):
mock_gen = mock.MagicMock()
rng = iarandom.RNG(0)
rng.generator = mock_gen
mock_gen.permutation.return_value = "foo"
result = rng.permutation([1, 2, 3])
assert result == "foo"
mock_gen.permutation.assert_called_once_with([1, 2, 3])
def test_beta_mocked(self):
self._test_sampling_func("beta", a=1.0, b=2.0, size=(1,))
def test_binomial_mocked(self):
self._test_sampling_func("binomial", n=10, p=0.1, size=(1,))
def test_chisquare_mocked(self):
self._test_sampling_func("chisquare", df=2, size=(1,))
def test_dirichlet_mocked(self):
self._test_sampling_func("dirichlet", alpha=0.1, size=(1,))
def test_exponential_mocked(self):
self._test_sampling_func("exponential", scale=1.1, size=(1,))
def test_f_mocked(self):
self._test_sampling_func("f", dfnum=1, dfden=2, size=(1,))
def test_gamma_mocked(self):
self._test_sampling_func("gamma", shape=1, scale=1.2, size=(1,))
def test_geometric_mocked(self):
self._test_sampling_func("geometric", p=0.5, size=(1,))
def test_gumbel_mocked(self):
self._test_sampling_func("gumbel", loc=0.1, scale=1.1, size=(1,))
def test_hypergeometric_mocked(self):
self._test_sampling_func("hypergeometric", ngood=2, nbad=4, nsample=6,
size=(1,))
def test_laplace_mocked(self):
self._test_sampling_func("laplace", loc=0.5, scale=1.5, size=(1,))
def test_logistic_mocked(self):
self._test_sampling_func("logistic", loc=0.5, scale=1.5, size=(1,))
def test_lognormal_mocked(self):
self._test_sampling_func("lognormal", mean=0.5, sigma=1.5, size=(1,))
def test_logseries_mocked(self):
self._test_sampling_func("logseries", p=0.5, size=(1,))
def test_multinomial_mocked(self):
self._test_sampling_func("multinomial", n=5, pvals=0.5, size=(1,))
def test_multivariate_normal_mocked(self):
self._test_sampling_func("multivariate_normal", mean=0.5, cov=1.0,
size=(1,), check_valid="foo", tol=1e-2)
def test_negative_binomial_mocked(self):
self._test_sampling_func("negative_binomial", n=10, p=0.5, size=(1,))
def test_noncentral_chisquare_mocked(self):
self._test_sampling_func("noncentral_chisquare", df=0.5, nonc=1.0,
size=(1,))
def test_noncentral_f_mocked(self):
self._test_sampling_func("noncentral_f", dfnum=0.5, dfden=1.5,
nonc=2.0, size=(1,))
def test_normal_mocked(self):
self._test_sampling_func("normal", loc=0.5, scale=1.0, size=(1,))
def test_pareto_mocked(self):
self._test_sampling_func("pareto", a=0.5, size=(1,))
def test_poisson_mocked(self):
self._test_sampling_func("poisson", lam=1.5, size=(1,))
def test_power_mocked(self):
self._test_sampling_func("power", a=0.5, size=(1,))
def test_rayleigh_mocked(self):
self._test_sampling_func("rayleigh", scale=1.5, size=(1,))
def test_standard_cauchy_mocked(self):
self._test_sampling_func("standard_cauchy", size=(1,))
def test_standard_exponential_np117_mocked(self):
fname = "standard_exponential"
arr = np.zeros((1,), dtype="float16")
args = []
kwargs = {"size": (1,), "dtype": "float16", "method": "foo",
"out": arr}
mock_gen = mock.MagicMock()
getattr(mock_gen, fname).return_value = "foo"
rng = iarandom.RNG(0)
rng.generator = mock_gen
rng._is_new_rng_style = True
result = getattr(rng, fname)(*args, **kwargs)
assert result == "foo"
getattr(mock_gen, fname).assert_called_once_with(*args, **kwargs)
def test_standard_exponential_np116_mocked(self):
fname = "standard_exponential"
arr_out = np.zeros((1,), dtype="float16")
arr_result = np.ones((1,), dtype="float16")
def _side_effect(x):
return arr_result
args = []
kwargs = {"size": (1,), "dtype": "float16", "method": "foo",
"out": arr_out}
kwargs_subcall = {"size": (1,)}
mock_gen = mock.MagicMock()
mock_gen.astype.side_effect = _side_effect
getattr(mock_gen, fname).return_value = mock_gen
rng = iarandom.RNG(0)
rng.generator = mock_gen
rng._is_new_rng_style = False
result = getattr(rng, fname)(*args, **kwargs)
getattr(mock_gen, fname).assert_called_once_with(*args,
**kwargs_subcall)
mock_gen.astype.assert_called_once_with("float16")
assert np.allclose(result, arr_result)
assert np.allclose(arr_out, arr_result)
def test_standard_gamma_np117_mocked(self):
fname = "standard_gamma"
arr = np.zeros((1,), dtype="float16")
args = []
kwargs = {"shape": 1.0, "size": (1,), "dtype": "float16", "out": arr}
mock_gen = mock.MagicMock()
getattr(mock_gen, fname).return_value = "foo"
rng = iarandom.RNG(0)
rng.generator = mock_gen
rng._is_new_rng_style = True
result = getattr(rng, fname)(*args, **kwargs)
assert result == "foo"
getattr(mock_gen, fname).assert_called_once_with(*args, **kwargs)
def test_standard_gamma_np116_mocked(self):
fname = "standard_gamma"
arr_out = np.zeros((1,), dtype="float16")
arr_result = np.ones((1,), dtype="float16")
def _side_effect(x):
return arr_result
args = []
kwargs = {"shape": 1.0, "size": (1,), "dtype": "float16",
"out": arr_out}
kwargs_subcall = {"shape": 1.0, "size": (1,)}
mock_gen = mock.MagicMock()
mock_gen.astype.side_effect = _side_effect
getattr(mock_gen, fname).return_value = mock_gen
rng = iarandom.RNG(0)
rng.generator = mock_gen
rng._is_new_rng_style = False
result = getattr(rng, fname)(*args, **kwargs)
getattr(mock_gen, fname).assert_called_once_with(*args,
**kwargs_subcall)
mock_gen.astype.assert_called_once_with("float16")
assert np.allclose(result, arr_result)
assert np.allclose(arr_out, arr_result)
def test_standard_normal_np117_mocked(self):
fname = "standard_normal"
arr = np.zeros((1,), dtype="float16")
args = []
kwargs = {"size": (1,), "dtype": "float16", "out": arr}
mock_gen = mock.MagicMock()
getattr(mock_gen, fname).return_value = "foo"
rng = iarandom.RNG(0)
rng.generator = mock_gen
rng._is_new_rng_style = True
result = getattr(rng, fname)(*args, **kwargs)
assert result == "foo"
getattr(mock_gen, fname).assert_called_once_with(*args, **kwargs)
def test_standard_normal_np116_mocked(self):
fname = "standard_normal"
arr_out = np.zeros((1,), dtype="float16")
arr_result = np.ones((1,), dtype="float16")
def _side_effect(x):
return arr_result
args = []
kwargs = {"size": (1,), "dtype": "float16", "out": arr_out}
kwargs_subcall = {"size": (1,)}
mock_gen = mock.MagicMock()
mock_gen.astype.side_effect = _side_effect
getattr(mock_gen, fname).return_value = mock_gen
rng = iarandom.RNG(0)
rng.generator = mock_gen
rng._is_new_rng_style = False
result = getattr(rng, fname)(*args, **kwargs)
getattr(mock_gen, fname).assert_called_once_with(*args,
**kwargs_subcall)
mock_gen.astype.assert_called_once_with("float16")
assert np.allclose(result, arr_result)
assert np.allclose(arr_out, arr_result)
def test_standard_t_mocked(self):
self._test_sampling_func("standard_t", df=1.5, size=(1,))
def test_triangular_mocked(self):
self._test_sampling_func("triangular", left=1.0, mode=1.5, right=2.0,
size=(1,))
def test_uniform_mocked(self):
self._test_sampling_func("uniform", low=0.5, high=1.5, size=(1,))
def test_vonmises_mocked(self):
self._test_sampling_func("vonmises", mu=1.0, kappa=1.5, size=(1,))
def test_wald_mocked(self):
self._test_sampling_func("wald", mean=0.5, scale=1.0, size=(1,))
def test_weibull_mocked(self):
self._test_sampling_func("weibull", a=1.0, size=(1,))
def test_zipf_mocked(self):
self._test_sampling_func("zipf", a=1.0, size=(1,))
@classmethod
def _test_sampling_func(cls, fname, *args, **kwargs):
mock_gen = mock.MagicMock()
getattr(mock_gen, fname).return_value = "foo"
rng = iarandom.RNG(0)
rng.generator = mock_gen
result = getattr(rng, fname)(*args, **kwargs)
assert result == "foo"
getattr(mock_gen, fname).assert_called_once_with(*args, **kwargs)
#
# outdated methods from RandomState
#
def test_rand_mocked(self):
self._test_sampling_func_alias("rand", "random", 1, 2, 3)
def test_randint_mocked(self):
self._test_sampling_func_alias("randint", "integers", 0, 100)
def randn(self):
self._test_sampling_func_alias("randn", "standard_normal", 1, 2, 3)
def random_integers(self):
self._test_sampling_func_alias("random_integers", "integers", 1, 2)
def random_sample(self):
self._test_sampling_func_alias("random_sample", "uniform", (1, 2, 3))
def tomaxint(self):
self._test_sampling_func_alias("tomaxint", "integers", (1, 2, 3))
def test_rand(self):
result = iarandom.RNG(0).rand(10, 20, 3)
assert result.dtype.name == "float32"
assert result.shape == (10, 20, 3)
assert np.all(result >= 0.0)
assert np.all(result <= 1.0)
assert np.any(result > 0.0)
assert np.any(result < 1.0)
def test_randint(self):
result = iarandom.RNG(0).randint(10, 100, size=(10, 20, 3))
assert result.dtype.name == "int32"
assert result.shape == (10, 20, 3)
assert np.all(result >= 10)
assert np.all(result <= 99)
assert np.any(result > 10)
assert np.any(result < 99)
def test_randn(self):
result = iarandom.RNG(0).randn(10, 50, 3)
assert result.dtype.name == "float32"
assert result.shape == (10, 50, 3)
assert np.any(result > 0.5)
assert np.any(result < -0.5)
assert np.average(np.logical_or(result < 2.0, result > -2.0)) > 0.5
def test_random_integers(self):
result = iarandom.RNG(0).random_integers(10, 100, size=(10, 20, 3))
assert result.dtype.name == "int32"
assert result.shape == (10, 20, 3)
assert np.all(result >= 10)
assert np.all(result <= 100)
assert np.any(result > 10)
assert np.any(result < 100)
def test_random_integers__no_high(self):
result = iarandom.RNG(0).random_integers(100, size=(10, 20, 3))
assert result.dtype.name == "int32"
assert result.shape == (10, 20, 3)
assert np.all(result >= 1)
assert np.all(result <= 100)
assert np.any(result > 1)
assert np.any(result < 100)
def test_random_sample(self):
result = iarandom.RNG(0).random_sample((10, 20, 3))
assert result.dtype.name == "float64"
assert result.shape == (10, 20, 3)
assert np.all(result >= 0.0)
assert np.all(result <= 1.0)
assert np.any(result > 0.0)
assert np.any(result < 1.0)
def test_tomaxint(self):
result = iarandom.RNG(0).tomaxint((10, 200, 3))
assert result.dtype.name == "int32"
assert result.shape == (10, 200, 3)
assert np.all(result >= 0)
assert np.any(result > 10000)
@classmethod
def _test_sampling_func_alias(cls, fname_alias, fname_subcall, *args,
**kwargs):
rng = iarandom.RNG(0)
mock_func = mock.Mock()
mock_func.return_value = "foo"
setattr(rng, fname_subcall, mock_func)
result = getattr(rng, fname_alias)(*args, **kwargs)
assert result == "foo"
assert mock_func.call_count == 1
class Test_supports_new_numpy_rng_style(_Base):
def test_call(self):
assert iarandom.supports_new_numpy_rng_style() is IS_NP_117_OR_HIGHER
class Test_get_global_rng(_Base):
def test_call(self):
iarandom.seed(0)
rng = iarandom.get_global_rng()
expected = iarandom.RNG(0)
assert rng is not None
assert rng.equals(expected)
class Test_seed(_Base):
@mock.patch("imgaug.random._seed_np117_")
@mock.patch("imgaug.random._seed_np116_")
def test_mocked_call(self, mock_np116, mock_np117):
iarandom.seed(1)
if IS_NP_117_OR_HIGHER:
mock_np117.assert_called_once_with(1)
assert mock_np116.call_count == 0
else:
mock_np116.assert_called_once_with(1)
assert mock_np117.call_count == 0
def test_integrationtest(self):
iarandom.seed(1)
assert iarandom.GLOBAL_RNG.equals(iarandom.RNG(1))
def test_seed_affects_augmenters_created_after_its_call(self):
image = np.full((50, 50, 3), 128, dtype=np.uint8)
images_aug = []
for _ in np.arange(5):
iarandom.seed(100)
aug = iaa.AdditiveGaussianNoise(scale=50, per_channel=True)
images_aug.append(aug(image=image))
# assert all images identical
for other_image_aug in images_aug[1:]:
assert np.array_equal(images_aug[0], other_image_aug)
# but different seed must lead to different image
iarandom.seed(101)
aug = iaa.AdditiveGaussianNoise(scale=50, per_channel=True)
image_aug = aug(image=image)
assert not np.array_equal(images_aug[0], image_aug)
def test_seed_affects_augmenters_created_before_its_call(self):
image = np.full((50, 50, 3), 128, dtype=np.uint8)
images_aug = []
for _ in np.arange(5):
aug = iaa.AdditiveGaussianNoise(scale=50, per_channel=True)
iarandom.seed(100)
images_aug.append(aug(image=image))
# assert all images identical
for other_image_aug in images_aug[1:]:
assert np.array_equal(images_aug[0], other_image_aug)
# but different seed must lead to different image
aug = iaa.AdditiveGaussianNoise(scale=50, per_channel=True)
iarandom.seed(101)
image_aug = aug(image=image)
assert not np.array_equal(images_aug[0], image_aug)
class Test_normalize_generator(_Base):
@mock.patch("imgaug.random.normalize_generator_")
def test_mocked_call(self, mock_subfunc):
mock_subfunc.return_value = "foo"
inputs = ["bar"]
result = iarandom.normalize_generator(inputs)
assert mock_subfunc.call_count == 1
assert mock_subfunc.call_args[0][0] is not inputs
assert mock_subfunc.call_args[0][0] == inputs
assert result == "foo"
class Test_normalize_generator_(_Base):
@mock.patch("imgaug.random._normalize_generator_np117_")
@mock.patch("imgaug.random._normalize_generator_np116_")
def test_mocked_call(self, mock_np116, mock_np117):
mock_np116.return_value = "np116"
mock_np117.return_value = "np117"
result = iarandom.normalize_generator_(None)
if IS_NP_117_OR_HIGHER:
assert result == "np117"
mock_np117.assert_called_once_with(None)
assert mock_np116.call_count == 0
else:
assert result == "np116"
mock_np116.assert_called_once_with(None)
assert mock_np117.call_count == 0
def test_called_with_none(self):
result = iarandom.normalize_generator_(None)
assert result is iarandom.get_global_rng().generator
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"SeedSequence does not exist in numpy <=1.16")
def test_called_with_seed_sequence(self):
seedseq = np.random.SeedSequence(0)
result = iarandom.normalize_generator_(seedseq)
expected = np.random.Generator(
iarandom.BIT_GENERATOR(np.random.SeedSequence(0)))
assert iarandom.is_generator_equal_to(result, expected)
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"BitGenerator does not exist in numpy <=1.16")
def test_called_with_bit_generator(self):
bgen = iarandom.BIT_GENERATOR(np.random.SeedSequence(0))
result = iarandom.normalize_generator_(bgen)
assert result.bit_generator is bgen
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Generator does not exist in numpy <=1.16")
def test_called_with_generator(self):
gen = np.random.Generator(
iarandom.BIT_GENERATOR(np.random.SeedSequence(0))
)
result = iarandom.normalize_generator_(gen)
assert result is gen
def test_called_with_random_state(self):
rs = np.random.RandomState(0)
result = iarandom.normalize_generator_(rs)
if IS_NP_117_OR_HIGHER:
seed = iarandom.generate_seed_(np.random.RandomState(0))
expected = iarandom.convert_seed_to_generator(seed)
assert iarandom.is_generator_equal_to(result, expected)
else:
assert result is rs
def test_called_int(self):
seed = 0
result = iarandom.normalize_generator_(seed)
expected = iarandom.convert_seed_to_generator(seed)
assert iarandom.is_generator_equal_to(result, expected)
class Test_convert_seed_to_generator(_Base):
@mock.patch("imgaug.random._convert_seed_to_generator_np117")
@mock.patch("imgaug.random._convert_seed_to_generator_np116")
def test_mocked_call(self, mock_np116, mock_np117):
mock_np116.return_value = "np116"
mock_np117.return_value = "np117"
result = iarandom.convert_seed_to_generator(1)
if IS_NP_117_OR_HIGHER:
assert result == "np117"
mock_np117.assert_called_once_with(1)
assert mock_np116.call_count == 0
else:
assert result == "np116"
mock_np116.assert_called_once_with(1)
assert mock_np117.call_count == 0
def test_call(self):
gen = iarandom.convert_seed_to_generator(1)
if IS_NP_117_OR_HIGHER:
expected = np.random.Generator(
iarandom.BIT_GENERATOR(np.random.SeedSequence(1)))
assert iarandom.is_generator_equal_to(gen, expected)
else:
expected = np.random.RandomState(1)
assert iarandom.is_generator_equal_to(gen, expected)
class Test_convert_seed_sequence_to_generator(_Base):
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"SeedSequence does not exist in numpy <=1.16")
def test_call(self):
seedseq = np.random.SeedSequence(1)
gen = iarandom.convert_seed_sequence_to_generator(seedseq)
expected = np.random.Generator(
iarandom.BIT_GENERATOR(np.random.SeedSequence(1)))
assert iarandom.is_generator_equal_to(gen, expected)
class Test_create_pseudo_random_generator_(_Base):
def test_call(self):
global_gen = copylib.deepcopy(iarandom.get_global_rng().generator)
gen = iarandom.create_pseudo_random_generator_()
expected = iarandom.convert_seed_to_generator(
iarandom.generate_seed_(global_gen))
assert iarandom.is_generator_equal_to(gen, expected)
class Test_create_fully_random_generator(_Base):
@mock.patch("imgaug.random._create_fully_random_generator_np117")
@mock.patch("imgaug.random._create_fully_random_generator_np116")
def test_mocked_call(self, mock_np116, mock_np117):
mock_np116.return_value = "np116"
mock_np117.return_value = "np117"
result = iarandom.create_fully_random_generator()
if IS_NP_117_OR_HIGHER:
assert result == "np117"
mock_np117.assert_called_once_with()
assert mock_np116.call_count == 0
else:
assert result == "np116"
mock_np116.assert_called_once_with()
assert mock_np117.call_count == 0
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_np117_mocked(self):
dummy_bitgen = np.random.SFC64(1)
with mock.patch("numpy.random.SFC64") as mock_bitgen:
mock_bitgen.return_value = dummy_bitgen
result = iarandom._create_fully_random_generator_np117()
assert mock_bitgen.call_count == 1
assert iarandom.is_generator_equal_to(
result, np.random.Generator(dummy_bitgen))
def test_np116_mocked(self):
dummy_rs = np.random.RandomState(1)
with mock.patch("numpy.random.RandomState") as mock_rs:
mock_rs.return_value = dummy_rs
result = iarandom._create_fully_random_generator_np116()
assert mock_rs.call_count == 1
assert iarandom.is_generator_equal_to(result, np.random.RandomState(1))
class Test_generate_seed_(_Base):
@mock.patch("imgaug.random.generate_seeds_")
def test_mocked_call(self, mock_seeds):
gen = iarandom.convert_seed_to_generator(0)
_ = iarandom.generate_seed_(gen)
mock_seeds.assert_called_once_with(gen, 1)
class Test_generate_seeds_(_Base):
@mock.patch("imgaug.random.polyfill_integers")
def test_mocked_call(self, mock_integers):
gen = iarandom.convert_seed_to_generator(0)
_ = iarandom.generate_seeds_(gen, 10)
mock_integers.assert_called_once_with(
gen, iarandom.SEED_MIN_VALUE, iarandom.SEED_MAX_VALUE, size=(10,))
def test_call(self):
gen = iarandom.convert_seed_to_generator(0)
seeds = iarandom.generate_seeds_(gen, 2)
assert len(seeds) == 2
assert ia.is_np_array(seeds)
assert seeds.dtype.name == "int32"
class Test_copy_generator(_Base):
@mock.patch("imgaug.random._copy_generator_np116")
def test_mocked_call_with_random_state(self, mock_np116):
mock_np116.return_value = "np116"
gen = np.random.RandomState(1)
gen_copy = iarandom.copy_generator(gen)
assert gen_copy == "np116"
mock_np116.assert_called_once_with(gen)
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
@mock.patch("imgaug.random._copy_generator_np117")
def test_mocked_call_with_generator(self, mock_np117):
mock_np117.return_value = "np117"
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen_copy = iarandom.copy_generator(gen)
assert gen_copy == "np117"
mock_np117.assert_called_once_with(gen)
def test_call_with_random_state(self):
gen = np.random.RandomState(1)
gen_copy = iarandom._copy_generator_np116(gen)
assert gen is not gen_copy
assert iarandom.is_generator_equal_to(gen, gen_copy)
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_call_with_generator(self):
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen_copy = iarandom._copy_generator_np117(gen)
assert gen is not gen_copy
assert iarandom.is_generator_equal_to(gen, gen_copy)
class Test_copy_generator_unless_global_generator(_Base):
@mock.patch("imgaug.random.get_global_rng")
@mock.patch("imgaug.random.copy_generator")
def test_mocked_gen_is_global(self, mock_copy, mock_get_global_rng):
gen = iarandom.convert_seed_to_generator(1)
mock_copy.return_value = "foo"
mock_get_global_rng.return_value = iarandom.RNG(gen)
result = iarandom.copy_generator_unless_global_generator(gen)
assert mock_get_global_rng.call_count == 1
assert mock_copy.call_count == 0
assert result is gen
@mock.patch("imgaug.random.get_global_rng")
@mock.patch("imgaug.random.copy_generator")
def test_mocked_gen_is_not_global(self, mock_copy, mock_get_global_rng):
gen1 = iarandom.convert_seed_to_generator(1)
gen2 = iarandom.convert_seed_to_generator(2)
mock_copy.return_value = "foo"
mock_get_global_rng.return_value = iarandom.RNG(gen2)
result = iarandom.copy_generator_unless_global_generator(gen1)
assert mock_get_global_rng.call_count == 1
mock_copy.assert_called_once_with(gen1)
assert result == "foo"
class Test_reset_generator_cache_(_Base):
@mock.patch("imgaug.random._reset_generator_cache_np117_")
@mock.patch("imgaug.random._reset_generator_cache_np116_")
def test_mocked_call(self, mock_np116, mock_np117):
mock_np116.return_value = "np116"
mock_np117.return_value = "np117"
gen = iarandom.convert_seed_to_generator(1)
result = iarandom.reset_generator_cache_(gen)
if IS_NP_117_OR_HIGHER:
assert result == "np117"
mock_np117.assert_called_once_with(gen)
assert mock_np116.call_count == 0
else:
assert result == "np116"
mock_np116.assert_called_once_with(gen)
assert mock_np117.call_count == 0
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_call_np117(self):
gen = iarandom.convert_seed_to_generator(1)
gen_without_cache_copy = copylib.deepcopy(gen)
state = iarandom._get_generator_state_np117(gen)
state["has_uint32"] = 1
gen_with_cache = copylib.deepcopy(gen)
iarandom.set_generator_state_(gen_with_cache, state)
gen_with_cache_copy = copylib.deepcopy(gen_with_cache)
gen_cache_reset = iarandom.reset_generator_cache_(gen_with_cache)
assert iarandom.is_generator_equal_to(gen_cache_reset,
gen_without_cache_copy)
assert not iarandom.is_generator_equal_to(gen_cache_reset,
gen_with_cache_copy)
def test_call_np116(self):
gen = np.random.RandomState(1)
gen_without_cache_copy = copylib.deepcopy(gen)
state = iarandom._get_generator_state_np116(gen)
state = list(state)
state[-2] = 1
gen_with_cache = copylib.deepcopy(gen)
iarandom.set_generator_state_(gen_with_cache, tuple(state))
gen_with_cache_copy = copylib.deepcopy(gen_with_cache)
gen_cache_reset = iarandom.reset_generator_cache_(gen_with_cache)
assert iarandom.is_generator_equal_to(gen_cache_reset,
gen_without_cache_copy)
assert not iarandom.is_generator_equal_to(gen_cache_reset,
gen_with_cache_copy)
class Test_derive_generator_(_Base):
@mock.patch("imgaug.random.derive_generators_")
def test_mocked_call(self, mock_derive_gens):
mock_derive_gens.return_value = ["foo"]
gen = iarandom.convert_seed_to_generator(1)
gen_derived = iarandom.derive_generator_(gen)
mock_derive_gens.assert_called_once_with(gen, n=1)
assert gen_derived == "foo"
def test_integration(self):
gen = iarandom.convert_seed_to_generator(1)
gen_copy = copylib.deepcopy(gen)
gen_derived = iarandom.derive_generator_(gen)
assert not iarandom.is_generator_equal_to(gen_derived, gen_copy)
# should have advanced the state
assert not iarandom.is_generator_equal_to(gen_copy, gen)
class Test_derive_generators_(_Base):
@mock.patch("imgaug.random._derive_generators_np117_")
@mock.patch("imgaug.random._derive_generators_np116_")
def test_mocked_call(self, mock_np116, mock_np117):
mock_np116.return_value = "np116"
mock_np117.return_value = "np117"
gen = iarandom.convert_seed_to_generator(1)
result = iarandom.derive_generators_(gen, 1)
if isinstance(gen, np.random.RandomState):
assert result == "np116"
mock_np116.assert_called_once_with(gen, n=1)
assert mock_np117.call_count == 0
else:
assert result == "np117"
mock_np117.assert_called_once_with(gen, n=1)
assert mock_np116.call_count == 0
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_call_np117(self):
gen = iarandom.convert_seed_to_generator(1)
gen_copy = copylib.deepcopy(gen)
result = iarandom.derive_generators_(gen, 2)
assert len(result) == 2
assert np.all([isinstance(gen, np.random.Generator)
for gen in result])
assert not iarandom.is_generator_equal_to(result[0], gen_copy)
assert not iarandom.is_generator_equal_to(result[1], gen_copy)
assert not iarandom.is_generator_equal_to(result[0], result[1])
# derive should advance state
assert not iarandom.is_generator_equal_to(gen, gen_copy)
def test_call_np116(self):
gen = np.random.RandomState(1)
gen_copy = copylib.deepcopy(gen)
result = iarandom.derive_generators_(gen, 2)
assert len(result) == 2
assert np.all([isinstance(gen, np.random.RandomState)
for gen in result])
assert not iarandom.is_generator_equal_to(result[0], gen_copy)
assert not iarandom.is_generator_equal_to(result[1], gen_copy)
assert not iarandom.is_generator_equal_to(result[0], result[1])
# derive should advance state
assert not iarandom.is_generator_equal_to(gen, gen_copy)
class Test_get_generator_state(_Base):
@mock.patch("imgaug.random._get_generator_state_np117")
@mock.patch("imgaug.random._get_generator_state_np116")
def test_mocked_call(self, mock_np116, mock_np117):
mock_np116.return_value = "np116"
mock_np117.return_value = "np117"
gen = iarandom.convert_seed_to_generator(1)
result = iarandom.get_generator_state(gen)
if isinstance(gen, np.random.RandomState):
assert result == "np116"
mock_np116.assert_called_once_with(gen)
assert mock_np117.call_count == 0
else:
assert result == "np117"
mock_np117.assert_called_once_with(gen)
assert mock_np116.call_count == 0
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_call_np117(self):
gen = iarandom.convert_seed_to_generator(1)
state = iarandom.get_generator_state(gen)
assert str(state) == str(gen.bit_generator.state)
def test_call_np116(self):
gen = np.random.RandomState(1)
state = iarandom.get_generator_state(gen)
assert str(state) == str(gen.get_state())
class Test_set_generator_state_(_Base):
@mock.patch("imgaug.random._set_generator_state_np117_")
@mock.patch("imgaug.random._set_generator_state_np116_")
def test_mocked_call(self, mock_np116, mock_np117):
gen = iarandom.convert_seed_to_generator(1)
state = {"state": 0}
iarandom.set_generator_state_(gen, state)
if isinstance(gen, np.random.RandomState):
mock_np116.assert_called_once_with(gen, state)
assert mock_np117.call_count == 0
else:
mock_np117.assert_called_once_with(gen, state)
assert mock_np116.call_count == 0
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_call_np117(self):
gen1 = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen2 = np.random.Generator(iarandom.BIT_GENERATOR(2))
gen1_copy = copylib.deepcopy(gen1)
gen2_copy = copylib.deepcopy(gen2)
iarandom._set_generator_state_np117_(
gen2, iarandom.get_generator_state(gen1))
assert iarandom.is_generator_equal_to(gen2, gen1)
assert iarandom.is_generator_equal_to(gen1, gen1_copy)
assert not iarandom.is_generator_equal_to(gen2, gen2_copy)
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_call_np117_via_samples(self):
gen1 = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen2 = np.random.Generator(iarandom.BIT_GENERATOR(2))
gen1_copy = copylib.deepcopy(gen1)
gen2_copy = copylib.deepcopy(gen2)
iarandom._set_generator_state_np117_(
gen2, iarandom.get_generator_state(gen1))
samples1 = gen1.random(size=(100,))
samples2 = gen2.random(size=(100,))
samples1_copy = gen1_copy.random(size=(100,))
samples2_copy = gen2_copy.random(size=(100,))
assert np.allclose(samples1, samples2)
assert np.allclose(samples1, samples1_copy)
assert not np.allclose(samples2, samples2_copy)
def test_call_np116(self):
gen1 = np.random.RandomState(1)
gen2 = np.random.RandomState(2)
gen1_copy = copylib.deepcopy(gen1)
gen2_copy = copylib.deepcopy(gen2)
iarandom._set_generator_state_np116_(
gen2, iarandom.get_generator_state(gen1))
assert iarandom.is_generator_equal_to(gen2, gen1)
assert iarandom.is_generator_equal_to(gen1, gen1_copy)
assert not iarandom.is_generator_equal_to(gen2, gen2_copy)
def test_call_np116_via_samples(self):
gen1 = np.random.RandomState(1)
gen2 = np.random.RandomState(2)
gen1_copy = copylib.deepcopy(gen1)
gen2_copy = copylib.deepcopy(gen2)
iarandom._set_generator_state_np116_(
gen2, iarandom.get_generator_state(gen1))
samples1 = gen1.uniform(0.0, 1.0, size=(100,))
samples2 = gen2.uniform(0.0, 1.0, size=(100,))
samples1_copy = gen1_copy.uniform(0.0, 1.0, size=(100,))
samples2_copy = gen2_copy.uniform(0.0, 1.0, size=(100,))
assert np.allclose(samples1, samples2)
assert np.allclose(samples1, samples1_copy)
assert not np.allclose(samples2, samples2_copy)
class Test_is_generator_equal_to(_Base):
@mock.patch("imgaug.random._is_generator_equal_to_np117")
@mock.patch("imgaug.random._is_generator_equal_to_np116")
def test_mocked_call(self, mock_np116, mock_np117):
mock_np116.return_value = "np116"
mock_np117.return_value = "np117"
gen = iarandom.convert_seed_to_generator(1)
result = iarandom.is_generator_equal_to(gen, gen)
if isinstance(gen, np.random.RandomState):
assert result == "np116"
mock_np116.assert_called_once_with(gen, gen)
assert mock_np117.call_count == 0
else:
assert result == "np117"
mock_np117.assert_called_once_with(gen, gen)
assert mock_np116.call_count == 0
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_generator_is_identical_np117(self):
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
result = iarandom._is_generator_equal_to_np117(gen, gen)
assert result is True
def test_generator_is_identical_np116(self):
gen = np.random.RandomState(1)
result = iarandom._is_generator_equal_to_np116(gen, gen)
assert result is True
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_generator_created_with_same_seed_np117(self):
gen1 = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen2 = np.random.Generator(iarandom.BIT_GENERATOR(1))
result = iarandom._is_generator_equal_to_np117(gen1, gen2)
assert result is True
def test_generator_created_with_same_seed_np116(self):
gen1 = np.random.RandomState(1)
gen2 = np.random.RandomState(1)
result = iarandom._is_generator_equal_to_np116(gen1, gen2)
assert result is True
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_generator_is_copy_of_itself_np117(self):
gen1 = np.random.Generator(iarandom.BIT_GENERATOR(1))
result = iarandom._is_generator_equal_to_np117(gen1,
copylib.deepcopy(gen1))
assert result is True
def test_generator_is_copy_of_itself_np116(self):
gen1 = np.random.RandomState(1)
result = iarandom._is_generator_equal_to_np116(gen1,
copylib.deepcopy(gen1))
assert result is True
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_generator_created_with_different_seed_np117(self):
gen1 = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen2 = np.random.Generator(iarandom.BIT_GENERATOR(2))
result = iarandom._is_generator_equal_to_np117(gen1, gen2)
assert result is False
def test_generator_created_with_different_seed_np116(self):
gen1 = np.random.RandomState(1)
gen2 = np.random.RandomState(2)
result = iarandom._is_generator_equal_to_np116(gen1, gen2)
assert result is False
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_generator_modified_to_have_same_state_np117(self):
gen1 = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen2 = np.random.Generator(iarandom.BIT_GENERATOR(2))
iarandom.set_generator_state_(gen2, iarandom.get_generator_state(gen1))
result = iarandom._is_generator_equal_to_np117(gen1, gen2)
assert result is True
def test_generator_modified_to_have_same_state_np116(self):
gen1 = np.random.RandomState(1)
gen2 = np.random.RandomState(2)
iarandom.set_generator_state_(gen2, iarandom.get_generator_state(gen1))
result = iarandom._is_generator_equal_to_np116(gen1, gen2)
assert result is True
class Test_advance_generator_(_Base):
@mock.patch("imgaug.random._advance_generator_np117_")
@mock.patch("imgaug.random._advance_generator_np116_")
def test_mocked_call(self, mock_np116, mock_np117):
gen = iarandom.convert_seed_to_generator(1)
iarandom.advance_generator_(gen)
if isinstance(gen, np.random.RandomState):
mock_np116.assert_called_once_with(gen)
assert mock_np117.call_count == 0
else:
mock_np117.assert_called_once_with(gen)
assert mock_np116.call_count == 0
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_call_np117(self):
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen_copy1 = copylib.deepcopy(gen)
iarandom._advance_generator_np117_(gen)
gen_copy2 = copylib.deepcopy(gen)
iarandom._advance_generator_np117_(gen)
assert iarandom.is_generator_equal_to(gen, copylib.deepcopy(gen))
assert not iarandom.is_generator_equal_to(gen_copy1, gen_copy2)
assert not iarandom.is_generator_equal_to(gen_copy2, gen)
assert not iarandom.is_generator_equal_to(gen_copy1, gen)
def test_call_np116(self):
gen = np.random.RandomState(1)
gen_copy1 = copylib.deepcopy(gen)
iarandom._advance_generator_np116_(gen)
gen_copy2 = copylib.deepcopy(gen)
iarandom._advance_generator_np116_(gen)
assert iarandom.is_generator_equal_to(gen, copylib.deepcopy(gen))
assert not iarandom.is_generator_equal_to(gen_copy1, gen_copy2)
assert not iarandom.is_generator_equal_to(gen_copy2, gen)
assert not iarandom.is_generator_equal_to(gen_copy1, gen)
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_samples_different_after_advance_np117(self):
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
gen_copy1 = copylib.deepcopy(gen)
# first advance
iarandom._advance_generator_np117_(gen)
gen_copy2 = copylib.deepcopy(gen)
# second advance
iarandom._advance_generator_np117_(gen)
sample_before = gen_copy1.uniform(0.0, 1.0)
sample_after = gen_copy2.uniform(0.0, 1.0)
sample_after_after = gen.uniform(0.0, 1.0)
assert not np.isclose(sample_after, sample_before, rtol=0)
assert not np.isclose(sample_after_after, sample_after, rtol=0)
assert not np.isclose(sample_after_after, sample_before, rtol=0)
def test_samples_different_after_advance_np116(self):
gen = np.random.RandomState(1)
gen_copy1 = copylib.deepcopy(gen)
# first advance
iarandom._advance_generator_np116_(gen)
gen_copy2 = copylib.deepcopy(gen)
# second advance
iarandom._advance_generator_np116_(gen)
sample_before = gen_copy1.uniform(0.0, 1.0)
sample_after = gen_copy2.uniform(0.0, 1.0)
sample_after_after = gen.uniform(0.0, 1.0)
assert not np.isclose(sample_after, sample_before, rtol=0)
assert not np.isclose(sample_after_after, sample_after, rtol=0)
assert not np.isclose(sample_after_after, sample_before, rtol=0)
class Test_polyfill_integers(_Base):
def test_mocked_standard_call_np116(self):
def side_effect(low, high=None, size=None, dtype='l'):
return "np116"
gen = mock.MagicMock()
gen.randint.side_effect = side_effect
result = iarandom.polyfill_integers(gen, 2, 2000, size=(10,),
dtype="int8")
assert result == "np116"
gen.randint.assert_called_once_with(low=2, high=2000, size=(10,),
dtype="int8")
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_mocked_standard_call_np117(self):
def side_effect(low, high=None, size=None, dtype='int64',
endpoint=False):
return "np117"
gen = mock.MagicMock()
gen.integers.side_effect = side_effect
del gen.randint
result = iarandom.polyfill_integers(gen, 2, 2000, size=(10,),
dtype="int8", endpoint=True)
assert result == "np117"
gen.integers.assert_called_once_with(low=2, high=2000, size=(10,),
dtype="int8", endpoint=True)
def test_mocked_call_with_default_values_np116(self):
def side_effect(low, high=None, size=None, dtype='l'):
return "np116"
gen = mock.MagicMock()
gen.randint.side_effect = side_effect
result = iarandom.polyfill_integers(gen, 2)
assert result == "np116"
gen.randint.assert_called_once_with(low=2, high=None, size=None,
dtype="int32")
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_mocked_call_with_default_values_np117(self):
def side_effect(low, high=None, size=None, dtype='int64',
endpoint=False):
return "np117"
gen = mock.MagicMock()
gen.integers.side_effect = side_effect
del gen.randint
result = iarandom.polyfill_integers(gen, 2)
assert result == "np117"
gen.integers.assert_called_once_with(low=2, high=None, size=None,
dtype="int32", endpoint=False)
def test_mocked_call_with_default_values_and_endpoint_np116(self):
def side_effect(low, high=None, size=None, dtype='l'):
return "np116"
gen = mock.MagicMock()
gen.randint.side_effect = side_effect
result = iarandom.polyfill_integers(gen, 2, endpoint=True)
assert result == "np116"
gen.randint.assert_called_once_with(low=0, high=3, size=None,
dtype="int32")
def test_mocked_call_with_low_high_and_endpoint_np116(self):
def side_effect(low, high=None, size=None, dtype='l'):
return "np116"
gen = mock.MagicMock()
gen.randint.side_effect = side_effect
result = iarandom.polyfill_integers(gen, 2, 5, endpoint=True)
assert result == "np116"
gen.randint.assert_called_once_with(low=2, high=6, size=None,
dtype="int32")
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_sampled_values_np117(self):
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
result = iarandom.polyfill_integers(gen, 1, 10, size=(1000,),
endpoint=False)
assert result.dtype.name == "int32"
assert np.all(result >= 1)
assert np.all(result < 10)
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_sampled_values_with_endpoint_np117(self):
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
result = iarandom.polyfill_integers(gen, 1, 10, size=(1000,),
endpoint=True)
assert result.dtype.name == "int32"
assert np.all(result >= 1)
assert np.all(result <= 10)
def test_sampled_values_np116(self):
gen = np.random.RandomState(1)
result = iarandom.polyfill_integers(gen, 1, 10, size=(1000,),
endpoint=False)
assert result.dtype.name == "int32"
assert np.all(result >= 1)
assert np.all(result < 10)
def test_sampled_values_with_endpoint_np116(self):
gen = np.random.RandomState(1)
result = iarandom.polyfill_integers(gen, 1, 10, size=(1000,),
endpoint=True)
assert result.dtype.name == "int32"
assert np.all(result >= 1)
assert np.all(result <= 10)
class Test_polyfill_random(_Base):
def test_mocked_standard_call_np116(self):
def side_effect(size=None):
return np.zeros((1,), dtype="float64")
gen = mock.MagicMock()
gen.random_sample.side_effect = side_effect
result = iarandom.polyfill_random(gen, size=(10,), dtype="float16")
assert result.dtype.name == "float16"
gen.random_sample.assert_called_once_with(
size=(10,))
def test_mocked_standard_call_np117(self):
def side_effect(size=None, dtype='d', out=None):
return "np117"
gen = mock.MagicMock()
gen.random.side_effect = side_effect
del gen.random_sample
result = iarandom.polyfill_random(gen, size=(10,), dtype="float16")
assert result == "np117"
gen.random.assert_called_once_with(
size=(10,), dtype="float16", out=None)
def test_mocked_call_with_out_arg_np116(self):
def side_effect(size=None):
return np.zeros((1,), dtype="float64")
gen = mock.MagicMock()
gen.random_sample.side_effect = side_effect
out = np.empty((10,), dtype="float16")
result = iarandom.polyfill_random(gen, size=(10,), dtype="float16",
out=out)
assert result.dtype.name == "float16"
# np1.16 does not support an out arg, hence it is not part of the
# expected call
gen.random_sample.assert_called_once_with(size=(10,))
def test_mocked_call_with_out_arg_np117(self):
def side_effect(size=None, dtype='d', out=None):
return "np117"
gen = mock.MagicMock()
gen.random.side_effect = side_effect
del gen.random_sample
out = np.empty((10,), dtype="float16")
result = iarandom.polyfill_random(gen, size=(10,), dtype="float16",
out=out)
assert result == "np117"
gen.random.assert_called_once_with(size=(10,), dtype="float16",
out=out)
def test_sampled_values_np116(self):
gen = np.random.RandomState(1)
result = iarandom.polyfill_random(gen, size=(1000,))
assert result.dtype.name == "float32"
assert np.all(result >= 0)
assert np.all(result < 1.0)
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_sampled_values_np117(self):
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
result = iarandom.polyfill_random(gen, size=(1000,))
assert result.dtype.name == "float32"
assert np.all(result >= 0)
assert np.all(result < 1.0)
def test_sampled_values_with_out_arg_np116(self):
gen = np.random.RandomState(1)
out = np.zeros((1000,), dtype="float32")
result = iarandom.polyfill_random(gen, size=(1000,), out=out)
assert result.dtype.name == "float32"
assert np.all(result >= 0)
assert np.all(result < 1.0)
assert np.any(out > 0.9)
assert np.all(out >= 0)
assert np.all(out < 1.0)
@unittest.skipIf(not IS_NP_117_OR_HIGHER,
"Function uses classes from numpy 1.17+")
def test_sampled_values_with_out_arg_np117(self):
gen = np.random.Generator(iarandom.BIT_GENERATOR(1))
out = np.zeros((1000,), dtype="float32")
result = iarandom.polyfill_random(gen, size=(1000,), out=out)
assert result.dtype.name == "float32"
assert np.all(result >= 0)
assert np.all(result < 1.0)
assert np.any(out > 0.9)
assert np.all(out >= 0)
assert np.all(out < 1.0)