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. [![Build Status](https://travis-ci.org/aleju/imgaug.svg?branch=master)](https://travis-ci.org/aleju/imgaug) [![codecov](https://codecov.io/gh/aleju/imgaug/branch/master/graph/badge.svg)](https://codecov.io/gh/aleju/imgaug) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/1370ce38e99e40af842d47a8dd721444)](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 input images input heatmaps input segmentation maps input keypoints input bounding boxes
Gauss. Noise
+ Contrast
+ Sharpen
non geometric augmentations, applied to images non geometric augmentations, applied to heatmaps non geometric augmentations, applied to segmentation maps non geometric augmentations, applied to keypoints non geometric augmentations, applied to bounding boxes
Affine affine augmentations, applied to images affine augmentations, applied to heatmaps affine augmentations, applied to segmentation maps affine augmentations, applied to keypoints affine augmentations, applied to bounding boxes
Crop
+ Pad
crop and pad augmentations, applied to images crop and pad augmentations, applied to heatmaps crop and pad augmentations, applied to segmentation maps crop and pad augmentations, applied to keypoints crop and pad augmentations, applied to bounding boxes
Fliplr
+ Perspective
Horizontal flip and perspective transform augmentations, applied to images Horizontal flip and perspective transform augmentations, applied to heatmaps Horizontal flip and perspective transform augmentations, applied to segmentation maps Horizontal flip and perspective transform augmentations, applied to keypoints Horizontal flip and perspective transform augmentations, applied to bounding boxes
**More (strong) example augmentations of one input image:** ![64 quokkas](https://raw.githubusercontent.com/aleju/imgaug-doc/master/readme_images/examples_grid.jpg?raw=true "64 quokkas") ## 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      
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
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
Cutout Dropout CoarseDropout p=0.2 CoarseDropout p=0.2, per_channel=True Dropout2d
SaltAndPepper CoarseSaltAndPepper
(p=0.2)
Invert Solarize JpegCompression
SaltAndPepper CoarseSaltAndPepper p=0.2 Invert Solarize JpegCompression
See also: AddElementwise, AdditiveLaplaceNoise, AdditivePoissonNoise, MultiplyElementwise, TotalDropout, ReplaceElementwise, ImpulseNoise, Salt, Pepper, CoarseSalt, CoarsePepper, Solarize
artistic
Cartoon        
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))
BlendAlpha with EdgeDetect1.0 BlendAlphaSimplexNoise with EdgeDetect1.0 BlendAlphaFrequencyNoise with EdgeDetect1.0 BlendAlphaSomeColors with RemoveSaturation1.0 BlendAlphaRegularGrid with Multiply0.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)
GaussianBlur AverageBlur MedianBlur BilateralBlur sigma_color=250, sigma_space=250 MotionBlur angle=0
MotionBlur
(k=5)
MeanShiftBlur      
MotionBlur k=5 MeanShiftBlur      
collections
RandAugment        
RandAugment        
color
MultiplyAndAddToBrightness MultiplyHueAndSaturation MultiplyHue MultiplySaturation AddToHueAndSaturation
MultiplyAndAddToBrightness MultiplyHueAndSaturation MultiplyHue MultiplySaturation AddToHueAndSaturation
Grayscale RemoveSaturation ChangeColorTemperature KMeansColorQuantization
(to_colorspace=RGB)
UniformColorQuantization
(to_colorspace=RGB)
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
GammaContrast GammaContrast per_channel=True SigmoidContrast cutoff=0.5 SigmoidContrast gain=10 LogContrast
LinearContrast AllChannels-
HistogramEqualization
HistogramEqualization AllChannelsCLAHE CLAHE
LinearContrast AllChannels- HistogramEqualization HistogramEqualization AllChannelsCLAHE CLAHE
See also: Equalize
convolutional
Sharpen
(alpha=1)
Emboss
(alpha=1)
EdgeDetect DirectedEdgeDetect
(alpha=1)
 
Sharpen alpha=1 Emboss alpha=1 EdgeDetect DirectedEdgeDetect alpha=1  
See also: Convolve
debug
See also: SaveDebugImageEveryNBatches
edges
Canny        
Canny        
flip
Fliplr Flipud  
Fliplr Flipud  
See also: HorizontalFlip, VerticalFlip
geometric
Affine Affine: Modes  
Affine Affine: Modes  
Affine: cval PiecewiseAffine  
Affine: cval PiecewiseAffine  
PerspectiveTransform ElasticTransformation
(sigma=1.0)
 
PerspectiveTransform ElasticTransformation sigma=1.0  
ElasticTransformation
(sigma=4.0)
Rot90  
ElasticTransformation sigma=4.0 Rot90  
WithPolarWarping
+Affine
Jigsaw
(5x5 grid)
 
WithPolarWarping +Affine Jigsaw 5x5 grid  
See also: ScaleX, ScaleY, TranslateX, TranslateY, Rotate
imgcorruptlike
GlassBlur DefocusBlur ZoomBlur Snow Spatter
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
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  
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)
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)
       
RegularGridVoronoi: p_replace n_rows=n_cols=16        
See also: Voronoi, RelativeRegularGridVoronoi, RegularGridPointsSampler, RelativeRegularGridPointsSampler, DropoutPointsSampler, UniformPointsSampler, SubsamplingPointsSampler
size
CropAndPad Crop  
CropAndPad Crop  
Pad PadToFixedSize
(height'=height+32,
width'=width+32)
 
Pad PadToFixedSize height'=height+32, width'=width+32  
CropToFixedSize
(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
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)