├── .github └── workflows │ ├── flip-deploy.yml │ └── flip_ci.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── images ├── reference.exr ├── reference.png ├── teaser.png ├── test.exr └── test.png ├── misc ├── CLA.md ├── FLIP.txt ├── HDRFLIP.txt ├── LDRFLIP.txt ├── LICENSE-third-party.md ├── papersUsingFLIP.md ├── precision.md ├── separatedConvolutions.pdf └── versionList.md ├── pyproject.toml └── src ├── CMakeLists.txt ├── cmake └── FLIPConfig.cmake ├── cpp ├── CMakeLists.txt ├── FLIP.h ├── FLIP.sln ├── README.md └── tool │ ├── CMakeLists.txt │ ├── CPP.vcxproj │ ├── CPP.vcxproj.filters │ ├── CUDA.vcxproj │ ├── CUDA.vcxproj.filters │ ├── FLIP-tool.cpp │ ├── FLIP-tool.cu │ ├── FLIPToolHelpers.h │ ├── commandline.h │ ├── filename.h │ ├── imagehelpers.h │ ├── pooling.h │ ├── stb_image.h │ ├── stb_image_write.h │ └── tinyexr.h ├── flip_evaluator ├── __init__.py └── flip_python_api.py ├── nanobindFLIP.cpp ├── python ├── README.md └── api_example.py ├── pytorch ├── README.md ├── data.py ├── flip_loss.py └── train.py └── tests ├── correct_hdrflip_cpp.png ├── correct_hdrflip_cuda.png ├── correct_ldrflip_cpp.png ├── correct_ldrflip_cuda.png ├── test.py └── test_pytorch.py /.github/workflows/flip-deploy.yml: -------------------------------------------------------------------------------- 1 | # Much of the code retrieved from Mitsuba3 (https://github.com/mitsuba-renderer/mitsuba3/blob/master/.github/workflows/wheels.yml.) 2 | 3 | name: Build wheels 4 | 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | release: 12 | types: 13 | - published 14 | 15 | jobs: 16 | build_wheels: 17 | strategy: 18 | matrix: 19 | # macos-13 is an intel runner, macos-14 is apple silicon 20 | os: [ubuntu-latest, windows-latest, macos-13, macos-14] 21 | python: [cp38, cp39, cp310, cp311, cp312, cp312_stable, cp313] 22 | exclude: 23 | # The first Python version to target Apple arm64 architectures is 3.9. 24 | - os: macos-14 25 | python: cp38 26 | name: > 27 | ${{ matrix.python }} wheel for ${{ matrix.os }} 28 | ${{ (endsWith(matrix.python, '_stable') && '(stable ABI)') || '' }} 29 | runs-on: ${{ matrix.os }} 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | 34 | - uses: actions/setup-python@v4 35 | name: Install Python 36 | with: 37 | python-version: '3.8' 38 | 39 | - name: Install cibuildwheel 40 | run: | 41 | python -m pip install cibuildwheel==2.20.0 42 | 43 | ######################### 44 | # Build and store wheels 45 | ######################### 46 | - name: Build wheel 47 | run: | 48 | python -m cibuildwheel --output-dir wheelhouse 49 | 50 | - uses: actions/upload-artifact@v3 51 | with: 52 | name: wheels 53 | path: ./wheelhouse/*.whl 54 | 55 | build_sdist: 56 | name: Build source distribution 57 | runs-on: ubuntu-latest 58 | steps: 59 | - uses: actions/checkout@v4 60 | 61 | - name: Build sdist 62 | run: pipx run build --sdist 63 | 64 | - uses: actions/upload-artifact@v4 65 | with: 66 | name: cibw-sdist 67 | path: dist/*.tar.gz -------------------------------------------------------------------------------- /.github/workflows/flip_ci.yml: -------------------------------------------------------------------------------- 1 | name: FLIP CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | matrix: 14 | os: [ubuntu-latest, windows-latest, macos-latest] 15 | config: [Release, Debug] 16 | 17 | steps: 18 | - name: Checkout Sources 19 | uses: actions/checkout@v3 20 | 21 | - name: Install CUDA 22 | if: ${{ matrix.os == 'ubuntu-latest' }} 23 | run: sudo apt update && sudo apt install -y nvidia-cuda-toolkit g++-10 24 | 25 | - name: Configure CMake 26 | run: > 27 | cmake -LA -B ${{github.workspace}}/src/build -S ${{github.workspace}}/src/ 28 | -DCMAKE_BUILD_TYPE=${{ matrix.config }} 29 | -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/src/build/install 30 | -DCMAKE_CUDA_ARCHITECTURES=all 31 | -DCMAKE_CUDA_HOST_COMPILER=g++-10 32 | -DFLIP_ENABLE_CUDA=${{ matrix.os == 'ubuntu-latest' }} 33 | 34 | - name: Build 35 | run: cmake --build ${{github.workspace}}/src/build --config ${{ matrix.config }} --target install 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore thumbnails created by Windows 2 | Thumbs.db 3 | 4 | # Ignore files built by Visual Studio 5 | *.user 6 | *.aps 7 | *.pch 8 | *.vspscc 9 | *_i.c 10 | *_p.c 11 | *.ncb 12 | *.suo 13 | *.bak 14 | *.cache 15 | *.ilk 16 | *.log 17 | #[Bb]in 18 | [Dd]ebug*/ 19 | *.sbr 20 | obj/ 21 | [Rr]elease*/ 22 | _ReSharper*/ 23 | *.VC.VC.opendb 24 | *.VC.db 25 | .vs/ 26 | 27 | # Matlab 28 | *.asv 29 | 30 | # Emacs ignores 31 | *~ 32 | 33 | # Python ignores 34 | *__pycache__* 35 | dist/ 36 | *.egg-info/ 37 | 38 | # LaTeX ignores 39 | *.synctex.gz 40 | *.out 41 | *.aux 42 | *.bbl 43 | *.blg 44 | 45 | # VSCode ignores 46 | *.vscode 47 | 48 | # Ignore build 49 | build/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | cmake_minimum_required(VERSION 3.15...3.27) 34 | project(flip_evaluator LANGUAGES CXX) 35 | 36 | # Warn if the user invokes CMake directly 37 | if (NOT SKBUILD) 38 | message(WARNING "\ 39 | This CMake file is meant to be executed using 'scikit-build-core'. 40 | Running it directly will almost certainly not produce the desired 41 | result. If you are a user trying to install this package, use the 42 | command below, which will install all necessary build dependencies, 43 | compile the package in an isolated environment, and then install it. 44 | ===================================================================== 45 | $ pip install . 46 | ===================================================================== 47 | If you are a software developer, and this is your own package, then 48 | it is usually much more efficient to install the build dependencies 49 | in your environment once and use the following command that avoids 50 | a costly creation of a new virtual environment at every compilation: 51 | ===================================================================== 52 | $ pip install nanobind scikit-build-core[pyproject] 53 | $ pip install --no-build-isolation -ve . 54 | ===================================================================== 55 | You may optionally add -Ceditable.rebuild=true to auto-rebuild when 56 | the package is imported. Otherwise, you need to rerun the above 57 | after editing C++ files.") 58 | endif() 59 | 60 | if (CMAKE_VERSION VERSION_LESS 3.18) 61 | set(DEV_MODULE Development) 62 | else() 63 | set(DEV_MODULE Development.Module) 64 | endif() 65 | 66 | if(MSVC) 67 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -openmp") 68 | elseif((NOT APPLE) AND UNIX) 69 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") 70 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgomp") 71 | endif() 72 | 73 | find_package(Python 3.8 74 | REQUIRED COMPONENTS Interpreter Development.Module 75 | OPTIONAL_COMPONENTS Development.SABIModule) 76 | 77 | if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 78 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) 79 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") 80 | endif() 81 | 82 | execute_process( 83 | COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir 84 | OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) 85 | find_package(nanobind CONFIG REQUIRED) 86 | 87 | nanobind_add_module(nbflip STABLE_ABI src/nanobindFLIP.cpp) 88 | 89 | install(TARGETS nbflip LIBRARY DESTINATION flip_evaluator) 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 31 | SPDX-License-Identifier: BSD-3-Clause -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Teaser image](images/teaser.png "Teaser image") 2 | 3 | # FLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.6) 4 | 5 | By 6 | [Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin) 7 | and 8 | [Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), 9 | with 10 | Jim Nilsson, 11 | [Magnus Oskarsson](https://www1.maths.lth.se/matematiklth/personal/magnuso/), 12 | [Kalle Åström](https://www.maths.lu.se/staff/kalleastrom/), 13 | [Mark D. Fairchild](https://www.rit.edu/directory/mdfpph-mark-fairchild), 14 | and 15 | [Peter Shirley](https://research.nvidia.com/person/peter-shirley). 16 | 17 | This repository holds implementations of the [LDR-FLIP](https://research.nvidia.com/publication/2020-07_FLIP) 18 | and [HDR-FLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics. 19 | It also holds code for the FLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). 20 | 21 | The changes made for the different versions of FLIP are summarized in the [version list](https://github.com/NVlabs/flip/blob/main/misc/versionList.md). 22 | 23 | [A list of papers](https://github.com/NVlabs/flip/blob/main/misc/papersUsingFLIP.md) that use/cite FLIP. 24 | 25 | [A note](https://github.com/NVlabs/flip/blob/main/misc/precision.md) about the precision of FLIP. 26 | 27 | [An image gallery](https://research.nvidia.com/node/3525) displaying a large quantity of reference/test images and corresponding error maps from 28 | different metrics. 29 | 30 | **Note**: since v1.6, the Python version of FLIP can now be installed via `pip install flip-evaluator`. 31 | 32 | **Note**: in v1.3, we switched to a *single header* ([FLIP.h](src/cpp/FLIP.h)) for C++/CUDA for easier integration. 33 | 34 | # License 35 | 36 | Copyright © 2020-2025, NVIDIA Corporation & Affiliates. All rights reserved. 37 | 38 | This work is made available under a [BSD 3-Clause License](LICENSE). 39 | 40 | The repository distributes code for `tinyexr`, which is subject to a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#bsd-3-clause-license),
41 | and `stb_image`, which is subject to an [MIT License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#mit-license). 42 | 43 | For individual contributions to the project, please confer the [Individual Contributor License Agreement](https://github.com/NVlabs/flip/blob/main/misc/CLA.md). 44 | 45 | For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). 46 | 47 | # Simplest Way To Get Started 48 | The simplest way to run FLIP to compare a test image `testImage.png` to a reference image `referenceImage.png` is as follows: 49 | ``` 50 | pip install flip-evaluator 51 | flip -r referenceImage.png -t testImage.png 52 | ``` 53 | For more information about the tool's capabilities, try running `flip -h`. 54 | 55 | If you wish to use FLIP in your Python or C++ evaluation scripts, please read the next sections. 56 | 57 | # Python (API and Tool) 58 | **Setup** (with pip): 59 | ``` 60 | pip install flip-evaluator 61 | ``` 62 | 63 | **Usage:**
64 | 65 | API:
66 | See the example script `src/python/api_example.py`. 67 | 68 | Tool: 69 | ``` 70 | flip --reference reference.{exr|png} --test test.{exr|png} [--options] 71 | ``` 72 | 73 | See the [README](https://github.com/NVlabs/flip/blob/main/src/python/README.md) in the `python` folder and run `flip -h` for further information and usage instructions. 74 | 75 | # C++ and CUDA (API and Tool) 76 | **Setup:** 77 | 78 | The `src/cpp/FLIP.sln` solution contains one CUDA backend project and one pure C++ backend project. 79 | 80 | Compiling the CUDA project requires a CUDA compatible GPU. Instruction on how to install CUDA can be found [here](https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html). 81 | 82 | Alternatively, a CMake build can be done by creating a build directory in the `src` directory and invoking CMake on the source `cpp` directory (add `--config Release` to build release configuration on Windows): 83 | 84 | ``` 85 | cd src 86 | mkdir build 87 | cd build 88 | cmake .. 89 | cmake --build . [--config Release] 90 | ``` 91 | 92 | CUDA support is enabled via the `FLIP_ENABLE_CUDA`, which can be passed to CMake on the command line with 93 | `-DFLIP_ENABLE_CUDA=ON` or set interactively with `ccmake` or `cmake-gui`. 94 | `FLIP_LIBRARY` option allows to output a library rather than an executable. 95 | 96 | **Usage:**
97 | 98 | API:
99 | See the [README](https://github.com/NVlabs/flip/blob/main/src/cpp/README.md). 100 | 101 | Tool: 102 | ``` 103 | flip[-cuda].exe --reference reference.{exr|png} --test test.{exr|png} [options] 104 | ``` 105 | 106 | See the [README](https://github.com/NVlabs/flip/blob/main/src/cpp/README.md) in the `src/cpp` folder and run `flip[-cuda].exe -h` for further information and usage instructions. 107 | 108 | # PyTorch (Loss Function) 109 | **Setup** (with Anaconda3 or Miniconda): 110 | ``` 111 | conda create -n flip_dl python numpy matplotlib 112 | conda activate flip_dl 113 | conda install pytorch torchvision torchaudio cudatoolkit=11.1 -c pytorch -c conda-forge 114 | conda install -c conda-forge openexr-python 115 | ``` 116 | 117 | **Usage:** 118 | 119 | *Remember to activate the* `flip_dl` *environment through* `conda activate flip_dl` *before using the loss function.* 120 | 121 | LDR- and HDR-FLIP are implemented as loss modules in `src/pytorch/flip_loss.py`. An example where the loss function is used to train a simple autoencoder is provided in `src/pytorch/train.py`. 122 | 123 | See the [README](https://github.com/NVlabs/flip/blob/main/src/pytorch/README.md) in the `pytorch` folder for further information and usage instructions. 124 | 125 | # Citation 126 | If your work uses the FLIP tool to find the errors between *low dynamic range* images, 127 | please cite the LDR-FLIP paper:
128 | [Paper](https://research.nvidia.com/publication/2020-07_FLIP) | [BibTeX](https://github.com/NVlabs/flip/blob/main/misc/LDRFLIP.txt) 129 | 130 | If it uses the FLIP tool to find the errors between *high dynamic range* images, 131 | instead cite the HDR-FLIP paper:
132 | [Paper](https://research.nvidia.com/publication/2021-05_HDR-FLIP) | [BibTeX](https://github.com/NVlabs/flip/blob/main/misc/HDRFLIP.txt) 133 | 134 | Should your work use the FLIP tool in a more general fashion, please cite the Ray Tracing Gems II article:
135 | [Chapter](https://link.springer.com/chapter/10.1007%2F978-1-4842-7185-8_19) | [BibTeX](https://github.com/NVlabs/flip/blob/main/misc/FLIP.txt) 136 | 137 | # Acknowledgements 138 | We appreciate the following peoples' contributions to this repository: 139 | Jonathan Granskog, Jacob Munkberg, Jon Hasselgren, Jefferson Amstutz, Alan Wolfe, Killian Herveau, Vinh Truong, Philippe Dagobert, Hannes Hergeth, Matt Pharr, Tizian Zeltner, Jan Honsbrok, Chris Zhang, and Wenzel Jakob. 140 | -------------------------------------------------------------------------------- /images/reference.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/images/reference.exr -------------------------------------------------------------------------------- /images/reference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/images/reference.png -------------------------------------------------------------------------------- /images/teaser.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/images/teaser.png -------------------------------------------------------------------------------- /images/test.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/images/test.exr -------------------------------------------------------------------------------- /images/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/images/test.png -------------------------------------------------------------------------------- /misc/CLA.md: -------------------------------------------------------------------------------- 1 | ## Individual Contributor License Agreement (CLA) 2 | 3 | **Thank you for submitting your contributions to this project.** 4 | 5 | By signing this CLA, you agree that the following terms apply to all of your past, present and future contributions 6 | to the project. 7 | 8 | ### License. 9 | 10 | You hereby represent that all present, past and future contributions are governed by the 11 | [MIT License](https://opensource.org/licenses/MIT) 12 | copyright statement. 13 | 14 | This entails that to the extent possible under law, you transfer all copyright and related or neighboring rights 15 | of the code or documents you contribute to the project itself or its maintainers. 16 | Furthermore you also represent that you have the authority to perform the above waiver 17 | with respect to the entirety of you contributions. 18 | 19 | ### Moral Rights. 20 | 21 | To the fullest extent permitted under applicable law, you hereby waive, and agree not to 22 | assert, all of your “moral rights” in or relating to your contributions for the benefit of the project. 23 | 24 | ### Third Party Content. 25 | 26 | If your Contribution includes or is based on any source code, object code, bug fixes, configuration changes, tools, 27 | specifications, documentation, data, materials, feedback, information or other works of authorship that were not 28 | authored by you (“Third Party Content”) or if you are aware of any third party intellectual property or proprietary 29 | rights associated with your Contribution (“Third Party Rights”), 30 | then you agree to include with the submission of your Contribution full details respecting such Third Party 31 | Content and Third Party Rights, including, without limitation, identification of which aspects of your 32 | Contribution contain Third Party Content or are associated with Third Party Rights, the owner/author of the 33 | Third Party Content and Third Party Rights, where you obtained the Third Party Content, and any applicable 34 | third party license terms or restrictions respecting the Third Party Content and Third Party Rights. For greater 35 | certainty, the foregoing obligations respecting the identification of Third Party Content and Third Party Rights 36 | do not apply to any portion of a Project that is incorporated into your Contribution to that same Project. 37 | 38 | ### Representations. 39 | 40 | You represent that, other than the Third Party Content and Third Party Rights identified by 41 | you in accordance with this Agreement, you are the sole author of your Contributions and are legally entitled 42 | to grant the foregoing licenses and waivers in respect of your Contributions. If your Contributions were 43 | created in the course of your employment with your past or present employer(s), you represent that such 44 | employer(s) has authorized you to make your Contributions on behalf of such employer(s) or such employer 45 | (s) has waived all of their right, title or interest in or to your Contributions. 46 | 47 | ### Disclaimer. 48 | 49 | To the fullest extent permitted under applicable law, your Contributions are provided on an "as is" 50 | basis, without any warranties or conditions, express or implied, including, without limitation, any implied 51 | warranties or conditions of non-infringement, merchantability or fitness for a particular purpose. You are not 52 | required to provide support for your Contributions, except to the extent you desire to provide support. 53 | 54 | ### No Obligation. 55 | 56 | You acknowledge that the maintainers of this project are under no obligation to use or incorporate your contributions 57 | into the project. The decision to use or incorporate your contributions into the project will be made at the 58 | sole discretion of the maintainers or their authorized delegates. -------------------------------------------------------------------------------- /misc/FLIP.txt: -------------------------------------------------------------------------------- 1 | @incollection{Andersson2021b, 2 | author = {Pontus Andersson and Jim Nilsson and Tomas Akenine{-}M{\"{o}}ller}, 3 | editor = {Adam Marrs and Peter Shirley and Ingo Wald}, 4 | title = "{Visualizing and Communicating Errors in Rendered Images}", 5 | booktitle = {Ray Tracing Gems II}, 6 | year = {2021}, 7 | chapter = {19}, 8 | pages = {301--320}, 9 | } -------------------------------------------------------------------------------- /misc/HDRFLIP.txt: -------------------------------------------------------------------------------- 1 | @inproceedings{Andersson2021a, 2 | author = {Pontus Andersson and 3 | Jim Nilsson and 4 | Peter Shirley and 5 | Tomas Akenine{-}M{\"{o}}ller}, 6 | title = "{Visualizing Errors in Rendered High Dynamic Range Images}", 7 | booktitle = {Eurographics Short Papers}, 8 | year = {2021}, 9 | month = {May}, 10 | DOI = {10.2312/egs.20211015} 11 | } 12 | -------------------------------------------------------------------------------- /misc/LDRFLIP.txt: -------------------------------------------------------------------------------- 1 | % The \FLIP command is defined using \usepackage{mathtools} \usepackage{xspace} \newcommand{\FLIP}{\protect\reflectbox{F}LIP\xspace} 2 | @article{Andersson2020, 3 | author = {Pontus Andersson and 4 | Jim Nilsson and 5 | Tomas Akenine{-}M{\"{o}}ller and 6 | Magnus Oskarsson and 7 | Kalle {\AA}str{\"{o}}m and 8 | Mark D. Fairchild}, 9 | title = "{{\FLIP:} {A} Difference Evaluator for Alternating Images}", 10 | journal = {Proceedings of the ACM on Computer Graphics and Interactive Techniques}, 11 | volume = {3}, 12 | number = {2}, 13 | pages = {15:1--15:23}, 14 | year = {2020}, 15 | doi={10.1145/3406183} 16 | } 17 | -------------------------------------------------------------------------------- /misc/LICENSE-third-party.md: -------------------------------------------------------------------------------- 1 | `tinyexr`: 2 | 3 | # BSD 3-Clause License 4 | 5 | Copyright (c) 2014 - 2020, Syoyo Fujita and many contributors. 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without 9 | modification, are permitted provided that the following conditions are met: 10 | * Redistributions of source code must retain the above copyright 11 | notice, this list of conditions and the following disclaimer. 12 | * Redistributions in binary form must reproduce the above copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | * Neither the name of the Syoyo Fujita nor the 16 | names of its contributors may be used to endorse or promote products 17 | derived from this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 23 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | 30 | TinyEXR contains some OpenEXR code, which is licensed under 31 | 32 | ================================================================================ 33 | 34 | Copyright (c) 2002, Industrial Light & Magic, a division of Lucas 35 | Digital Ltd. LLC 36 | 37 | All rights reserved. 38 | 39 | Redistribution and use in source and binary forms, with or without 40 | modification, are permitted provided that the following conditions are 41 | met: 42 | * Redistributions of source code must retain the above copyright 43 | notice, this list of conditions and the following disclaimer. 44 | * Redistributions in binary form must reproduce the above 45 | copyright notice, this list of conditions and the following disclaimer 46 | in the documentation and/or other materials provided with the 47 | distribution. 48 | * Neither the name of Industrial Light & Magic nor the names of 49 | its contributors may be used to endorse or promote products derived 50 | from this software without specific prior written permission. 51 | 52 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 53 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 54 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 55 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 56 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 57 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 58 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 59 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 60 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 61 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 62 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 63 | 64 | ================================================================================ 65 | 66 | End of OpenEXR license ------------------------------------------------- 67 | 68 | `tinyexr` uses miniz, which is developed by Rich Geldreich richgel99@gmail.com, and licensed under public domain. 69 | 70 | `tinyexr` tools uses stb, which is licensed under public domain: https://github.com/nothings/stb tinyexr uses some code from OpenEXR, which is licensed under a BSD 3-clause license. 71 | 72 | ************************************************************************************************************************************************************************************* 73 | ************************************************************************************************************************************************************************************* 74 | 75 | `stb_image`: 76 | 77 | # MIT License 78 | 79 | Copyright (c) 2017 Sean Barrett 80 | 81 | Permission is hereby granted, free of charge, to any person obtaining a copy of 82 | this software and associated documentation files (the "Software"), to deal in 83 | the Software without restriction, including without limitation the rights to 84 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 85 | of the Software, and to permit persons to whom the Software is furnished to do 86 | so, subject to the following conditions: 87 | 88 | The above copyright notice and this permission notice shall be included in all 89 | copies or substantial portions of the Software. 90 | 91 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 92 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 93 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 94 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 95 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 96 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 97 | SOFTWARE. -------------------------------------------------------------------------------- /misc/precision.md: -------------------------------------------------------------------------------- 1 | # A note about precision 2 | 3 | We have several different implementations of FLIP (Python, PyTorch, C++, and CUDA) and we have tried to make 4 | the implementations as similar as possible. However, there are several facts about these that make it very hard 5 | to get perfect matches between the implementations. 6 | These include: 7 | 1. Our computations are made using 32-bit floating-point arithmetic. 8 | 2. The order of operations matter, with respect to the result. 9 | * We are using different versions of functions in the different versions of FLIP, and these may not all use similar implementations. 10 | Even computing the mean of an array can give different results because of this. 11 | * As an example, if a 2D filter implementation's outer loop is on `x` and the inner loop is on `y`, that will in the majority 12 | of cases give a different floating-point result compared to have the outer loop be `y` and the inner `x`. 13 | 4. GPUs attempt to try to use fused multiply-and-add (FMA) operations, i.e., `a*b+c`, as much as possible. These are faster, but the entire 14 | operation is also computed at higher precision. Since the CPU implementation may not use FMA, this is another source of difference 15 | between implementations. 16 | 5. Depending on compiler flags, `sqrt()` may be computed using lower precision on GPUs. 17 | 6. For the C++ and CUDA implementations, we have changed to using separated filters for faster performance. 18 | This has given rise to small differences compared to previous versions. For our tests, 19 | we have therefore updated the `images/correct_{ldr|hdr}flip_{cpp|cuda}.{png|exr}` images. 20 | 21 | That said, we have tried to make the results of our different implementations as close to each other as we could. There may still be differences. 22 | 23 | Furthermore, the Python version of FLIP, installed using `pip install flip_evaluator`, runs on Windows, Linux (tested on Ubuntu 24.04), 24 | and OS X ($\ge$ 10.15). However, its output sometimes differ slightly between the different operative systems. 25 | The references used for `flip_evaluator/tests/test.py` are made for Windows. While the mean tests (means compared up to six decimal points) 26 | pass on each mentioned operative system, not all error map pixels are identical. 27 | -------------------------------------------------------------------------------- /misc/separatedConvolutions.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/misc/separatedConvolutions.pdf -------------------------------------------------------------------------------- /misc/versionList.md: -------------------------------------------------------------------------------- 1 | # FLIP Version List 2 | 3 | In addition to various minor changes, the following was 4 | changed for the different versions of FLIP: 5 | 6 | # Version 1.6 (commit 7967578) 7 | - Flipped the ꟻ in ꟻLIP. The entire name (FLIP) should now be readable on all devices. 8 | - Published Python version of FLIP to PyPI (URL: https://pypi.org/project/flip-evaluator/). 9 | - The Python version of FLIP (tool and API) is now installed by `pip install flip-evaluator`. 10 | - The distribution has been tested on Windows, Linux (Ubuntu 24.04), and OS X ($\ge$ 10.15). Wheels are built for each (and various CPython versions $\ge$ 3.8) using [cibuildwheel](https://github.com/pypa/cibuildwheel). Note that FLIP's output might differ slightly between the different operative systems. The references used for `src/tests/test.py` are made for Windows. While the mean tests (means compared up to six decimal points) pass on each mentioned operative system, not all error map pixels are identical. 11 | - After installation, the tool can be run directly in a shell by `flip --reference reference.{png|exr} --test test.{png|exr}`. 12 | - After installation, the FLIP API is available in Python by `import flip_evaluator as flip`. 13 | - Python version is now built using `scikit` instead of `setuptools`, and uses [nanobind](https://github.com/wjakob/nanobind) instead of [pybind11](https://github.com/pybind/pybind11). 14 | - Directory structure in the FLIP repository has been slightly altered to accomodate the Python version being published to PyPI. 15 | - Updated Python/C++/CUDA test script. 16 | - Various significant bugfixes. 17 | 18 | # Version 1.5 (commit -) 19 | - Skipped version 1.5 due to PyPI-related mistake. Version 1.6 is the same as version 1.5 was supposed to be. 20 | 21 | # Version 1.4 (commits 6265f80 to 0349494) 22 | - Changed the Python version of FLIP so that it leverages the C++ code through [pybind11](https://github.com/pybind/pybind11). 23 | - Results (only evaluation, not including file load/save, etc; measured on an AMD Ryzen Threadripper 3970X 32-Core Processor, 3693 MHz, with 32 Cores and 64 Logical Processors): 24 | - 20-47x faster for LDR/HDR CPU. 25 | - Timings for 1920x1080 images: 26 | - Python/LDR: 77 ms 27 | - Python/HDR: 1007 ms 28 | - **NOTE**: The Python version can currently _not_ run the CUDA version of FLIP (see issue [#22](https://github.com/NVlabs/flip/issues/22)). 29 | - **NOTE**: The Python tool now uses the C++ tool. Compared to before, you will need to change `_` to `-` when calling flip.py (e.g., `python flip.py -r reference.exr -t test.exr --start_exposure 3` is now `python flip.py -r reference.exr -t test.exr --start-exposure 3`; see `python flip.py -h`). 30 | - The Python version of FLIP can now be installed using `pip` (run `pip install -r requirements.txt .` from the `python` folder). 31 | - The code for the C++/CUDA tool is now in `FLIPToolHelpers.h`. 32 | - **NOTE**: The fourth `evaluate()` function in `FLIP.h` now takes two additional arguments: `computeMeanError` and `meanError`. Furthermore, its list of arguments has been partly reordered. 33 | - **NOTE**: The median computation (used for automatic start and stop expsoure computations in HDR-FLIP) in the C++/CUDA code has been changed, sometimes causing a minor change in results but always resulting in a significant speedup. The tests have been updated following this change. 34 | - Timings for 1920x1080 images (only evaluation, not including file load/save, etc, *but* measured with another GPU and including more code than the numbers presented in the v1.2 update, so the numbers are not directly comparable; measured on an AMD Ryzen Threadripper 3970X 32-Core Processor, 3693 MHz, with 32 Cores and 64 Logical Processors and an NVIDIA RTX 4090 GPU): 35 | - CPP/LDR: 86 ms 36 | - CPP/HDR: 1179 ms 37 | - CUDA/LDR: 8 ms 38 | - CUDA/HDR: 131 ms 39 | - Added check for OpenMP for CMake build. 40 | - Overlapped histograms are now available in the C++ tool code. These are created when one reference and _two_ test images are input, together with the `--histogram` flag. 41 | - Text file output are now available in the C++ tool code. These are created when the `--textfile` flag is input. 42 | - The Python and C++ tests now use the same targets. 43 | 44 | # Version 1.3 (commit a00bc7d) 45 | - Changed to CUDA 12.3. 46 | - Rewrote C++ code so that FLIP is in a single header (both CPP/CUDA). 47 | - Rewrote `FLIP-tool.cpp` to use many more local functions to make the code easier to read. 48 | - Some of the `tests/correct_*.png` images have been update due to minor changes in output that occurred as part of switching to CUDA 12.3 and changing the order of some transforms. 49 | 50 | # Version 1.2 (commit dde1eca) 51 | - Changed to CUDA 11.5 (was done after v1.1, but before v1.2). 52 | - Adds tests for C++ and CUDA in the tests-directory. 53 | Additionally, the Python and PyTorch tests were moved to that directory. 54 | - Performance optimizations for C++ and CUDA implementations: 55 | - Uses separable filters. 56 | - Merges several functions/kernels into fewer. 57 | - Uses OpenMP for the CPU. 58 | - Results (not including file load/save): 59 | - 111-124x faster for LDR/HDR CPU (measured on an AMD Ryzen Threadripper 3970X 32-Core Processor, 3693 MHz, with 32 Cores and 64 Logical Processors). 60 | - 2.4-2.8x faster LDR/HDR CUDA (measured on an AMD Ryzen Threadripper 3970X 32-Core Processor, 3693 MHz, with 32 Cores and 64 Logical Processors, together with an NVIDIA RTX 3090). 61 | - Timings for 1920x1080 images: 62 | - CPP/LDR: 63 ms 63 | - CPP/HDR: 1050 ms 64 | - CUDA/LDR: 13 ms 65 | - CUDA/HDR: 136 ms 66 | 67 | # Version 1.1 (commit 4ed59e9) 68 | - NVIDIA Source Code License changed to a BSD 3-Clause License 69 | - Precision updates: 70 | - Constants use nine decimal digits (a float32 number has the same 71 | bit representation if stored with nine or more decimals in NumPy 72 | and C++) 73 | - Increased accuracy in the XYZ2CIELab transform and its inverse in 74 | C++ and CUDA 75 | - Changed reference_illuminant to a float32 array in Python 76 | (reducing LDR-FLIP runtime by almost 30% for a 1080p image) 77 | - Magma and Viridis are indexed into using round instead of floor 78 | - Introduced the constant 1.0 / referenceIlluminant to avoid unnecessary 79 | divisions during color transforms 80 | - Updated the Python reference error maps based on the changes above 81 | - Updated the PyTorch test script based on the changes above 82 | - Expected CUDA version updated from 11.2 to 11.3 83 | - Removed erroneous abs() from the XYZ2CIELab transform in C++ and CUDA 84 | - Added "Acknowledgements" section to the main README file 85 | - A cross platform CMake build was added recently (commit 6bdbbaa) 86 | 87 | # Version 1.0 88 | - Initial release 89 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | [build-system] 34 | requires = ["scikit-build-core >=0.4.3", "nanobind >=1.3.2"] 35 | build-backend = "scikit_build_core.build" 36 | 37 | [project] 38 | name = "flip_evaluator" 39 | version = "1.6.0.1" 40 | description = "A Difference Evaluator for Alternating Images" 41 | readme = "README.md" 42 | requires-python = ">=3.8" 43 | authors = [ 44 | { name = "Pontus Ebelin" }, 45 | { name = "Tomas Akenine-Möller" } 46 | ] 47 | classifiers = [ 48 | "License :: OSI Approved :: BSD License" 49 | ] 50 | 51 | [project.scripts] 52 | flip = "flip_evaluator.flip_python_api:main" 53 | 54 | [project.urls] 55 | Homepage = "https://github.com/nvlabs/flip" 56 | 57 | [tool.cibuildwheel] 58 | build-verbosity = 1 59 | build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*", "cp313-*"] 60 | test-command = ["flip -h"] 61 | 62 | [tool.cibuildwheel.macos.environment] 63 | MACOSX_DEPLOYMENT_TARGET = "10.15" 64 | 65 | [tool.scikit-build] 66 | minimum-version = "0.4" 67 | sdist.include = ["src/flip_evaluator/__init__.py", "src/flip_evaluator/flip_python_api.py", "src/cpp/FLIP.h", "src/cpp/tool/*.h"] 68 | sdist.exclude = [".github", ".gitignore", "images", "misc", "dist", "*__pycache__*", "src/pytorch", "src/cmake", "src/tests", "src/CMakeLists.txt", "src/python", "src/cpp/FLIP.sln", "src/cpp/CMakeLists.txt", "src/cpp/README.md", "src/cpp/tool/CMakeLists.txt", "src/cpp/tool/CPP.vcxproj*", "src/cpp/tool/CUDA.vcxproj*", "src/cpp/tool/*.cpp", "src/cpp/tool/*.cu"] 69 | 70 | [tool.hatch.metadata.hooks.fancy-pypi-readme] 71 | content-type = "text/markdown" 72 | 73 | build-dir = "build/{wheel_tag}" 74 | 75 | wheel.py-api = "cp312" -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | cmake_minimum_required(VERSION 3.9) 34 | 35 | set(CMAKE_DISABLE_SOURCE_CHANGES ON) 36 | set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) 37 | 38 | set(CMAKE_CXX_STANDARD 17) 39 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 40 | set(CMAKE_CXX_EXTENSIONS OFF) 41 | 42 | set(CMAKE_BUILD_TYPE_INIT "Release") 43 | 44 | project(flip LANGUAGES CXX) 45 | 46 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) 47 | include(GNUInstallDirs) 48 | 49 | option(FLIP_ENABLE_CUDA "Include CUDA version of flip" OFF) 50 | 51 | add_subdirectory(cpp) 52 | -------------------------------------------------------------------------------- /src/cmake/FLIPConfig.cmake: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2021 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | if (TARGET FLIP::flip OR FLIP_FOUND) 34 | return() 35 | endif() 36 | 37 | add_library(FLIP::flip INTERFACE IMPORTED) 38 | set_target_properties(FLIP::flip PROPERTIES 39 | INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/../cpp" 40 | ) 41 | 42 | -------------------------------------------------------------------------------- /src/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | add_subdirectory(tool) 34 | -------------------------------------------------------------------------------- /src/cpp/FLIP.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.8.34330.188 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CPP", "tool\CPP.vcxproj", "{4B544BFF-EFF8-417A-9F6A-911C4EDB5675}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CUDA", "tool\CUDA.vcxproj", "{F9C72EA1-F42E-4640-8E03-C9AE310AAEB3}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Release|x64 = Release|x64 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {4B544BFF-EFF8-417A-9F6A-911C4EDB5675}.Debug|x64.ActiveCfg = Debug|x64 17 | {4B544BFF-EFF8-417A-9F6A-911C4EDB5675}.Debug|x64.Build.0 = Debug|x64 18 | {4B544BFF-EFF8-417A-9F6A-911C4EDB5675}.Release|x64.ActiveCfg = Release|x64 19 | {4B544BFF-EFF8-417A-9F6A-911C4EDB5675}.Release|x64.Build.0 = Release|x64 20 | {F9C72EA1-F42E-4640-8E03-C9AE310AAEB3}.Debug|x64.ActiveCfg = Debug|x64 21 | {F9C72EA1-F42E-4640-8E03-C9AE310AAEB3}.Debug|x64.Build.0 = Debug|x64 22 | {F9C72EA1-F42E-4640-8E03-C9AE310AAEB3}.Release|x64.ActiveCfg = Release|x64 23 | {F9C72EA1-F42E-4640-8E03-C9AE310AAEB3}.Release|x64.Build.0 = Release|x64 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {59E3C0F7-4245-4A91-8E0A-F1CF8D15FDCA} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/cpp/README.md: -------------------------------------------------------------------------------- 1 | # FLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.6) 2 | 3 | By 4 | [Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin) 5 | and 6 | [Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), 7 | with 8 | Jim Nilsson, 9 | [Magnus Oskarsson](https://www1.maths.lth.se/matematiklth/personal/magnuso/), 10 | [Kalle Åström](https://www.maths.lu.se/staff/kalleastrom/), 11 | [Mark D. Fairchild](https://www.rit.edu/directory/mdfpph-mark-fairchild), 12 | and 13 | [Peter Shirley](https://research.nvidia.com/person/peter-shirley). 14 | 15 | This [repository](https://github.com/NVlabs/flip) holds implementations of the [LDR-FLIP](https://research.nvidia.com/publication/2020-07_FLIP) 16 | and [HDR-FLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics in C++ and CUDA. 17 | It also holds code for the FLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). 18 | 19 | Note that since v1.2, we use separated convolutions for the C++ and CUDA versions of FLIP. A note explaining those 20 | can be found [here](https://github.com/NVlabs/flip/blob/main/misc/separatedConvolutions.pdf). 21 | 22 | With v1.3, we have switched to a single header [FLIP.h](FLIP.h) for easier integration into other projects. 23 | 24 | Since v1.4, the majority of the code for the tool is contained in [FLIPToolHelpers.h](FLIPToolHelpers.h), but the tool is still run through [FLIP-tool.cpp](FLIP-tool.cpp) and [FLIP-tool.cu](FLIP-tool.cu), respectively. 25 | 26 | 27 | # License 28 | 29 | Copyright © 2020-2025, NVIDIA Corporation & Affiliates. All rights reserved. 30 | 31 | This work is made available under a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/LICENSE). 32 | 33 | The repository distributes code for `tinyexr`, which is subject to a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#bsd-3-clause-license),
34 | and `stb_image`, which is subject to an [MIT License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#mit-license). 35 | 36 | For individual contributions to the project, please confer the [Individual Contributor License Agreement](https://github.com/NVlabs/flip/blob/main/misc/CLA.md). 37 | 38 | For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). 39 | 40 | # C++ and CUDA (API and Tool) 41 | - If you want to use FLIP in your own project, it should suffice to use the header [FLIP.h](FLIP.h). Typical usage would be: 42 | ``` 43 | #define FLIP_ENABLE_CUDA // You need to define this if you want to run FLIP using CUDA. Otherwise, comment this out. 44 | #include "FLIP.h" // See the bottom of FLIP.h for four different FLIP::evaluate(...) functions that can be used. 45 | 46 | void someFunction() 47 | { 48 | FLIP::evaluate(...); // See FLIP-tool.cpp for an example of how to use one of these overloaded functions. 49 | } 50 | ``` 51 | - The FLIP.sln solution contains one CUDA backend project and one pure C++ backend project for the FLIP tool. 52 | - Compiling the CUDA project requires a CUDA compatible GPU. Instruction on how to install CUDA can be found [here](https://docs.nvidia.com/cuda/cuda-installation-guide-microsoft-windows/index.html). 53 | - Alternatively, a CMake build can be done by creating a build directory in the `src` directory and invoking CMake on the source `cpp` directory (add `--config Release` to build release configuration on Windows): 54 | 55 | ``` 56 | cd src 57 | mkdir build 58 | cd build 59 | cmake .. 60 | cmake --build . [--config Release] 61 | ``` 62 | 63 | CUDA support is enabled via the `FLIP_ENABLE_CUDA`, which can be passed to CMake on the command line with `-DFLIP_ENABLE_CUDA=ON` or set interactively with `ccmake` or `cmake-gui`. 64 | `FLIP_LIBRARY` option allows to output a library rather than an executable. 65 | - Usage: `flip[-cuda].exe --reference reference.{exr|png} --test test.{exr|png} [options]`, where the list of options can be seen by `flip[-cuda].exe -h`. 66 | - Tested on Windows 10 version 22H2 and Windows 11 version 23H2 with CUDA 12.6. Compiled with Visual Studio 2022. If you use another version of CUDA, you will need to change the `CUDA 12.6` strings in the `CUDA.vcxproj` file accordingly. 67 | - `src/tests/test.py` contains simple tests used to test whether code updates alter results. Notice that those scripts require `numpy` and `matplotlib`, both of which may be installed using pip. 68 | - Weighted histograms are output as Python scripts. Running the script will create a PDF version of the histogram. Like the test script, these scripts require `numpy` and `matplotlib`, both of which may be installed using pip. 69 | - The naming convention used for the FLIP tool's output is as follows (where `ppd` is the assumed number of pixels per degree, 70 | `tm` is the tone mapper assumed by HDR-FLIP, `cstart` and `cstop` are the shortest and longest exposures, respectively, assumed by HDR-FLIP, 71 | with `p` indicating a positive value and `m` indicating a negative value, 72 | `N` is the number of exposures used in the HDR-FLIP calculation, `nnn` is a counter used to sort the intermediate results, 73 | and `exp` is the exposure used for the intermediate LDR image / FLIP map): 74 | 75 | **Default:** 76 | 77 | *Low dynamic range images:*
78 | 79 | LDR-FLIP: `flip...ppd.ldr.png`
80 | Weighted histogram: `weighted_histogram.reference>..ppd.ldr.py`
81 | Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.ldr.py`
82 | Text file: `pooled_values...ppd.ldr.txt`
83 | 84 | *High dynamic range images:*
85 | 86 | HDR-FLIP: `flip...ppd.hdr.._to_..png`
87 | Exposure map: `exposure_map...ppd.hdr.._to_..png`
88 | Intermediate LDR-FLIP maps: `flip...ppd.ldr....png`
89 | Intermediate LDR images: `....png`
90 | Weighted histogram: `weighted_histogram...ppd.hdr.._to_..py`
91 | Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.hdr.._to_..py`
92 | Text file: `pooled_values...ppd.hdr.._to_..txt`
93 | 94 | **With** `--basename ` **(note: not applicable if more than one test image is evaluated):** 95 | 96 | *Low dynamic range images:*
97 | 98 | LDR-FLIP: `.png`
99 | Weighted histogram: `.py`
100 | Overlapping weighted histogram: N/A
101 | Text file: `.txt`
102 | 103 | *High dynamic range images:*
104 | 105 | HDR-FLIP: `.png`
106 | Exposure map: `.exposure_map.png`
107 | Intermediate LDR-FLIP maps: `..png`
108 | Intermediate LDR images: `.reference|test..png`
109 | Weighted histogram: `.py`
110 | Overlapping weighted histogram: N/A
111 | Text file: `.txt`
112 | 113 | **Example usage:** 114 | After compiling the `src/cpp/FLIP.sln` project, navigate to the `flip[-cuda].exe` executable and try: 115 | ``` 116 | flip[-cuda].exe -r ../../../images/reference.exr -t ../../../images/test.exr 117 | ``` 118 | Assuming using the images in the source bundle, the result should be: 119 | ``` 120 | Invoking HDR-FLIP 121 | Pixels per degree: 67 122 | Assumed tone mapper: ACES 123 | Start exposure: -12.5423 124 | Stop exposure: 0.9427 125 | Number of exposures: 14 126 | 127 | FLIP between reference image and test image : 128 | Mean: 0.283478 129 | Weighted median: 0.339430 130 | 1st weighted quartile: 0.251122 131 | 3rd weighted quartile: 0.434673 132 | Min: 0.003123 133 | Max: 0.962022 134 | Evaluation time: seconds 135 | FLIP error map location: 136 | FLIP exposure map location: 137 | ``` 138 | where `` is the time it took to evaluate HDR-FLIP. In addition, you will now find the files `flip.reference.test.67ppd.hdr.aces.m12.5423_to_p0.9427.14.png` and `exposure_map.reference.test.67ppd.hdr.aces.m12.5423_to_p0.9427.14.png` 139 | in the directory containing the `flip[-cuda].exe` executable, and we urge you to inspect those, which will reveal where the errors in the test image are located. 140 | -------------------------------------------------------------------------------- /src/cpp/tool/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | set(FLIP_DIR ${CMAKE_CURRENT_LIST_DIR}/../../cmake) 34 | find_package(FLIP REQUIRED CONFIG) # Exercise CMake config in cmake/ directory 35 | 36 | ## CPU version ## 37 | 38 | project(flip-cli LANGUAGES CXX) 39 | add_executable(${PROJECT_NAME} FLIP-tool.cpp) 40 | target_link_libraries(${PROJECT_NAME} FLIP::flip) 41 | set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME flip) 42 | install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 43 | 44 | find_package(OpenMP) 45 | if (TARGET OpenMP::OpenMP_CXX) 46 | target_link_libraries(${PROJECT_NAME} OpenMP::OpenMP_CXX) 47 | endif() 48 | 49 | ## CUDA version ## 50 | 51 | if (FLIP_ENABLE_CUDA) 52 | project(flip-cuda-cli LANGUAGES CUDA) 53 | add_executable(${PROJECT_NAME} FLIP-tool.cu) 54 | target_link_libraries(${PROJECT_NAME} FLIP::flip) 55 | set_target_properties(${PROJECT_NAME} PROPERTIES 56 | CUDA_ARCHITECTURES OFF 57 | OUTPUT_NAME flip-cuda 58 | ) 59 | install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 60 | endif() 61 | -------------------------------------------------------------------------------- /src/cpp/tool/CPP.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 16.0 15 | Win32Proj 16 | {4b544bff-eff8-417a-9f6a-911c4edb5675} 17 | CPP 18 | 10.0 19 | 20 | 21 | 22 | Application 23 | true 24 | v143 25 | Unicode 26 | 27 | 28 | Application 29 | false 30 | v143 31 | true 32 | Unicode 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | true 48 | flip 49 | $(VC_IncludePath);$(WindowsSDK_IncludePath);../ 50 | 51 | 52 | false 53 | flip 54 | $(VC_IncludePath);$(WindowsSDK_IncludePath);../ 55 | 56 | 57 | 58 | Level3 59 | true 60 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 61 | true 62 | $(ProjectDir);%(AdditionalIncludeDirectories) 63 | true 64 | stdcpp17 65 | 66 | 67 | Console 68 | true 69 | 70 | 71 | 72 | 73 | Level3 74 | true 75 | true 76 | true 77 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 78 | true 79 | $(ProjectDir);%(AdditionalIncludeDirectories) 80 | stdcpp17 81 | true 82 | 83 | 84 | 85 | 86 | Console 87 | true 88 | true 89 | true 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/cpp/tool/CPP.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | tool 7 | 8 | 9 | tool 10 | 11 | 12 | tool 13 | 14 | 15 | tool 16 | 17 | 18 | tool 19 | 20 | 21 | tool 22 | 23 | 24 | tool 25 | 26 | 27 | tool 28 | 29 | 30 | 31 | 32 | {d88e6fe1-629c-4cb2-a204-549ed75b0fc0} 33 | 34 | 35 | 36 | 37 | tool 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/cpp/tool/CUDA.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | true 30 | true 31 | 32 | 33 | 34 | {F9C72EA1-F42E-4640-8E03-C9AE310AAEB3} 35 | CUDA 36 | 10.0 37 | 38 | 39 | 40 | Application 41 | true 42 | MultiByte 43 | v143 44 | 45 | 46 | Application 47 | false 48 | true 49 | MultiByte 50 | v143 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | true 65 | flip-cuda 66 | $(VC_IncludePath);$(WindowsSDK_IncludePath);../ 67 | 68 | 69 | flip-cuda 70 | $(VC_IncludePath);$(WindowsSDK_IncludePath);../ 71 | 72 | 73 | 74 | Level3 75 | Disabled 76 | WIN32;WIN64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 77 | $(ProjectDir);%(AdditionalIncludeDirectories) 78 | stdcpp17 79 | 80 | 81 | true 82 | Console 83 | cudart_static.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 84 | 85 | 86 | 64 87 | /std:c++17 88 | --std c++17 %(AdditionalOptions) 89 | 90 | 91 | 92 | 93 | Level3 94 | MaxSpeed 95 | true 96 | true 97 | WIN32;WIN64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 98 | $(ProjectDir);%(AdditionalIncludeDirectories) 99 | stdcpp17 100 | 101 | 102 | true 103 | true 104 | true 105 | Console 106 | cudart_static.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) 107 | 108 | 109 | 64 110 | /std:c++17 111 | --std c++17 %(AdditionalOptions) 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/cpp/tool/CUDA.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | tool 7 | 8 | 9 | tool 10 | 11 | 12 | tool 13 | 14 | 15 | tool 16 | 17 | 18 | tool 19 | 20 | 21 | tool 22 | 23 | 24 | tool 25 | 26 | 27 | tool 28 | 29 | 30 | 31 | 32 | {272613f8-4491-449f-8116-7a1ad0cee96a} 33 | 34 | 35 | 36 | 37 | tool 38 | 39 | 40 | 41 | 42 | tool 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/cpp/tool/FLIP-tool.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its 15 | * contributors may be used to endorse or promote products derived from 16 | * this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | * SPDX-License-Identifier: BSD-3-Clause 31 | */ 32 | 33 | // Visualizing and Communicating Errors in Rendered Images 34 | // Ray Tracing Gems II, 2021, 35 | // by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | // Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | // Visualizing Errors in Rendered High Dynamic Range Images 39 | // Eurographics 2021, 40 | // by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | // Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | // FLIP: A Difference Evaluator for Alternating Images 44 | // High Performance Graphics 2020, 45 | // by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | // Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | // Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | // Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 50 | 51 | #include "../FLIP.h" 52 | #include "FLIPToolHelpers.h" 53 | #include "commandline.h" 54 | 55 | int main(int argc, char** argv) 56 | { 57 | commandline commandLine = commandline(argc, argv, getAllowedCommandLineOptions()); 58 | FLIPTool::execute(commandLine); 59 | } 60 | -------------------------------------------------------------------------------- /src/cpp/tool/FLIP-tool.cu: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its 15 | * contributors may be used to endorse or promote products derived from 16 | * this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | * SPDX-License-Identifier: BSD-3-Clause 31 | */ 32 | 33 | // Visualizing and Communicating Errors in Rendered Images 34 | // Ray Tracing Gems II, 2021, 35 | // by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | // Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | // Visualizing Errors in Rendered High Dynamic Range Images 39 | // Eurographics 2021, 40 | // by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | // Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | // FLIP: A Difference Evaluator for Alternating Images 44 | // High Performance Graphics 2020, 45 | // by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | // Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | // Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | // Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 50 | 51 | #define FLIP_ENABLE_CUDA // Let FLIP.h know that we want the CUDA implementation. 52 | #include "FLIP-tool.cpp" // This was done so that all code could be shared between the CPU and CUDA versions of the tool. -------------------------------------------------------------------------------- /src/cpp/tool/commandline.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its 15 | * contributors may be used to endorse or promote products derived from 16 | * this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | * SPDX-License-Identifier: BSD-3-Clause 31 | */ 32 | 33 | // Visualizing and Communicating Errors in Rendered Images 34 | // Ray Tracing Gems II, 2021, 35 | // by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | // Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | // Visualizing Errors in Rendered High Dynamic Range Images 39 | // Eurographics 2021, 40 | // by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | // Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | // FLIP: A Difference Evaluator for Alternating Images 44 | // High Performance Graphics 2020, 45 | // by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | // Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | // Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | // Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 50 | 51 | #pragma once 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | 60 | template 61 | class unique_vector; 62 | 63 | template 64 | class unique_vector_iterator 65 | { 66 | private: 67 | size_t mPosition; 68 | const unique_vector* mpUniqueVector; 69 | 70 | public: 71 | unique_vector_iterator(const unique_vector* pVector, size_t position) : mPosition(position), mpUniqueVector(pVector) { } 72 | bool operator!=(const unique_vector_iterator& other) const { return this->mPosition != other.mPosition; } 73 | T operator*(void) const { return this->mpUniqueVector->at(this->mPosition); } 74 | const unique_vector_iterator& operator++(void) { this->mPosition++; return *this; } 75 | unique_vector_iterator operator+(size_t v) const { return unique_vector_iterator(this->mpUniqueVector, this->mPosition + v); } 76 | }; 77 | 78 | template 79 | class unique_vector 80 | { 81 | public: 82 | typedef unique_vector_iterator iterator; 83 | 84 | private: 85 | std::vector mVector; 86 | 87 | public: 88 | void push_back(T value) { if (!this->contains(value)) this->mVector.push_back(value); } 89 | T at(size_t index) const { return this->mVector.at(index); } 90 | T operator[](size_t index) const { return this->mVector.at(index); } 91 | T& operator[](size_t index) { return this->mVector.at(index); } 92 | size_t size(void) const { return this->mVector.size(); } 93 | bool contains(T value) const { return std::find(this->mVector.begin(), this->mVector.end(), value) != mVector.end(); } 94 | 95 | iterator begin(size_t begin = 0) const { return iterator(this, begin); } 96 | iterator end(size_t end = std::string::npos) const { return iterator(this, (end != std::string::npos && end <= mVector.size() ? end : mVector.size())); } 97 | 98 | // Conversion to const std::vector. 99 | operator const std::vector() const { return mVector; } 100 | operator std::vector() { return mVector; } 101 | }; 102 | 103 | typedef struct 104 | { 105 | std::string longName; 106 | std::string shortName; 107 | int numArgs; // -1: '+' if required, '*' if not required. 108 | bool required; 109 | std::string meta; 110 | std::string help; 111 | std::string value; 112 | } commandline_option; 113 | 114 | typedef struct 115 | { 116 | std::string description; 117 | std::vector options; 118 | } commandline_options; 119 | 120 | static const commandline_options getAllowedCommandLineOptions(const bool cpptool = true) 121 | { 122 | commandline_options commandLineOptions = 123 | { 124 | "Compute FLIP between reference. and test..\n" 125 | "Reference and test(s) must have same resolution and format.\n" 126 | "If pngs are entered, LDR-FLIP will be evaluated. If exrs are entered, HDR-FLIP will be evaluated.\n" 127 | "For HDR-FLIP, the reference is used to automatically calculate start exposure and/or stop exposure and/or number of exposures, if they are not entered.\n", 128 | { 129 | { "help", "h", 0, false, "", "show this help message and exit" }, 130 | { "reference", "r", 1, true, "REFERENCE", "Relative or absolute path (including file name and extension) for reference image" }, 131 | { "test", "t", -1, true, "TEST", "Relative or absolute path(s) (including file name and extension) for test image(s)" }, 132 | { "pixels-per-degree", "ppd", 1, false, "PIXELS-PER-DEGREE", "Observer's number of pixels per degree of visual angle. Default corresponds to\nviewing the images at 0.7 meters from a 0.7 meter wide 4K display" }, 133 | { "viewing-conditions", "vc", 3, false, "MONITOR-DISTANCE MONITOR-WIDTH MONITOR-WIDTH-PIXELS", "Distance to monitor (in meters), width of monitor (in meters), width of monitor (in pixels).\nDefault corresponds to viewing the monitor at 0.7 meters from a 0.7 meters wide 4K display" }, 134 | { "tone-mapper", "tm", 1, false, "ACES | HABLE | REINHARD", "Tone mapper used for HDR-FLIP. Supported tone mappers are ACES, Hable, and Reinhard (default: ACES)" }, 135 | { "num-exposures", "n", 1, false, "NUM-EXPOSURES", "Number of exposures between (and including) start and stop exposure used to compute HDR-FLIP" }, 136 | { "start-exposure", "cstart", 1, false, "C-START", "Start exposure used to compute HDR-FLIP" }, 137 | { "stop-exposure", "cstop", 1, false, "C-STOP", "Stop exposure used to comput HDR-FLIP" }, 138 | { "verbosity", "v", 1, false, "VERBOSITY", "Level of verbosity (default: 2).\n0: no printed output,\n\t\t1: print mean FLIP error,\n\t\t2: print pooled FLIP errors, PPD, and evaluation time and (for HDR-FLIP) start and stop exposure and number of exposures"}, 139 | { "basename", "b", 1, false, "BASENAME", "Basename for outfiles, e.g., error and exposure maps. Only compatible with one test image as input" }, 140 | { "textfile", "txt", 0, false, "", "Save text file with pooled FLIP values (mean, weighted median and weighted 1st and 3rd quartiles as well as minimum and maximum error)" }, 141 | { "csv", "c", 1, false, "CSV_FILENAME", "Write results to a csv file. Input is the desired file name (including .csv extension).\nResults are appended if the file already exists" }, 142 | { "histogram", "hist", 0, false, "", "Save weighted histogram of the FLIP error map(s)" }, 143 | { "y-max", "", 1, true, "", "Set upper limit of weighted histogram's y-axis" }, 144 | { "log", "lg", 0, false, "", "Use logarithmic scale on y-axis in histogram" }, 145 | { "exclude-pooled-values", "epv", 0, false, "", "Do not include pooled FLIP values in the weighted histogram" }, 146 | { "save-ldr-images", "sli", 0, false, "", "Save all exposure compensated and tone mapped LDR images (png) used for HDR-FLIP" }, 147 | { "save-ldrflip", "slf", 0, false, "", "Save all LDR-FLIP maps used for HDR-FLIP" }, 148 | { "no-magma", "nm", 0, false, "", "Save FLIP error maps in grayscale instead of magma" }, 149 | { "no-exposure-map", "nexm", 0, false, "", "Do not save the HDR-FLIP exposure map" }, 150 | { "no-error-map", "nerm", 0, false, "", "Do not save the FLIP error map" }, 151 | { "directory", "d", 1, false, "Relative or absolute path to save directory"}, 152 | } }; 153 | 154 | // Add C++ specific options. 155 | std::vector cppSpecific = {}; 156 | if (cpptool) 157 | { 158 | cppSpecific = 159 | { 160 | { "exit-on-test", "et", 0, false, "", "Do exit(EXIT_FAILURE) if the selected FLIP QUANTITY is greater than THRESHOLD"}, 161 | { "exit-test-parameters", "etp", 2, false, "QUANTITY = {MEAN (default) | WEIGHTED-MEDIAN | MAX} THRESHOLD (default = 0.05) ", "Test parameters for selected quantity and threshold value (in [0,1]) for exit on test"}, 162 | }; 163 | } 164 | for (auto opt : cppSpecific) 165 | { 166 | commandLineOptions.options.push_back(opt); 167 | } 168 | 169 | return commandLineOptions; 170 | } 171 | 172 | 173 | class commandline 174 | { 175 | private: 176 | std::string mCommand; 177 | commandline_options mAllowedOptions; 178 | std::map> mOptions; 179 | std::vector mArguments; 180 | bool mError = false; 181 | std::string mErrorString; 182 | 183 | public: 184 | commandline() = default; 185 | 186 | commandline(int argc, char* argv[], const commandline_options& allowedOptions = {}) : 187 | mAllowedOptions(allowedOptions) 188 | { 189 | clear(); 190 | parse(argc, argv, allowedOptions); 191 | } 192 | 193 | bool getError(void) const 194 | { 195 | return mError; 196 | } 197 | 198 | std::string getErrorString(void) const 199 | { 200 | return mErrorString; 201 | } 202 | 203 | void clear(void) 204 | { 205 | mCommand = ""; 206 | mOptions.clear(); 207 | mArguments.clear(); 208 | } 209 | 210 | bool parse(int argc, char* argv[], const commandline_options& allowedOptions = {}) 211 | { 212 | mCommand = argv[0]; 213 | 214 | std::string tOption = ""; 215 | int atArg = 1; 216 | 217 | std::string longOptionName; 218 | std::string shortOptionName; 219 | while (atArg < argc) 220 | { 221 | bool atOption = false; 222 | bool optionHasArgument = false; 223 | int numOptionArguments = 0; 224 | 225 | std::string arg(argv[atArg]); 226 | 227 | if (arg[0] == '-') 228 | { 229 | bool bIsLong = (arg[1] == '-'); 230 | std::string optionString = arg.substr(bIsLong ? 2 : 1); 231 | 232 | bool bFound = false; 233 | for (auto allowedOption : allowedOptions.options) 234 | { 235 | longOptionName = allowedOption.longName; 236 | shortOptionName = allowedOption.shortName; 237 | if ((bIsLong && longOptionName == optionString) || (!bIsLong && shortOptionName == optionString)) 238 | { 239 | bFound = true; 240 | atOption = true; 241 | tOption = optionString; 242 | optionHasArgument = (allowedOption.numArgs != 0); 243 | numOptionArguments = allowedOption.numArgs; 244 | break; 245 | } 246 | } 247 | 248 | if (atOption) 249 | { 250 | if (!bFound) 251 | { 252 | this->mError = true; 253 | this->mErrorString = "Option not found!"; 254 | return false; 255 | } 256 | 257 | if (optionHasArgument) 258 | { 259 | // Eat option arguments. 260 | atArg++; 261 | while (atArg < argc && numOptionArguments != 0) 262 | { 263 | arg = argv[atArg]; 264 | if (arg[0] == '-' && numOptionArguments < 0) 265 | { 266 | break; 267 | } 268 | this->mOptions[longOptionName].push_back(arg); 269 | if (numOptionArguments > 0) 270 | { 271 | numOptionArguments--; 272 | } 273 | atArg++; 274 | } 275 | } 276 | else 277 | { 278 | this->mOptions[longOptionName].push_back(""); 279 | atArg++; 280 | } 281 | } 282 | else 283 | { 284 | this->mArguments.push_back(arg); 285 | atArg++; 286 | } 287 | 288 | } 289 | else 290 | { 291 | this->mArguments.push_back(arg); 292 | atArg++; 293 | } 294 | } 295 | 296 | return true; 297 | } 298 | 299 | bool parse(std::string commandLine, const commandline_options& allowedOptions = {}) 300 | { 301 | std::vector vCommandLine; 302 | std::string command; 303 | std::stringstream ss(commandLine); 304 | 305 | while (ss >> command) 306 | vCommandLine.push_back(command); 307 | 308 | std::vector vpCommandLine; 309 | for (size_t i = 0; i < vCommandLine.size(); i++) 310 | vpCommandLine.push_back(const_cast(vCommandLine[i].c_str())); 311 | 312 | return parse(int(vpCommandLine.size()), &vpCommandLine[0], allowedOptions); 313 | } 314 | 315 | inline const std::string& getCommand(void) const 316 | { 317 | return mCommand; 318 | } 319 | 320 | inline size_t getNumArguments(void) const 321 | { 322 | return this->mArguments.size(); 323 | } 324 | 325 | inline const std::string getArgument(size_t index) const 326 | { 327 | assert(index < this->mArguments.size()); 328 | return this->mArguments[index]; 329 | } 330 | 331 | const std::vector& getArguments(void) const 332 | { 333 | return this->mArguments; 334 | } 335 | 336 | const std::string& getOptionValue(const std::string& option, size_t n = 0) const 337 | { 338 | return this->mOptions.at(option)[n]; 339 | } 340 | 341 | std::vector& getOptionValues(const std::string& option) 342 | { 343 | return (std::vector&)this->mOptions.at(option); 344 | } 345 | 346 | const std::string& getOption(const std::string& option, size_t index) const 347 | { 348 | static const std::string emptyString; 349 | 350 | auto& options = this->mOptions.at(option); 351 | 352 | if (index < options.size()) 353 | { 354 | return options[index]; 355 | } 356 | 357 | return emptyString; 358 | } 359 | 360 | size_t getNumOptionValues(const std::string& option) const 361 | { 362 | return this->mOptions.at(option).size(); 363 | } 364 | 365 | bool optionSet(const std::string& option) const 366 | { 367 | return this->mOptions.find(option) != this->mOptions.end(); 368 | } 369 | 370 | void print(void) 371 | { 372 | commandline_options& ao = this->mAllowedOptions; 373 | 374 | std::string command = mCommand.substr(mCommand.find_last_of("/\\") + 1); 375 | 376 | size_t numOptionalOptions = 0; 377 | for (auto& o : ao.options) 378 | { 379 | if (o.required == false) 380 | { 381 | numOptionalOptions++; 382 | } 383 | } 384 | 385 | std::cout << "usage: " << command; 386 | if (numOptionalOptions > 0) 387 | { 388 | std::cout << " [OPTIONS]"; 389 | if (numOptionalOptions > 1) 390 | { 391 | std::cout << "..."; 392 | } 393 | } 394 | 395 | for (auto& o : ao.options) 396 | { 397 | if (o.required) 398 | { 399 | std::cout << " -" << o.shortName << ""; 400 | if (o.numArgs != 0) 401 | { 402 | std::cout << " " << o.meta; 403 | if (o.numArgs == -1) 404 | { 405 | std::cout << " [...]"; 406 | } 407 | } 408 | } 409 | } 410 | std::cout << "\n\n"; 411 | 412 | std::cout << ao.description << "\n\n"; 413 | 414 | for (auto& o : ao.options) 415 | { 416 | if (o.shortName != "") 417 | { 418 | std::cout << " -" << o.shortName; 419 | if (o.longName != "") 420 | { 421 | std::cout << ","; 422 | } 423 | } 424 | if (o.longName != "") 425 | { 426 | std::cout << " --" << o.longName; 427 | } 428 | 429 | if (o.numArgs != 0) 430 | { 431 | std::cout << " " << o.meta; 432 | if (o.numArgs == -1) 433 | { 434 | std::cout << " [...]"; 435 | } 436 | } 437 | std::cout << "\n"; 438 | 439 | if (o.help != "") 440 | { 441 | size_t loc = o.help.find("\n"); 442 | if (loc != std::string::npos) 443 | { 444 | o.help.replace(loc, 1, "\n\t\t"); 445 | } 446 | 447 | std::cout << "\t\t" << o.help << "\n"; 448 | } 449 | } 450 | } 451 | }; 452 | -------------------------------------------------------------------------------- /src/cpp/tool/filename.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its 15 | * contributors may be used to endorse or promote products derived from 16 | * this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | * SPDX-License-Identifier: BSD-3-Clause 31 | */ 32 | 33 | // Visualizing and Communicating Errors in Rendered Images 34 | // Ray Tracing Gems II, 2021, 35 | // by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | // Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | // Visualizing Errors in Rendered High Dynamic Range Images 39 | // Eurographics 2021, 40 | // by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | // Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | // FLIP: A Difference Evaluator for Alternating Images 44 | // High Performance Graphics 2020, 45 | // by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | // Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | // Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | // Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 50 | 51 | #pragma once 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | #include 62 | #include 63 | 64 | #ifndef MAX_PATH 65 | #define MAX_PATH 260 66 | #endif 67 | 68 | namespace FLIP 69 | { 70 | 71 | class filename 72 | { 73 | private: 74 | std::string mDirectory; 75 | std::string mName; 76 | std::string mExtension; 77 | 78 | public: 79 | filename() { init(); }; 80 | filename(const std::string& path) { parse(path); } 81 | filename(const filename& fileName) { *this = fileName; } 82 | ~filename() {}; 83 | 84 | filename operator=(const std::string& path) { parse(path); return *this; } 85 | 86 | bool operator==(const filename& fileName) const 87 | { 88 | return this->mDirectory == fileName.mDirectory && 89 | this->mName == fileName.mName && 90 | this->mExtension == fileName.mExtension; 91 | } 92 | bool operator!=(const filename& fileName) const { return !(*this == fileName); } 93 | 94 | inline void setName(std::string name) 95 | { 96 | // Remove forbidden characters from the name. 97 | this->mName = std::regex_replace(name, std::regex("\\\\|/|:|\\*|\\?|\\||\"|<|>"), "_"); 98 | } 99 | inline const std::string& getName(void) const { return this->mName; } 100 | 101 | inline void setExtension(std::string extension) { this->mExtension = extension; } 102 | inline const std::string& getExtension(void) const { return this->mExtension; } 103 | 104 | 105 | static const filename& empty(void) 106 | { 107 | static const filename emptyFileName = filename(); 108 | return emptyFileName; 109 | } 110 | 111 | std::string toString(bool toLower = false) const 112 | { 113 | if (*this == this->empty()) 114 | return ""; 115 | 116 | std::stringstream ss; 117 | if (this->mDirectory != "") 118 | { 119 | #if _WIN32 120 | ss << this->mDirectory << "\\"; 121 | #else 122 | ss << this->mDirectory << "/"; 123 | #endif 124 | } 125 | ss << this->mName; 126 | if (this->mExtension != "") 127 | { 128 | ss << "." << this->mExtension; 129 | } 130 | 131 | std::string string = ss.str(); 132 | 133 | if (toLower) 134 | { 135 | std::transform(string.begin(), string.end(), string.begin(), ::tolower); 136 | } 137 | 138 | return string; 139 | } 140 | 141 | 142 | void init(void) 143 | { 144 | this->mDirectory = ""; 145 | this->mName = ""; 146 | this->mExtension = ""; 147 | } 148 | 149 | bool parse(std::string path) 150 | { 151 | init(); 152 | size_t periodCount = std::count_if(path.begin(), path.end(), [](char c) {return c == '.'; }); 153 | if (path[0] == '.' && periodCount == 1) 154 | { 155 | mExtension = path.substr(1); 156 | return true; 157 | } 158 | 159 | // Must be at least one slash to contain a directory. 160 | size_t iLastSlash = path.find_last_of("\\/"); 161 | if (iLastSlash != std::string::npos) 162 | { 163 | this->mDirectory = path.substr(0, iLastSlash); 164 | this->mDirectory = this->mDirectory.substr(0, this->mDirectory.find_last_not_of("\\/") + 1); 165 | 166 | // No wildcards allowed in the directory. 167 | if (this->mDirectory.find_first_of("*?[]{}") != std::string::npos) 168 | return false; 169 | } 170 | 171 | // Period, plus no later slash, constitutes an extension. 172 | size_t iLastPeriod = path.find_last_of("."); 173 | if (iLastPeriod != std::string::npos && (iLastSlash == std::string::npos || iLastPeriod > iLastSlash)) 174 | { 175 | this->mExtension = path.substr(path.find_last_of(".") + 1); 176 | } 177 | 178 | path = path.substr(0, iLastPeriod); // Remove extension. 179 | path = path.substr(path.find_last_of("\\/") + 1); // Remove directory. 180 | 181 | this->mName = path; 182 | return true; 183 | } 184 | 185 | }; 186 | 187 | } 188 | -------------------------------------------------------------------------------- /src/cpp/tool/imagehelpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its 15 | * contributors may be used to endorse or promote products derived from 16 | * this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | * SPDX-License-Identifier: BSD-3-Clause 31 | */ 32 | 33 | // Visualizing and Communicating Errors in Rendered Images 34 | // Ray Tracing Gems II, 2021, 35 | // by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | // Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | // Visualizing Errors in Rendered High Dynamic Range Images 39 | // Eurographics 2021, 40 | // by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | // Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | // FLIP: A Difference Evaluator for Alternating Images 44 | // High Performance Graphics 2020, 45 | // by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | // Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | // Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | // Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 50 | 51 | #pragma once 52 | #include 53 | #include 54 | #include 55 | 56 | #define TINYEXR_IMPLEMENTATION 57 | #include "tinyexr.h" 58 | #define STB_IMAGE_IMPLEMENTATION 59 | #include "stb_image.h" 60 | #define STB_IMAGE_WRITE_IMPLEMENTATION 61 | #include "stb_image_write.h" 62 | 63 | namespace ImageHelpers 64 | { 65 | bool ldrLoad(const std::string& filename, int& imgWidth, int& imgHeight, float*& pixels) 66 | { 67 | int bpp; 68 | unsigned char* ldrPixels = stbi_load(filename.c_str(), &imgWidth, &imgHeight, &bpp, 3); 69 | if (!ldrPixels) 70 | { 71 | return false; 72 | } 73 | 74 | pixels = new float[3 * imgWidth * imgHeight]; 75 | #pragma omp parallel for 76 | for (int y = 0; y < imgHeight; y++) 77 | { 78 | for (int x = 0; x < imgWidth; x++) 79 | { 80 | int linearIdx = 3 * (y * imgWidth + x); 81 | pixels[linearIdx + 0] = ldrPixels[linearIdx + 0] / 255.0f; 82 | pixels[linearIdx + 1] = ldrPixels[linearIdx + 1] / 255.0f; 83 | pixels[linearIdx + 2] = ldrPixels[linearIdx + 2] / 255.0f; 84 | 85 | } 86 | } 87 | delete[] ldrPixels; 88 | return true; 89 | } 90 | 91 | bool hdrLoad(const std::string& fileName, int& imgWidth, int& imgHeight, float*& hdrPixels) 92 | { 93 | EXRVersion exrVersion; 94 | EXRImage exrImage; 95 | EXRHeader exrHeader; 96 | InitEXRHeader(&exrHeader); 97 | InitEXRImage(&exrImage); 98 | 99 | { 100 | int ret; 101 | const char* errorString; 102 | 103 | ret = ParseEXRVersionFromFile(&exrVersion, fileName.c_str()); 104 | if (ret != TINYEXR_SUCCESS || exrVersion.multipart || exrVersion.non_image) 105 | { 106 | std::cerr << "Unsupported EXR version or type!" << std::endl; 107 | return false; 108 | } 109 | 110 | ret = ParseEXRHeaderFromFile(&exrHeader, &exrVersion, fileName.c_str(), &errorString); 111 | if (ret != TINYEXR_SUCCESS) 112 | { 113 | std::cerr << "Error loading EXR header: " << errorString << std::endl; 114 | return false; 115 | } 116 | 117 | for (int i = 0; i < exrHeader.num_channels; i++) 118 | { 119 | if (exrHeader.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) 120 | { 121 | exrHeader.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; 122 | } 123 | } 124 | 125 | ret = LoadEXRImageFromFile(&exrImage, &exrHeader, fileName.c_str(), &errorString); 126 | if (ret != TINYEXR_SUCCESS) 127 | { 128 | std::cerr << "Error loading EXR file: " << errorString << std::endl; 129 | return false; 130 | } 131 | } 132 | 133 | imgWidth = exrImage.width; 134 | imgHeight = exrImage.height; 135 | 136 | int idxR = -1; 137 | int idxG = -1; 138 | int idxB = -1; 139 | int numRecognizedChannels = 0; 140 | for (int c = 0; c < exrHeader.num_channels; c++) 141 | { 142 | std::string channelName = exrHeader.channels[c].name; 143 | std::transform(channelName.begin(), channelName.end(), channelName.begin(), ::tolower); 144 | if (channelName == "r") 145 | { 146 | idxR = c; 147 | ++numRecognizedChannels; 148 | } 149 | else if (channelName == "g") 150 | { 151 | idxG = c; 152 | ++numRecognizedChannels; 153 | } 154 | else if (channelName == "b") 155 | { 156 | idxB = c; 157 | ++numRecognizedChannels; 158 | } 159 | else if (channelName == "a") 160 | { 161 | ++numRecognizedChannels; 162 | } 163 | } 164 | 165 | auto rawImgChn = reinterpret_cast(exrImage.images); 166 | bool loaded = false; 167 | 168 | hdrPixels = new float[imgWidth * imgHeight * 3]; 169 | 170 | 171 | if (numRecognizedChannels == 1) // 1 channel images can be loaded into either scalar or vector formats. 172 | { 173 | for (int y = 0; y < imgHeight; y++) 174 | { 175 | for (int x = 0; x < imgWidth; x++) 176 | { 177 | int linearIdx = y * imgWidth + x; 178 | float color(rawImgChn[0][linearIdx]); 179 | hdrPixels[3 * linearIdx + 0] = color; 180 | hdrPixels[3 * linearIdx + 1] = color; 181 | hdrPixels[3 * linearIdx + 2] = color; 182 | } 183 | } 184 | loaded = true; 185 | } 186 | else if (numRecognizedChannels == 2) // 2 channel images can only be loaded into vector2/3/4 formats. 187 | { 188 | assert(idxR != -1 && idxG != -1); 189 | #pragma omp parallel for 190 | for (int y = 0; y < imgHeight; y++) 191 | { 192 | for (int x = 0; x < imgWidth; x++) 193 | { 194 | int linearIdx = y * imgWidth + x; 195 | hdrPixels[3 * linearIdx + 0] = rawImgChn[idxR][linearIdx]; 196 | hdrPixels[3 * linearIdx + 1] = rawImgChn[idxG][linearIdx]; 197 | hdrPixels[3 * linearIdx + 2] = 0.0f; 198 | } 199 | } 200 | loaded = true; 201 | } 202 | else if (numRecognizedChannels == 3 || numRecognizedChannels == 4) // 3 or 4 channel images can only be loaded into vector3/4 formats. 203 | { 204 | assert(idxR != -1 && idxG != -1 && idxB != -1); 205 | #pragma omp parallel for 206 | for (int y = 0; y < imgHeight; y++) 207 | { 208 | for (int x = 0; x < imgWidth; x++) 209 | { 210 | int linearIdx = y * imgWidth + x; 211 | hdrPixels[3 * linearIdx + 0] = rawImgChn[idxR][linearIdx]; 212 | hdrPixels[3 * linearIdx + 1] = rawImgChn[idxG][linearIdx]; 213 | hdrPixels[3 * linearIdx + 2] = rawImgChn[idxB][linearIdx]; 214 | } 215 | } 216 | loaded = true; 217 | } 218 | 219 | FreeEXRHeader(&exrHeader); 220 | FreeEXRImage(&exrImage); 221 | 222 | if (!loaded) 223 | { 224 | std::cerr << "Insufficient target channels when loading EXR: need " << exrHeader.num_channels << std::endl; 225 | return false; 226 | } 227 | else 228 | { 229 | return true; 230 | } 231 | } 232 | 233 | // Note that when an image us loaded, the variable pixels is allocated, and it is up to the user to deallocate it later. 234 | bool loadImage(const std::string& fileName, int& imgWidth, int& imgHeight, float*& pixels) 235 | { 236 | bool bOk = false; 237 | std::string extension = fileName.substr(fileName.find_last_of(".") + 1); 238 | if (extension == "png" || extension == "bmp" || extension == "tga") 239 | { 240 | bOk = ldrLoad(fileName, imgWidth, imgHeight, pixels); 241 | } 242 | else if (extension == "exr") 243 | { 244 | bOk = hdrLoad(fileName, imgWidth, imgHeight, pixels); 245 | } 246 | 247 | return bOk; 248 | } 249 | 250 | bool load(FLIP::image& dstImage, const std::string& fileName) 251 | { 252 | int imgWidth; 253 | int imgHeight; 254 | float* pixels; 255 | if (loadImage(fileName, imgWidth, imgHeight, pixels)) 256 | { 257 | dstImage.setPixels(pixels, imgWidth, imgHeight); 258 | return true; 259 | } 260 | return false; 261 | } 262 | 263 | bool pngSave(const std::string& filename, FLIP::image& image) 264 | { 265 | unsigned char* pixels = new unsigned char[3 * image.getWidth() * image.getHeight()]; 266 | 267 | #ifdef FLIP_ENABLE_CUDA 268 | image.synchronizeHost(); 269 | #endif 270 | 271 | #pragma omp parallel for 272 | for (int y = 0; y < image.getHeight(); y++) 273 | { 274 | for (int x = 0; x < image.getWidth(); x++) 275 | { 276 | int index = image.index(x, y); 277 | FLIP::color3 color = image.get(x, y); 278 | 279 | pixels[3 * index + 0] = (unsigned char)(255.0f * std::clamp(color.x, 0.0f, 1.0f) + 0.5f); 280 | pixels[3 * index + 1] = (unsigned char)(255.0f * std::clamp(color.y, 0.0f, 1.0f) + 0.5f); 281 | pixels[3 * index + 2] = (unsigned char)(255.0f * std::clamp(color.z, 0.0f, 1.0f) + 0.5f); 282 | } 283 | } 284 | 285 | int ok = stbi_write_png(filename.c_str(), image.getWidth(), image.getHeight(), 3, pixels, 3 * image.getWidth()); 286 | delete[] pixels; 287 | 288 | return (ok != 0); 289 | } 290 | 291 | bool exrSave(const std::string& fileName, FLIP::image& image) 292 | { 293 | #ifdef FLIP_ENABLE_CUDA 294 | image.synchronizeHost(); 295 | #endif 296 | constexpr int channels = 3; 297 | 298 | float* vpImage[channels] = {}; 299 | std::vector vImages[channels]; 300 | for (int i = 0; i < channels; ++i) 301 | { 302 | vImages[i].resize(image.getWidth() * image.getHeight()); 303 | } 304 | int pixelIndex = 0; 305 | for (int y = 0; y < image.getHeight(); y++) 306 | { 307 | for (int x = 0; x < image.getWidth(); x++) 308 | { 309 | FLIP::color3 p = image.get(x, y); 310 | vImages[0][pixelIndex] = p.r; 311 | vImages[1][pixelIndex] = p.g; 312 | vImages[2][pixelIndex] = p.b; 313 | pixelIndex++; 314 | } 315 | } 316 | vpImage[0] = &(vImages[2].at(0)); // B 317 | vpImage[1] = &(vImages[1].at(0)); // G 318 | vpImage[2] = &(vImages[0].at(0)); // R 319 | 320 | EXRHeader exrHeader; 321 | InitEXRHeader(&exrHeader); 322 | exrHeader.num_channels = channels; 323 | exrHeader.channels = (EXRChannelInfo*)malloc(channels * sizeof(EXRChannelInfo)); 324 | exrHeader.pixel_types = (int*)malloc(channels * sizeof(int)); 325 | exrHeader.requested_pixel_types = (int*)malloc(channels * sizeof(int)); 326 | exrHeader.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP; 327 | for (int i = 0; i < channels; i++) 328 | { 329 | exrHeader.pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; 330 | exrHeader.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT; 331 | exrHeader.channels[i].name[1] = '\0'; 332 | } 333 | exrHeader.channels[0].name[0] = 'B'; 334 | exrHeader.channels[1].name[0] = 'G'; 335 | exrHeader.channels[2].name[0] = 'R'; 336 | 337 | EXRImage exrImage; 338 | InitEXRImage(&exrImage); 339 | exrImage.num_channels = channels; 340 | exrImage.images = (unsigned char**)vpImage; 341 | exrImage.width = image.getWidth(); 342 | exrImage.height = image.getHeight(); 343 | 344 | const char* error; 345 | int ret = SaveEXRImageToFile(&exrImage, &exrHeader, fileName.c_str(), &error); 346 | if (ret != TINYEXR_SUCCESS) 347 | { 348 | std::cerr << "Failed to save EXR file <" << fileName << ">: " << error << "\n"; 349 | return false; 350 | } 351 | 352 | free(exrHeader.channels); 353 | free(exrHeader.pixel_types); 354 | free(exrHeader.requested_pixel_types); 355 | 356 | return true; 357 | } 358 | } -------------------------------------------------------------------------------- /src/cpp/tool/pooling.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its 15 | * contributors may be used to endorse or promote products derived from 16 | * this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | * SPDX-License-Identifier: BSD-3-Clause 31 | */ 32 | 33 | // Visualizing and Communicating Errors in Rendered Images 34 | // Ray Tracing Gems II, 2021, 35 | // by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | // Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | // Visualizing Errors in Rendered High Dynamic Range Images 39 | // Eurographics 2021, 40 | // by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | // Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | // FLIP: A Difference Evaluator for Alternating Images 44 | // High Performance Graphics 2020, 45 | // by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | // Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | // Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | // Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 50 | 51 | #pragma once 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | #include 61 | 62 | 63 | namespace FLIPPooling 64 | { 65 | template 66 | class histogram 67 | { 68 | private: 69 | T mMinValue, mMaxValue; 70 | size_t mValueCount, mErrorValueCount; 71 | T mBucketSize; 72 | std::vector mvBuckets; 73 | size_t mBucketIdRange[2]; 74 | size_t mBucketIdMax; 75 | 76 | public: 77 | histogram(size_t buckets, T minValue = 0.0, T maxValue = 1.0) 78 | { 79 | this->init(buckets, minValue, maxValue); 80 | } 81 | 82 | void init(size_t buckets, T minValue, T maxValue, size_t value = 0) 83 | { 84 | this->mMinValue = minValue; 85 | this->mMaxValue = maxValue; 86 | this->mValueCount = 0; 87 | this->mErrorValueCount = 0; 88 | this->mBucketIdRange[0] = std::string::npos; 89 | this->mBucketIdRange[1] = 0; 90 | this->resize(buckets); 91 | this->mvBuckets.resize(buckets, value); 92 | } 93 | 94 | T getBucketSize() const { return mBucketSize; } 95 | size_t getBucketIdMin() const { return mBucketIdRange[0]; } 96 | size_t getBucketIdMax() const { return mBucketIdRange[1]; } 97 | size_t getBucketValue(size_t bucketId) const { return mvBuckets[bucketId]; } 98 | size_t size() const { return mvBuckets.size(); } 99 | T getMinValue() const { return this->mMinValue; } 100 | T getMaxValue() const { return this->mMaxValue; } 101 | T getBucketStep() const { return (this->mMaxValue - this->mMinValue) / this->mvBuckets.size(); } 102 | 103 | void clear() 104 | { 105 | this->mvBuckets.resize(mvBuckets.size(), 0); 106 | } 107 | 108 | void resize(size_t buckets) 109 | { 110 | this->mBucketSize = (this->mMaxValue - this->mMinValue) / buckets; 111 | this->mvBuckets.resize(buckets); 112 | } 113 | 114 | size_t valueBucketId(T value) const 115 | { 116 | if (value < this->mMinValue || value > this->mMaxValue) 117 | return std::string::npos; 118 | 119 | size_t bucketId = size_t(double(value) / this->mBucketSize); 120 | 121 | if (bucketId == this->mvBuckets.size()) 122 | { 123 | bucketId--; 124 | } 125 | return bucketId; 126 | } 127 | 128 | void inc(T value, size_t amount = 1) 129 | { 130 | size_t bucketId = valueBucketId(value); 131 | if (bucketId != std::string::npos) 132 | { 133 | this->mvBuckets[bucketId] += amount; 134 | this->mValueCount += amount; 135 | this->mBucketIdRange[0] = std::min(this->mBucketIdRange[0], bucketId); 136 | this->mBucketIdRange[1] = std::max(this->mBucketIdRange[1], bucketId); 137 | } 138 | else 139 | { 140 | mErrorValueCount += amount; 141 | } 142 | } 143 | 144 | std::string toPython(const std::string fileName, const size_t numPixels, const T meanValue, const T maxValue, const T minValue, const T weightedMedian, const T firstWeightedQuartile, const T thirdWeightedQuartile, const bool optionLog, const bool includeValues, const float yMax) const 145 | { 146 | std::stringstream ss; 147 | 148 | T bucketStep = getBucketStep(); 149 | 150 | // imports 151 | ss << "import matplotlib.pyplot as plt\n"; 152 | ss << "import os\n"; 153 | ss << "import sys\n"; 154 | ss << "import numpy as np\n"; 155 | ss << "from matplotlib.ticker import (MultipleLocator)\n\n"; 156 | 157 | ss << "dimensions = (25, 15) # centimeters\n\n"; 158 | 159 | ss << "lineColor = 'blue'\n"; 160 | ss << "fillColor = 'lightblue'\n"; 161 | ss << "meanLineColor = 'red'\n"; 162 | ss << "weightedMedianLineColor = 'gray'\n"; 163 | ss << "quartileLineColor = 'purple'\n"; 164 | ss << "fontSize = 14\n"; 165 | ss << "numPixels = " << numPixels << "\n\n"; 166 | 167 | ss << "meanValue = " << meanValue << "\n"; 168 | ss << "maxValue = " << maxValue << "\n"; 169 | ss << "minValue = " << minValue << "\n\n"; 170 | ss << "weightedMedianValue = " << weightedMedian << "\n\n"; 171 | ss << "firstWeightedQuartileValue = " << firstWeightedQuartile << "\n\n"; 172 | ss << "thirdWeightedQuartileValue = " << thirdWeightedQuartile << "\n\n"; 173 | 174 | // X-axis 175 | ss << "dataX = ["; 176 | for (size_t bucketId = 0; bucketId < this->mvBuckets.size(); bucketId++) 177 | { 178 | ss << (bucketId > 0 ? ", " : ""); 179 | ss << bucketStep * bucketId + 0.5 * bucketStep; 180 | } 181 | ss << "]\n\n"; 182 | 183 | // FLIP histogram. 184 | ss << "dataFLIP = ["; 185 | for (size_t bucketId = 0; bucketId < this->mvBuckets.size(); bucketId++) 186 | { 187 | ss << (bucketId > 0 ? ", " : ""); 188 | ss << this->mvBuckets[bucketId]; 189 | } 190 | ss << "]\n\n"; 191 | 192 | // Weighted FLIP histogram. 193 | ss << "bucketStep = " << bucketStep << "\n"; 194 | ss << "weightedDataFLIP = np.empty(" << this->mvBuckets.size() << ")\n"; 195 | ss << "moments = np.empty(" << this->mvBuckets.size() << ")\n"; 196 | ss << "for i in range(" << this->mvBuckets.size() << ") :\n"; 197 | ss << "\tweight = (i + 0.5) * bucketStep\n"; 198 | ss << "\tweightedDataFLIP[i] = dataFLIP[i] * weight\n"; 199 | ss << "weightedDataFLIP /= (numPixels /(1024 * 1024)) # normalized with the number of megapixels in the image\n\n"; 200 | if (optionLog) 201 | { 202 | ss << "for i in range(" << this->mvBuckets.size() << ") :\n"; 203 | ss << "\tif weightedDataFLIP[i] > 0 :\n"; 204 | ss << "\t\tweightedDataFLIP[i] = np.log10(weightedDataFLIP[i]) # avoid log of zero\n\n"; 205 | } 206 | 207 | if (yMax != 0.0f) 208 | { 209 | ss << "maxY = " << yMax << "\n\n"; 210 | } 211 | else 212 | { 213 | ss << "maxY = max(weightedDataFLIP)\n\n"; 214 | } 215 | 216 | ss << "sumWeightedDataFLIP = sum(weightedDataFLIP)\n\n"; 217 | 218 | ss << "font = { 'family' : 'serif', 'style' : 'normal', 'weight' : 'normal', 'size' : fontSize }\n"; 219 | ss << "lineHeight = fontSize / (dimensions[1] * 15)\n"; 220 | ss << "plt.rc('font', **font)\n"; 221 | ss << "fig = plt.figure()\n"; 222 | ss << "axes = plt.axes()\n"; 223 | ss << "axes.xaxis.set_minor_locator(MultipleLocator(0.1))\n"; 224 | ss << "axes.xaxis.set_major_locator(MultipleLocator(0.2))\n\n"; 225 | 226 | ss << "fig.set_size_inches(dimensions[0] / 2.54, dimensions[1] / 2.54)\n"; 227 | 228 | if (optionLog) 229 | { 230 | ss << "axes.set(title = 'Weighted \\uA7FBLIP Histogram', xlabel = '\\uA7FBLIP error', ylabel = 'log(weighted \\uA7FBLIP sum per megapixel)')\n\n"; 231 | } 232 | else 233 | { 234 | ss << "axes.set(title = 'Weighted \\uA7FBLIP Histogram', xlabel = '\\uA7FBLIP error', ylabel = 'Weighted \\uA7FBLIP sum per megapixel')\n\n"; 235 | } 236 | 237 | ss << "plt.bar(dataX, weightedDataFLIP, width = " << bucketStep << ", color = fillColor, edgecolor = lineColor)\n\n"; 238 | 239 | if (includeValues) 240 | { 241 | ss << "plt.text(0.99, 1.0 - 1 * lineHeight, 'Mean: ' + str(f'{meanValue:.4f}'), ha = 'right', fontsize = fontSize, transform = axes.transAxes, color=meanLineColor)\n\n"; 242 | ss << "plt.text(0.99, 1.0 - 2 * lineHeight, 'Weighted median: ' + str(f'{weightedMedianValue:.4f}'), ha = 'right', fontsize = fontSize, transform = axes.transAxes, color=weightedMedianLineColor)\n\n"; 243 | ss << "plt.text(0.99, 1.0 - 3 * lineHeight, '1st weighted quartile: ' + str(f'{firstWeightedQuartileValue:.4f}'), ha = 'right', fontsize = fontSize, transform = axes.transAxes, color=quartileLineColor)\n\n"; 244 | ss << "plt.text(0.99, 1.0 - 4 * lineHeight, '3rd weighted quartile: ' + str(f'{thirdWeightedQuartileValue:.4f}'), ha = 'right', fontsize = fontSize, transform = axes.transAxes, color=quartileLineColor)\n\n"; 245 | ss << "plt.text(0.99, 1.0 - 5 * lineHeight, 'Min: ' + str(f'{minValue:.4f}'), ha = 'right', fontsize = fontSize, transform = axes.transAxes)\n"; 246 | ss << "plt.text(0.99, 1.0 - 6 * lineHeight, 'Max: ' + str(f'{maxValue:.4f}'), ha = 'right', fontsize = fontSize, transform = axes.transAxes)\n"; 247 | } 248 | 249 | ss << "axes.set_xlim(0.0, 1.0)\n"; 250 | ss << "axes.set_ylim(0.0, maxY * 1.05)\n"; 251 | 252 | if (includeValues) 253 | { 254 | ss << "axes.axvline(x = meanValue, color = meanLineColor, linewidth = 1.5)\n\n"; 255 | ss << "axes.axvline(x = weightedMedianValue, color = weightedMedianLineColor, linewidth = 1.5)\n\n"; 256 | ss << "axes.axvline(x = firstWeightedQuartileValue, color = quartileLineColor, linewidth = 1.5)\n\n"; 257 | ss << "axes.axvline(x = thirdWeightedQuartileValue, color = quartileLineColor, linewidth = 1.5)\n\n"; 258 | ss << "axes.axvline(x = minValue, color='black', linestyle = ':', linewidth = 1.5)\n\n"; 259 | ss << "axes.axvline(x = maxValue, color='black', linestyle = ':', linewidth = 1.5)\n\n"; 260 | } 261 | 262 | ss << "plt.savefig(\"" << fileName.substr(0, fileName.size() - 3) << ".pdf\")"; 263 | 264 | ss << std::endl; 265 | 266 | return ss.str(); 267 | } 268 | }; 269 | 270 | template 271 | class pooling 272 | { 273 | private: 274 | size_t mValueCount; 275 | T mValueSum; 276 | T mSquareValueSum; 277 | T mMinValue; 278 | T mMaxValue; 279 | uint32_t mMinCoord[2]; 280 | uint32_t mMaxCoord[2]; 281 | histogram mHistogram = histogram(100); 282 | bool mDataSorted = false; 283 | std::vector mvData; 284 | 285 | public: 286 | pooling() { clear(); } 287 | pooling(size_t buckets) { mHistogram.resize(buckets); clear(); } 288 | 289 | histogram& getHistogram() { return mHistogram; } 290 | T getMinValue(void) const { return mMinValue; } 291 | T getMaxValue(void) const { return mMaxValue; } 292 | T getMean(void) const { return mValueSum / mValueCount; } 293 | 294 | double getWeightedPercentile(const double percent) const 295 | { 296 | double weight; 297 | double weightedValue; 298 | double bucketStep = mHistogram.getBucketStep(); 299 | double sumWeightedDataValue = 0.0; 300 | for (size_t bucketId = 0; bucketId < mHistogram.size(); bucketId++) 301 | { 302 | weight = (bucketId + 0.5) * bucketStep; 303 | weightedValue = mHistogram.getBucketValue(bucketId) * weight; 304 | sumWeightedDataValue += weightedValue; 305 | } 306 | 307 | double sum = 0; 308 | size_t weightedMedianIndex = 0; 309 | for (size_t bucketId = 0; bucketId < mHistogram.size(); bucketId++) 310 | { 311 | weight = (bucketId + 0.5) * bucketStep; 312 | weightedValue = mHistogram.getBucketValue(bucketId) * weight; 313 | weightedMedianIndex = bucketId; 314 | if (sum + weightedValue > percent * sumWeightedDataValue) 315 | break; 316 | sum += weightedValue; 317 | } 318 | 319 | weight = (weightedMedianIndex + 0.5) * bucketStep; 320 | weightedValue = mHistogram.getBucketValue(weightedMedianIndex) * weight; 321 | double discrepancy = percent * sumWeightedDataValue - sum; 322 | double linearWeight = discrepancy / weightedValue; // In [0,1]. 323 | double percentile = (weightedMedianIndex + linearWeight) * bucketStep; 324 | return percentile; 325 | } 326 | 327 | T getPercentile(const float percent, const bool bWeighted = false) 328 | { 329 | if (!mDataSorted) 330 | { 331 | std::sort(mvData.begin(), mvData.end()); 332 | mDataSorted = true; 333 | } 334 | 335 | T percentile = T(0); 336 | if (bWeighted) 337 | { 338 | T runningSum = T(0); 339 | for (size_t i = 0; i < mvData.size(); i++) 340 | { 341 | runningSum += mvData[i]; 342 | if (runningSum > percent * mValueSum) 343 | { 344 | percentile = mvData[i]; 345 | break; 346 | } 347 | } 348 | } 349 | else 350 | { 351 | // Using the nearest-rank method. 352 | percentile = mvData[size_t(std::ceil(mvData.size() * percent))]; 353 | } 354 | 355 | return percentile; 356 | } 357 | 358 | void clear() 359 | { 360 | mValueCount = 0; 361 | mValueSum = T(0); 362 | mSquareValueSum = T(0); 363 | mMinValue = std::numeric_limits::max(); 364 | mMaxValue = std::numeric_limits::min(); 365 | mvData.clear(); 366 | mHistogram.clear(); 367 | } 368 | 369 | void update(uint32_t xcoord, uint32_t ycoord, T value) 370 | { 371 | mValueCount++; 372 | mValueSum += value; 373 | mSquareValueSum += (value * value); 374 | mHistogram.inc(value); 375 | 376 | mvData.push_back(value); 377 | 378 | mDataSorted = false; 379 | 380 | if (value < mMinValue) 381 | { 382 | mMinValue = value; 383 | mMinCoord[0] = xcoord; 384 | mMinCoord[1] = ycoord; 385 | } 386 | 387 | if (value > mMaxValue) 388 | { 389 | mMaxValue = value; 390 | mMaxCoord[0] = xcoord; 391 | mMaxCoord[1] = ycoord; 392 | } 393 | } 394 | 395 | void save(const std::string& fileName, size_t imgWidth, size_t imgHeight, const bool optionLog, const bool includeValues, const float yMax) 396 | { 397 | std::ofstream file; 398 | std::string pyFileName = fileName; 399 | 400 | size_t area = size_t(imgWidth) * size_t(imgHeight); 401 | 402 | // Python output. 403 | std::ofstream pythonHistogramFile; 404 | pythonHistogramFile.open(pyFileName); 405 | pythonHistogramFile << mHistogram.toPython(pyFileName, area, getMean(), getMaxValue(), getMinValue(), getPercentile(0.5f, true), getPercentile(0.25f, true), getPercentile(0.75f, true), optionLog, includeValues, yMax); 406 | pythonHistogramFile.close(); 407 | } 408 | 409 | void saveOverlappedHistogram(pooling firstPooledValues, const std::string& fileName, const size_t imgWidth, const size_t imgHeight, const bool optionLog, const std::string referenceFileName, const std::string firstTestFileName, const std::string testFileName, const bool includeValues, const float yMax) const 410 | { 411 | // Create histogram. 412 | size_t numPixels = size_t(imgWidth) * size_t(imgHeight); 413 | 414 | size_t bucketsSize = firstPooledValues.getHistogram().size(); 415 | T bucketStep = this->mHistogram.getBucketStep(); 416 | 417 | float firstPooledValuesMean = firstPooledValues.getMean(); 418 | float meanValue = this->getMean(); 419 | 420 | std::stringstream ss; 421 | 422 | // Imports. 423 | ss << "import matplotlib.pyplot as plt\n"; 424 | ss << "import sys\n"; 425 | ss << "import numpy as np\n"; 426 | ss << "from matplotlib.ticker import (MultipleLocator)\n\n"; 427 | 428 | ss << "dimensions = (25, 15) # centimeters\n\n"; 429 | 430 | ss << "lineColor = 'green'\n"; 431 | ss << "lineColor2 = 'blue'\n"; 432 | ss << "fillColorBelow = [118 / 255, 185 / 255, 0]\n"; 433 | ss << "fillColorAbove = 'lightblue'\n"; 434 | ss << "meanLineColor = [107 / 255, 168 / 255, 0]\n"; 435 | ss << "meanLineColor2 = [113 / 255, 171 / 255, 189 / 255]\n"; 436 | ss << "fontSize = 14\n\n"; 437 | ss << "numPixels = " << numPixels << "\n\n"; 438 | 439 | ss << "font = { 'family' : 'serif', 'style' : 'normal', 'weight' : 'normal', 'size' : fontSize }\n"; 440 | ss << "lineHeight = fontSize / (dimensions[1] * 15)\n"; 441 | ss << "plt.rc('font', **font)\n\n"; 442 | 443 | ss << "fig = plt.figure()\n"; 444 | ss << "fig.set_size_inches(dimensions[0] / 2.54, dimensions[1] / 2.54)\n"; 445 | 446 | ss << "meanValue = " << firstPooledValuesMean << "\n"; 447 | ss << "meanValue2 = " << meanValue << "\n"; 448 | 449 | // X-axis. 450 | ss << "dataX = ["; 451 | for (size_t bucketId = 0; bucketId < bucketsSize; bucketId++) 452 | { 453 | ss << (bucketId > 0 ? ", " : ""); 454 | ss << bucketStep * bucketId + 0.5 * bucketStep; 455 | } 456 | ss << "]\n\n"; 457 | 458 | // FLIP histogram. 459 | ss << "dataFLIP = ["; 460 | for (size_t bucketId = 0; bucketId < bucketsSize; bucketId++) 461 | { 462 | ss << (bucketId > 0 ? ", " : ""); 463 | ss << firstPooledValues.getHistogram().getBucketValue(bucketId); 464 | } 465 | ss << "]\n\n"; 466 | ss << "dataFLIP2 = ["; 467 | for (size_t bucketId = 0; bucketId < bucketsSize; bucketId++) 468 | { 469 | ss << (bucketId > 0 ? ", " : ""); 470 | ss << this->mHistogram.getBucketValue(bucketId); 471 | } 472 | ss << "]\n\n"; 473 | 474 | // Weighted FLIP histogram. 475 | ss << "bucketStep = " << bucketStep << "\n"; 476 | ss << "weightedDataFLIP = np.empty(" << bucketsSize << ")\n"; 477 | ss << "weightedDataFLIP2 = np.empty(" << bucketsSize << ")\n"; 478 | ss << "for i in range(" << bucketsSize << ") :\n"; 479 | ss << "\tweight = (i + 0.5) * bucketStep\n"; 480 | ss << "\tweightedDataFLIP[i] = dataFLIP[i] * weight\n"; 481 | ss << "\tweightedDataFLIP2[i] = dataFLIP2[i] * weight\n"; 482 | ss << "weightedDataFLIP /= (numPixels /(1024 * 1024)) # normalized with the number of megapixels in the image\n"; 483 | ss << "weightedDataFLIP2 /= (numPixels /(1024 * 1024)) # normalized with the number of megapixels in the image\n\n"; 484 | if (optionLog) 485 | { 486 | ss << "for i in range(" << bucketsSize << ") :\n"; 487 | ss << "\tif weightedDataFLIP[i] > 0 :\n"; 488 | ss << "\t\tweightedDataFLIP[i] = np.log10(weightedDataFLIP[i]) # avoid log of zero\n"; 489 | ss << "\tif weightedDataFLIP2[i] > 0 :\n"; 490 | ss << "\t\tweightedDataFLIP2[i] = np.log10(weightedDataFLIP2[i]) # avoid log of zero\n\n"; 491 | } 492 | 493 | if (yMax != 0.0f) 494 | { 495 | ss << "maxY = " << yMax << "\n\n"; 496 | } 497 | else 498 | { 499 | ss << "maxY = 1.05 * max(max(weightedDataFLIP), max(weightedDataFLIP2))\n\n"; 500 | } 501 | 502 | ss << "axes = plt.axes()\n"; 503 | ss << "axes.xaxis.set_minor_locator(MultipleLocator(0.1))\n"; 504 | ss << "axes.xaxis.set_major_locator(MultipleLocator(0.2))\n"; 505 | ss << "axes.set_xlim(0.0, 1.0)\n"; 506 | ss << "axes.set_ylim(0.0, maxY)\n\n"; 507 | 508 | if (optionLog) 509 | { 510 | ss << "axes.set(title = 'Weighted \\uA7FBLIP Histogram', xlabel = '\\uA7FBLIP error', ylabel = 'log(weighted \\uA7FBLIP sum per megapixel)')\n"; 511 | } 512 | else 513 | { 514 | ss << "axes.set(title = 'Weighted \\uA7FBLIP Histogram', xlabel = '\\uA7FBLIP error', ylabel = 'Weighted \\uA7FBLIP sum per megapixel')\n"; 515 | } 516 | 517 | if (includeValues) 518 | { 519 | ss << "if " << includeValues << ":\n"; 520 | ss << "\taxes.axvline(x = meanValue, color = meanLineColor, linewidth = 1.5)\n"; 521 | ss << "\taxes.axvline(x = meanValue2, color = meanLineColor2, linewidth = 1.5)\n"; 522 | ss << "\tplt.text(0.99, 1.0 - 1 * lineHeight, 'Mean (" << firstTestFileName << "): ' + str(f'{meanValue:.4f}'), ha = 'right', fontsize = fontSize, transform = axes.transAxes, color = meanLineColor)\n"; 523 | ss << "\tplt.text(0.99, 1.0 - 2 * lineHeight, 'Mean (" << testFileName << "): ' + str(f'{meanValue2:.4f}'), ha = 'right', fontsize = fontSize, transform = axes.transAxes, color = meanLineColor2)\n"; 524 | } 525 | 526 | 527 | ss << "sumWeightedDataFLIP = sum(weightedDataFLIP)\n\n"; 528 | ss << "sumWeightedDataFLIP2 = sum(weightedDataFLIP2)\n\n"; 529 | 530 | ss << "bins = np.arange(101) / 100\n"; 531 | ss << "plt.hist(bins[:-1], bins = bins, weights = weightedDataFLIP, ec = lineColor, alpha = 0.5, color = fillColorBelow)\n"; 532 | ss << "plt.hist(bins[:-1], bins = bins, weights = weightedDataFLIP2, ec = lineColor2, alpha = 0.5, color = fillColorAbove)\n\n"; 533 | 534 | // Construct final histogram filename. 535 | std::string finalFileName; 536 | std::string subFileName = fileName; 537 | size_t lastIndex = subFileName.find_last_of("/"); 538 | finalFileName = subFileName.substr(0, lastIndex) + "/" + (optionLog ? "log_" : "") + "overlapping_weighted_histogram.flip." + referenceFileName + "."; 539 | subFileName = subFileName.substr(lastIndex + 1); 540 | size_t firstIndex; 541 | for (int q = 0; q < 3; q++) 542 | { 543 | firstIndex = subFileName.find_first_of("."); 544 | subFileName = subFileName.substr(firstIndex + 1); 545 | } 546 | finalFileName.append(firstTestFileName).append(".").append(subFileName); 547 | 548 | ss << "plt.savefig(\"" << finalFileName.substr(0, finalFileName.size() - 3) << ".pdf\")"; 549 | 550 | ss << std::endl; 551 | 552 | // Save histogram (see save() above). 553 | std::ofstream file; 554 | std::string pyFileName = finalFileName; 555 | std::ofstream pythonHistogramFile; 556 | pythonHistogramFile.open(pyFileName); 557 | pythonHistogramFile << ss.str(); 558 | pythonHistogramFile.close(); 559 | } 560 | }; 561 | } 562 | -------------------------------------------------------------------------------- /src/flip_evaluator/__init__.py: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | from .flip_python_api import evaluate, load -------------------------------------------------------------------------------- /src/flip_evaluator/flip_python_api.py: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | from flip_evaluator import nbflip 34 | import sys, os 35 | 36 | def evaluate(reference, test, dynamicRangeString, inputsRGB=True, applyMagma=True, computeMeanError=True, parameters={}): 37 | """ 38 | Compute the FLIP error map between two images. 39 | 40 | :param reference: string containing relative or absolute path to reference image OR 41 | numpy array containing reference image (with HxWxC layout and nonnegative values in the case of HDR and values in [0,1] in the case of LDR) 42 | :param test: string containing relative or absolute path to test image OR 43 | numpy array containing test image (with HxWxC layout and nonnegative values in the case of HDR and values in [0,1] in the case of LDR) 44 | :param dynamicRangeString: string describing the assumed dynamic range of the reference and test image, valid arguments are \"LDR\" (low dynamic range) and \"HDR\" (high dynamic range) 45 | :param inputsRGB: (optional) bool describing if input images are in sRGB. LDR images are assumed to be sRGB while HDR images are assumed to be in linear sRGB 46 | :param applyMagma: (optional) bool saying if the magma color map should be applied to the error map, default is True 47 | :param computeMeanError: (optional) bool saying if mean FLIP error should be computed, default is True. If False, -1 is returned. 48 | :param parameters: (optional) dictionary containing non-default settings for the FLIP evaluation: 49 | \"ppd\": float describing assumed pixels per degree of visual angle (default is 67 PPD). Should not be included in the dictionary if \"vc\" is included, and vice versa 50 | \"vc\": list of three floats. First float is the distance to display (meters), second is display width (pixels), and third is display width (meters). Should not be included if \"ppd\" is included, and vice versa 51 | \"startExposure\": float setting the start exposre (c_start). Its value is computed by FLIP if not entered. startExposure must be smaller than or equal to stopExposure 52 | \"startExposure\": float setting the stop exposre (c_stop). Its value is computed by FLIP if not entered. stopExposure must be greater than or equal to startExposure 53 | \"numExposures\": int setting the number of exposure steps. Its value is computed by FLIP if not entered 54 | \"tonemapper\": string setting the assumed tone mapper. Allowed options are \"ACES\", \"Hable\", and \"Reinhard\" 55 | :return: tuple containing 1: the FLIP error map, 2: the mean FLIP error (computed if computeMeanError is True, else -1), 3: the parameter dictionary used to compute the results 56 | """ 57 | # Set correct bools based on if HDR- or LDR-FLIP should be used. 58 | if dynamicRangeString.lower() == "hdr": 59 | useHDR = True 60 | inputsRGB = False # Assume HDR input is in linear RGB. 61 | elif dynamicRangeString.lower() == "ldr": 62 | useHDR = False 63 | else: 64 | print("Third argument to evaluate() must be \"LDR\" or \"HDR\".\nExiting.") 65 | sys.exit(1) 66 | 67 | # Check that parameters only include settings for either viewing conditions or pixels per degree. 68 | if "vc" in parameters and "ppd" in parameters: 69 | print("\"vc\" and \"ppd\" are mutually exclusive. Use only one of the two.\nExiting.") 70 | sys.exit(1) 71 | 72 | # Check that PPD values are valid: 73 | if "vc" in parameters: 74 | if parameters["vc"][0] <= 0 or parameters["vc"][1] <= 0 or parameters["vc"][2] <= 0: 75 | print("Viewing condition options must be positive.\nExiting.") 76 | sys.exit(1) 77 | elif "ppd" in parameters: 78 | if parameters["ppd"] <= 0: 79 | print("The number of pixels per degree must be positive.\nExiting.") 80 | sys.exit(1) 81 | 82 | # Check that tonemapper is valid. 83 | if "tonemapper" in parameters and parameters["tonemapper"].lower() not in ["aces", "hable", "reinhard"]: 84 | print("Invalid tonemapper. Valid options are \"ACES\", \"Hable\", and \"Reinhard\".\nExiting.") 85 | sys.exit(1) 86 | 87 | # If strings are input, load images before evaluation. 88 | if isinstance(reference, str): 89 | if not os.path.exists(reference): 90 | print("Path to reference image is invalid, or the reference image does not exist.\nExiting.") 91 | sys.exit(1) 92 | reference = nbflip.load(reference) 93 | if isinstance(test, str): 94 | if not os.path.exists(test): 95 | print("Path to test image is invalid, or the reference image does not exist.\nExiting.") 96 | sys.exit(1) 97 | test = nbflip.load(test) 98 | 99 | # Evaluate FLIP. Return error map, mean FLIP error, and dictionary containing the parameters used. 100 | return nbflip.evaluate(reference, test, useHDR, inputsRGB, applyMagma, computeMeanError, parameters) 101 | 102 | def load(imgpath): 103 | """ 104 | Load an image. 105 | 106 | :param imgpath: string containing the relative or absolute path to an image, allowed file types are png, exr, bmp, and tga 107 | :return: numpy array containing the image (with HxWxC layout) 108 | """ 109 | return nbflip.load(imgpath) 110 | 111 | def main(): 112 | nbflip.execute(sys.argv) 113 | 114 | if __name__ == '__main__': 115 | main() -------------------------------------------------------------------------------- /src/nanobindFLIP.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | * 4 | * Redistribution and use in source and binary forms, with or without 5 | * modification, are permitted provided that the following conditions are met: 6 | * 7 | * 1. Redistributions of source code must retain the above copyright notice, this 8 | * list of conditions and the following disclaimer. 9 | * 10 | * 2. Redistributions in binary form must reproduce the above copyright notice, 11 | * this list of conditions and the following disclaimer in the documentation 12 | * and/or other materials provided with the distribution. 13 | * 14 | * 3. Neither the name of the copyright holder nor the names of its 15 | * contributors may be used to endorse or promote products derived from 16 | * this software without specific prior written permission. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | * 29 | * SPDX-FileCopyrightText: Copyright (c) 2020-2025 NVIDIA CORPORATION & AFFILIATES 30 | * SPDX-License-Identifier: BSD-3-Clause 31 | */ 32 | 33 | // Visualizing and Communicating Errors in Rendered Images 34 | // Ray Tracing Gems II, 2021, 35 | // by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | // Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | // Visualizing Errors in Rendered High Dynamic Range Images 39 | // Eurographics 2021, 40 | // by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | // Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | // FLIP: A Difference Evaluator for Alternating Images 44 | // High Performance Graphics 2020, 45 | // by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | // Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | // Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | // Code by Pontus Ebelin (formerly Andersson) and Tomas Akenine-Moller. 50 | 51 | #include 52 | #include 53 | #include 54 | 55 | #include "cpp/FLIP.h" 56 | #include "cpp/tool/FLIPToolHelpers.h" 57 | 58 | namespace nb = nanobind; 59 | 60 | using Array3D = nb::ndarray, nb::device::cpu>; 61 | 62 | Array3D createThreeChannelImage(float*& data, const size_t imageHeight, const size_t imageWidth, const size_t numChannels) 63 | { 64 | // Create an ndarray from the raw data and an owner to make sure the data is deallocated when no longer used. 65 | nb::capsule owner(data, [](void* p) noexcept { 66 | delete[](float*) p; 67 | }); 68 | 69 | // Allocate 3D array 70 | Array3D image(data, { imageHeight, imageWidth, numChannels }, owner); 71 | 72 | return image; 73 | } 74 | 75 | // Load the .exr, .png, .bmp, or .tga file with path fileName. 76 | Array3D load(std::string fileName) 77 | { 78 | FLIP::image image; 79 | bool imageOk = ImageHelpers::load(image, fileName); 80 | if (!imageOk) 81 | { 82 | std::cout << "Error: could not read image file <" << fileName << ">. Note that FLIP only loads png, bmp, tga, and exr images. Exiting.\n"; 83 | exit(EXIT_FAILURE); 84 | } 85 | 86 | const size_t imageWidth = image.getWidth(); 87 | const size_t imageHeight = image.getHeight(); 88 | const size_t channels = 3; 89 | 90 | // Allocate memory for the ndarray. 91 | float* data = new float[imageWidth * imageHeight * channels * sizeof(float)]; 92 | 93 | // Copy the image data to the allocated memory. 94 | memcpy(data, image.getHostData(), imageWidth * imageHeight * sizeof(float) * channels); 95 | 96 | // Create an ndarray from the raw data and an owner to make sure the data is deallocated when no longer used. 97 | Array3D numpyImage = createThreeChannelImage(data, imageHeight, imageWidth, channels); 98 | 99 | return numpyImage; 100 | } 101 | 102 | // Convert linear RGB image to sRGB. 103 | void sRGBToLinearRGB(float* image, const size_t imageWidth, const size_t imageHeight) 104 | { 105 | #pragma omp parallel for 106 | for (int y = 0; y < imageHeight; y++) 107 | { 108 | for (int x = 0; x < imageWidth; x++) 109 | { 110 | size_t idx = (y * imageWidth + x) * 3; 111 | image[idx] = FLIP::color3::sRGBToLinearRGB(image[idx]); 112 | image[idx + 1] = FLIP::color3::sRGBToLinearRGB(image[idx + 1]); 113 | image[idx + 2] = FLIP::color3::sRGBToLinearRGB(image[idx + 2]); 114 | } 115 | } 116 | } 117 | 118 | // Set parameters for evaluate function based on input settings. 119 | FLIP::Parameters setParameters(nb::dict inputParameters) 120 | { 121 | FLIP::Parameters parameters; 122 | 123 | for (auto item : inputParameters) 124 | { 125 | std::string key = nb::cast(item.first); 126 | std::string errorMessage = "Unrecognized parameter dictionary key or invalid value type. Available ones are \"ppd\" (float), \"startExposure\" (float), \"stopExposure\" (float), \"numExposures\" (int), and \"tonemapper\" (string)."; 127 | if (key == "ppd") 128 | { 129 | parameters.PPD = nb::cast(item.second); 130 | } 131 | else if (key == "vc") 132 | { 133 | auto vc = nb::cast(item.second); 134 | float distanceToDisplay = nb::cast(vc[0]); 135 | float displayWidthPixels = nb::cast(vc[1]); 136 | float displayWidthMeters = nb::cast(vc[2]); 137 | parameters.PPD = FLIP::calculatePPD(distanceToDisplay, displayWidthPixels, displayWidthMeters); 138 | } 139 | else if (key == "startExposure") 140 | { 141 | parameters.startExposure = nb::cast(item.second); 142 | } 143 | else if (key == "stopExposure") 144 | { 145 | parameters.stopExposure = nb::cast(item.second); 146 | } 147 | else if (key == "numExposures") 148 | { 149 | parameters.numExposures = nb::cast(item.second); 150 | } 151 | else if (key == "tonemapper") 152 | { 153 | parameters.tonemapper = nb::cast(item.second); 154 | } 155 | else 156 | { 157 | std::cout << errorMessage << std::endl; 158 | } 159 | } 160 | 161 | return parameters; 162 | } 163 | 164 | // Update parameter dictionary that is returned to the Python side. 165 | void updateInputParameters(const FLIP::Parameters& parameters, nb::dict& inputParameters) 166 | { 167 | inputParameters["ppd"] = parameters.PPD; 168 | inputParameters["startExposure"] = parameters.startExposure; 169 | inputParameters["stopExposure"] = parameters.stopExposure; 170 | inputParameters["numExposures"] = parameters.numExposures; 171 | inputParameters["tonemapper"] = parameters.tonemapper; 172 | } 173 | 174 | /** A simplified function for evaluating (the image metric called) FLIP between a reference image and a test image. Corresponds to the fourth evaluate() option in FLIP.h. 175 | * 176 | * @param[in] referenceInput Reference input image. For LDR, the content should be in [0,1]. The image is expected to have 3 floats per pixel. 177 | * @param[in] testInput Test input image. For LDR, the content should be in [0,1]. The image is expected to have 3 floats per pixel. 178 | * @param[in] useHDR Set to true if the input images are to be considered containing HDR content, i.e., not necessarily in [0,1]. 179 | * @param[in] inputsRGB Set to true if the input images are given in the sRGB color space. 180 | * @param[in] applyMagma A boolean indicating whether the output should have the Magma map applied to it before the image is returned. 181 | * @param[in] computeMeanFLIPError A boolean indicating whether the mean FLIP error should be computed. If false, the returned mean error is -1. 182 | * @param[in,out] inputParameters Contains parameters (e.g., PPD, exposure settings, etc). If the exposures have not been set by the user, then those will be computed (and returned). 183 | * @return tuple containing FLIP error map (in Magma if applyMagma is true), the mean FLIP error (computed if computeMeanFLIPError is true, else -1), and dictionary of parameters. 184 | */ 185 | nb::tuple evaluate(const Array3D referenceInput, const Array3D testInput, const bool useHDR, const bool inputsRGB = true, const bool applyMagma = true, const bool computeMeanFLIPError = true, nb::dict inputParameters = {}) 186 | { 187 | size_t r_ndim = referenceInput.ndim(); 188 | size_t t_ndim = testInput.ndim(); 189 | 190 | // Check number of dimensions and resolution. 191 | if (r_ndim != 3 || t_ndim != 3) 192 | { 193 | std::stringstream message; 194 | message << "Number of dimensions must be three. The reference image has " << r_ndim << " dimensions, while the test image has "<< t_ndim << " dimensions."; 195 | throw std::runtime_error(message.str()); 196 | } 197 | 198 | if (referenceInput.shape(0) != testInput.shape(0) || referenceInput.shape(1) != testInput.shape(1)) 199 | { 200 | std::stringstream message; 201 | message << "Reference and test image resolutions differ.\nReference image resolution: " << referenceInput.shape(0) << "x" << referenceInput.shape(1) << "\nTest image resolution: "<< testInput.shape(0) << "x" << testInput.shape(0); 202 | throw std::runtime_error(message.str()); 203 | } 204 | 205 | // Image size. 206 | const size_t imageHeight = referenceInput.shape(0), imageWidth = referenceInput.shape(1); 207 | const size_t nChannelsOut = applyMagma ? 3 : 1; 208 | 209 | // FLIP 210 | float* flip = nullptr; // Allocated in FLIP::evaluate(). 211 | 212 | // Arrays for reference and test. 213 | float* referenceImage = new float[imageHeight * imageWidth * 3 * sizeof(float)]; 214 | float* testImage = new float[imageHeight * imageWidth * 3 * sizeof(float)]; 215 | memcpy(referenceImage, referenceInput.data(), imageHeight * imageWidth * sizeof(float) * 3); 216 | memcpy(testImage, testInput.data(), imageHeight * imageWidth * sizeof(float) * 3); 217 | 218 | // Transform to linear RGB if desired. 219 | if (inputsRGB) 220 | { 221 | sRGBToLinearRGB(referenceImage, imageWidth, imageHeight); 222 | sRGBToLinearRGB(testImage, imageWidth, imageHeight); 223 | } 224 | 225 | // Run FLIP. 226 | FLIP::Parameters parameters = setParameters(inputParameters); 227 | float meanError = -1; 228 | FLIP::evaluate(referenceImage, testImage, int(imageWidth), int(imageHeight), useHDR, parameters, applyMagma, computeMeanFLIPError, meanError, &flip); 229 | 230 | nb::dict returnParams = {}; 231 | updateInputParameters(parameters, returnParams); 232 | 233 | Array3D flipNumpy = createThreeChannelImage(flip, imageHeight, imageWidth, nChannelsOut); 234 | 235 | delete [] referenceImage; 236 | delete [] testImage; 237 | 238 | return nb::make_tuple(flipNumpy, meanError, returnParams); 239 | } 240 | 241 | // Create command line, based on the Python command line string, for the FLIP tool to parse. 242 | commandline generateCommandLine(const nb::list argvPy) 243 | { 244 | size_t argc = argvPy.size(); 245 | char** argv = new char* [argc]; 246 | 247 | int counter = 0; 248 | for (auto item : argvPy) 249 | { 250 | const std::string it = nb::steal(item).c_str(); 251 | argv[counter] = strdup(it.c_str()); 252 | counter++; 253 | } 254 | 255 | commandline cmd = commandline(int(argc), argv, getAllowedCommandLineOptions(false)); 256 | 257 | for (int i = 0; i < counter; i++) 258 | { 259 | delete [] argv[i]; 260 | } 261 | delete [] argv; 262 | 263 | return cmd; 264 | } 265 | 266 | // Run the FLIP tool based on Python command line string. 267 | int execute(const nb::list argvPy) 268 | { 269 | commandline commandLine = generateCommandLine(argvPy); 270 | FLIPTool::execute(commandLine); 271 | 272 | return EXIT_SUCCESS; 273 | } 274 | 275 | // Setup the pybind11 module. 276 | NB_MODULE(nbflip, handle) 277 | { 278 | handle.doc() = "Load images (load), evaluate FLIP (evaluate), or run the full FLIP tool (execute)."; 279 | handle.def("load", &load); 280 | handle.def("evaluate", &evaluate); 281 | handle.def("execute", &execute); 282 | } -------------------------------------------------------------------------------- /src/python/README.md: -------------------------------------------------------------------------------- 1 | # FLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.6) 2 | 3 | By 4 | [Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin) 5 | and 6 | [Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), 7 | with 8 | Jim Nilsson, 9 | [Magnus Oskarsson](https://www1.maths.lth.se/matematiklth/personal/magnuso/), 10 | [Kalle Åström](https://www.maths.lu.se/staff/kalleastrom/), 11 | [Mark D. Fairchild](https://www.rit.edu/directory/mdfpph-mark-fairchild), 12 | and 13 | [Peter Shirley](https://research.nvidia.com/person/peter-shirley). 14 | 15 | This [repository](https://github.com/NVlabs/flip) implements the [LDR-FLIP](https://research.nvidia.com/publication/2020-07_FLIP) 16 | and [HDR-FLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics in Python, using the C++ implementation through [nanobind](https://github.com/wjakob/nanobind). 17 | Similarly, it implements the FLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). 18 | 19 | # License 20 | 21 | Copyright © 2020-2024, NVIDIA Corporation & Affiliates. All rights reserved. 22 | 23 | This work is made available under a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/LICENSE). 24 | 25 | The repository distributes code for `tinyexr`, which is subject to a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#bsd-3-clause-license),
26 | and `stb_image`, which is subject to an [MIT License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#mit-license). 27 | 28 | For individual contributions to the project, please confer the [Individual Contributor License Agreement](https://github.com/NVlabs/flip/blob/main/misc/CLA.md). 29 | 30 | For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). 31 | 32 | # Python (API and Tool) 33 | - **Setup** (with pip): 34 | ``` 35 | pip install flip-evaluator 36 | ``` 37 | - Usage (API): See example in the script `src/python/api_example.py`. 38 | - Usage (tool): `flip --reference reference.{exr|png} --test test.{exr|png} [--options]`, where the list of options can be seen by `flip -h`. 39 | - Tested with pip 24.0, Python 3.11.8, pybind11 2.11.1, and C++20. 40 | - FLIP runs on Windows, Linux (tested on Ubuntu 24.04), and OS X ($\ge$ 10.15), though its output might differ slightly between the different operative systems. The references used for `src/tests/test.py` are made for Windows. While the mean tests (means compared up to six decimal points) pass for each mentioned operative system, not all error map pixels are identical. 41 | - The code that implements FLIP metrics and the FLIP tool is available in [src/cpp/FLIP.h](https://github.com/NVlabs/flip/blob/main/cpp/FLIP.h) and [src/cpp/tool](https://github.com/NVlabs/flip/blob/main/cpp/tool), respectively. The relevant functions are called by the Python API using [nanobind](https://github.com/wjakob/nanobind) (see [src/nanobindFLIP.cpp](https://github.com/NVlabs/flip/blob/main/src/nanobindFLIP.cpp)). The Python API is provided in `src/flip_evaluator/flip_python_api.py`. 42 | `src/tests/test.py` contains simple tests used to test whether code updates alter results. Notice that those scripts require `numpy` and `matplotlib`, both of which can be installed using pip. 43 | - Weighted histograms are output as Python scripts. Running the script will create a PDF version of the histogram. Like the test scripts, these scripts require `numpy` and `matplotlib`, both of which can be installed using pip. 44 | - The naming convention used for the FLIP tool's output is as follows (where `ppd` is the assumed number of pixels per degree, 45 | `tm` is the tone mapper assumed by HDR-FLIP, `cstart` and `cstop` are the shortest and longest exposures, respectively, assumed by HDR-FLIP, 46 | with `p` indicating a positive value and `m` indicating a negative value, 47 | `N` is the number of exposures used in the HDR-FLIP calculation, `nnn` is a counter used to sort the intermediate results, 48 | and `exp` is the exposure used for the intermediate LDR image / FLIP map): 49 | 50 | **Default:** 51 | 52 | *Low dynamic range images:*
53 | 54 | LDR-FLIP: `flip...ppd.ldr.png`
55 | Weighted histogram: `weighted_histogram.reference>..ppd.ldr.py`
56 | Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.ldr.py`
57 | Text file: `pooled_values...ppd.ldr.txt`
58 | 59 | *High dynamic range images:*
60 | 61 | HDR-FLIP: `flip...ppd.hdr.._to_..png`
62 | Exposure map: `exposure_map...ppd.hdr.._to_..png`
63 | Intermediate LDR-FLIP maps: `flip...ppd.ldr....png`
64 | Intermediate LDR images: `....png`
65 | Weighted histogram: `weighted_histogram...ppd.hdr.._to_..py`
66 | Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.hdr.._to_..py`
67 | Text file: `pooled_values...ppd.hdr.._to_..txt`
68 | 69 | **With** `--basename ` **(note: not applicable if more than one test image is evaluated):** 70 | 71 | *Low dynamic range images:*
72 | 73 | LDR-FLIP: `.png`
74 | Weighted histogram: `.py`
75 | Overlapping weighted histogram: N/A
76 | Text file: `.txt`
77 | 78 | *High dynamic range images:*
79 | 80 | HDR-FLIP: `.png`
81 | Exposure map: `.exposure_map.png`
82 | Intermediate LDR-FLIP maps: `..png`
83 | Intermediate LDR images: `.reference|test..png`
84 | Weighted histogram: `.py`
85 | Overlapping weighted histogram: N/A
86 | Text file: `.txt`
87 | 88 | **Example usage:** 89 | To test the API, please inspect the `src/python/api_example.py` script. This shows how the available API commands may be used. 90 | Please note that not all capabilities of the tool is available through the Python API. For example, the exposure map is not output when running HDR-FLIP. For that, use the tool or the C++ API in [FLIP.h](https://github.com/NVlabs/flip/blob/main/src/cpp/FLIP.h). 91 | 92 | To test the tool, start a shell, navigate to `images/` and try: 93 | ``` 94 | flip -r reference.exr -t test.exr 95 | ``` 96 | The result should be: 97 | ``` 98 | Invoking HDR-FLIP 99 | Pixels per degree: 67 100 | Assumed tone mapper: ACES 101 | Start exposure: -12.5423 102 | Stop exposure: 0.9427 103 | Number of exposures: 14 104 | 105 | FLIP between reference image and test image : 106 | Mean: 0.283478 107 | Weighted median: 0.339430 108 | 1st weighted quartile: 0.251122 109 | 3rd weighted quartile: 0.434673 110 | Min: 0.003123 111 | Max: 0.962022 112 | Evaluation time: seconds 113 | FLIP error map location: 114 | FLIP exposure map location: 115 | ``` 116 | where `` is the time it took to evaluate HDR-FLIP. In addition, you will now find the files `flip.reference.test.67ppd.hdr.aces.m12.5423_to_p0.9427.14.png` and `exposure_map.reference.test.67ppd.hdr.aces.m12.5423_to_p0.9427.14.png` 117 | in the working directory, and we urge you to inspect those, which will reveal where the errors in the test image are located. 118 | -------------------------------------------------------------------------------- /src/python/api_example.py: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | import flip_evaluator as flip 34 | import matplotlib.pyplot as plt 35 | 36 | if __name__ == '__main__': 37 | # Print docs for flip.evaluate. 38 | print("\n############################### DOCS ###############################\n") 39 | help(flip.evaluate) 40 | 41 | # Compute FLIP for reference and test image. 42 | ref = "../images/reference.exr" 43 | test = "../images/test.exr" 44 | flipErrorMap, meanFLIPError, parameters = flip.evaluate(ref, test, "HDR") 45 | 46 | # NOTE: An alternative to the above is to run flip.evaluate() with numpy arrays as input. 47 | # Images may be loaded using flip.load(imagePath). 48 | # ref = flip.load("../images/reference.exr") 49 | # test = flip.load("../images/test.exr") 50 | # flipErrorMap, meanFLIPError, parameters = flip.evaluate(ref, test, "HDR") 51 | 52 | print("\n############################### FLIP OUTPUT ###############################\n") 53 | print("Mean FLIP error: ", round(meanFLIPError, 6), "\n") 54 | 55 | print("The following parameters were used:") 56 | for key in parameters: 57 | val = parameters[key] 58 | if isinstance(val, float): 59 | val = round(val, 4) 60 | print("\t%s: %s" % (key, str(val))) 61 | 62 | plt.imshow(flipErrorMap) 63 | plt.show() 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/pytorch/README.md: -------------------------------------------------------------------------------- 1 | # FLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.6) 2 | 3 | By 4 | [Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin) 5 | and 6 | [Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), 7 | with 8 | Jim Nilsson, 9 | [Magnus Oskarsson](https://www1.maths.lth.se/matematiklth/personal/magnuso/), 10 | [Kalle Åström](https://www.maths.lu.se/staff/kalleastrom/), 11 | [Mark D. Fairchild](https://www.rit.edu/directory/mdfpph-mark-fairchild), 12 | and 13 | [Peter Shirley](https://research.nvidia.com/person/peter-shirley). 14 | 15 | This [repository](https://github.com/NVlabs/flip) holds implementations of the [LDR-FLIP](https://research.nvidia.com/publication/2020-07_FLIP) 16 | and [HDR-FLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics as loss modules in PyTorch. 17 | 18 | # License 19 | 20 | Copyright © 2020-2024, NVIDIA Corporation & Affiliates. All rights reserved. 21 | 22 | This work is made available under a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/LICENSE). 23 | 24 | The repository distributes code for `tinyexr`, which is subject to a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#bsd-3-clause-license),
25 | and `stb_image`, which is subject to an [MIT License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#mit-license). 26 | 27 | For individual contributions to the project, please confer the [Individual Contributor License Agreement](https://github.com/NVlabs/flip/blob/main/misc/CLA.md). 28 | 29 | For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). 30 | 31 | # PyTorch (Loss Function) 32 | 33 | - **Setup** (with Anaconda3): 34 | ``` 35 | conda create -n flip_dl python numpy matplotlib 36 | conda activate flip_dl 37 | conda install pytorch torchvision torchaudio cudatoolkit=11.1 -c pytorch -c conda-forge 38 | conda install -c conda-forge openexr-python 39 | ``` 40 | - *Remember to activate the* `flip_dl` *environment through* `conda activate flip_dl` *before using the loss function.* 41 | - LDR- and HDR-FLIP are implemented as loss modules in `flip_evaluator/pytorch/flip_loss.py`. 42 | An example where the loss function is used to train a simple autoencoder is provided in `flip_evaluator/pytorch/train.py`. 43 | - Tested on Windows with Conda 4.10.0, CUDA 11.2, Python 3.9.4, PyTorch 1.8.1, NumPy 1.20.1, and OpenEXR b1.3.2. 44 | - Per default, the loss function returns the mean of the error maps. To return the full error maps, 45 | remove `torch.mean()` from the `forward()` function. 46 | - For LDR-FLIP, the images are assumed to be in sRGB space 47 | (change the color space transform in `LDRFLIPLoss`'s `forward()` function to `linrgb2ycxcz` if your network's output is in linear RGB), 48 | in the [0,1] range. 49 | - Both LDR- and HDR-FLIP takes an optional argument describing the assumed number of pixels per 50 | degree of the observer. Per default, it is assume that the images are viewed at a distance 0.7 m from 51 | a 0.7 m wide 4K monitor. 52 | - The `HDRFLIPLoss` can take three additional, optional arguments: `tone_mapper`, `start_exposure`, and `stop_exposure`. 53 | `tone_mapper` is a string describing the tone mapper that HDR-FLIP should assume, for which the choices are `aces` (default), `hable`, and `reinhard`. The default assumption is the [ACES](https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/) tone mapper. 54 | `start_exposure`, and `stop_exposure` should have `Nx1x1x1` layout and hold the 55 | start and stop exposures, respectively, used for each of the `N` reference/test 56 | pairs in the batch. Per default, `HDRFLIPLoss` computes start and stop exposures as described 57 | in the [paper](https://d1qx31qr3h6wln.cloudfront.net/publications/HDRFLIP-paper.pdf). 58 | **NOTE:** When start and/or stop exposures are not provided, HDR-FLIP is not symmetric. The 59 | user should therefore make sure to input the test images as the *first* argument and the reference image 60 | as the *second* argument to the `HDRFLIPLoss`'s `forward()` function. 61 | - `flip_evaluator/tests/test_pytorch.py` contains simple tests used to test whether code updates alter results and 62 | `flip_evaluator/pytorch/data.py` contains image loading/saving functions. 63 | -------------------------------------------------------------------------------- /src/pytorch/data.py: -------------------------------------------------------------------------------- 1 | """ Functions for image loading and saving """ 2 | ################################################################################# 3 | # Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES 31 | # SPDX-License-Identifier: BSD-3-Clause 32 | ################################################################################# 33 | 34 | # Visualizing and Communicating Errors in Rendered Images 35 | # Ray Tracing Gems II, 2021, 36 | # by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 37 | # Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 38 | 39 | # Visualizing Errors in Rendered High Dynamic Range Images 40 | # Eurographics 2021, 41 | # by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 42 | # Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 43 | 44 | # FLIP: A Difference Evaluator for Alternating Images 45 | # High Performance Graphics 2020, 46 | # by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 47 | # Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 48 | # Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 49 | 50 | # Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 51 | 52 | import numpy as np 53 | import torch 54 | from matplotlib.pyplot import imsave 55 | from PIL import Image 56 | import OpenEXR as exr 57 | import Imath 58 | 59 | from flip_loss import color_space_transform, tone_map 60 | 61 | def HWCtoCHW(x): 62 | """ 63 | Transforms an image from HxWxC layout to CxHxW 64 | 65 | :param x: image with HxWxC layout 66 | :return: image with CxHxW layout 67 | """ 68 | return np.rollaxis(x, 2) 69 | 70 | def CHWtoHWC(x): 71 | """ 72 | Transforms an image from CxHxW layout to HxWxC 73 | 74 | :param x: image with CxHxW layout 75 | :return: image with HxWxC layout 76 | """ 77 | return np.swapaxes(np.swapaxes(x, 0, 1), 1, 2) 78 | 79 | def save_image(img_file, img): 80 | """ 81 | Saves image as png 82 | 83 | :param img_file: image's filename 84 | :param img: float image (in the [0,1] range) to save 85 | """ 86 | img = CHWtoHWC(img.cpu().numpy()[0]) 87 | imsave(img_file, img) 88 | 89 | def load_image_tensor(img_file): 90 | """ 91 | Loads an image and transforms it into a numpy array and into the [0, 1] range 92 | 93 | :param img_file: image's filename 94 | :return: float image (in the [0,1] range) with 1xCxHxW layout 95 | """ 96 | img = Image.open(img_file, 'r').convert('RGB') 97 | img = np.asarray(img).astype(np.float32) 98 | img = np.rollaxis(img, 2) 99 | img = img / 255.0 100 | img = torch.from_numpy(img).unsqueeze(0).cuda() 101 | return img 102 | 103 | def read_exr(filename): 104 | """ 105 | Read color data from EXR image file. Set negative values and nans to 0. 106 | 107 | :param filename: string describing file path 108 | :return: RGB image in float32 format (with 1xCxHxW layout) 109 | """ 110 | exrfile = exr.InputFile(filename) 111 | header = exrfile.header() 112 | 113 | dw = header['dataWindow'] 114 | isize = (dw.max.y - dw.min.y + 1, dw.max.x - dw.min.x + 1) 115 | 116 | channelData = dict() 117 | 118 | # Convert all channels in the image to numpy arrays 119 | for c in header['channels']: 120 | C = exrfile.channel(c, Imath.PixelType(Imath.PixelType.FLOAT)) 121 | C = np.frombuffer(C, dtype=np.float32) 122 | C = np.reshape(C, isize) 123 | 124 | channelData[c] = C 125 | 126 | if len(channelData) == 1: 127 | channelData['R'] = channelData['G'] = channelData['B'] = channelData[next(iter(channelData))] 128 | 129 | colorChannels = ['R', 'G', 'B'] 130 | img_np = np.concatenate([channelData[c][...,np.newaxis] for c in colorChannels], axis=2) 131 | 132 | img_torch = torch.max(torch.nan_to_num(torch.from_numpy(np.array(img_np))), torch.tensor([0.0])).cuda() # added maximum to avoid negative values in images 133 | 134 | return img_torch.permute(2, 0, 1).unsqueeze(0) 135 | 136 | def save_ldr_image_range(img_file, hdr_img, epoch=0, tone_mapper="aces", start_exposure=0.0, stop_exposure=0.0): 137 | """ 138 | Saves exposure compensated and tone mapped versions of the input HDR image corresponding 139 | to the exposure range and tone mapper assumed by HDR-FLIP during its calculations 140 | 141 | :param img_file: string describing image's base file path 142 | :param epoch: (optional) int describing training epoch 143 | :param tone_mapper: (optional) string describing tone mapper to apply before storing the LDR image 144 | :param start_exposure: (optional) float describing the shortest exposure in the exposure range 145 | :param stop_exposure: (optional) float describing the longest exposure in the exposure range 146 | """ 147 | start_exposure_sign = "m" if start_exposure < 0 else "p" 148 | stop_exposure_sign = "m" if stop_exposure < 0 else "p" 149 | 150 | num_exposures = int(max(2.0, np.ceil(stop_exposure.item() - start_exposure.item()))) 151 | step_size = (stop_exposure - start_exposure) / max(num_exposures - 1, 1) 152 | 153 | for i in range(0, num_exposures): 154 | exposure = start_exposure + i * step_size 155 | exposure_sign = "m" if exposure < 0 else "p" 156 | img_tone_mapped = color_space_transform(tone_map(hdr_img, tone_mapper, exposure), "linrgb2srgb") 157 | save_image((img_file + '_epoch_%04d.%04d.' + start_exposure_sign + '%0.4f_to_' + stop_exposure_sign + '%0.4f.' + exposure_sign + '%0.4f.png') 158 | % (epoch, i, abs(start_exposure), abs(stop_exposure), abs(exposure)), 159 | img_tone_mapped) 160 | -------------------------------------------------------------------------------- /src/pytorch/train.py: -------------------------------------------------------------------------------- 1 | """ Simple example of using FLIP as a loss function """ 2 | ################################################################################# 3 | # Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, this 9 | # list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # 3. Neither the name of the copyright holder nor the names of its 16 | # contributors may be used to endorse or promote products derived from 17 | # this software without specific prior written permission. 18 | # 19 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | # 30 | # SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES 31 | # SPDX-License-Identifier: BSD-3-Clause 32 | ################################################################################# 33 | 34 | # Visualizing and Communicating Errors in Rendered Images 35 | # Ray Tracing Gems II, 2021, 36 | # by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 37 | # Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 38 | 39 | # Visualizing Errors in Rendered High Dynamic Range Images 40 | # Eurographics 2021, 41 | # by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 42 | # Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 43 | 44 | # FLIP: A Difference Evaluator for Alternating Images 45 | # High Performance Graphics 2020, 46 | # by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 47 | # Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 48 | # Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 49 | 50 | # Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 51 | import numpy as np 52 | import torch 53 | import torch.nn as nn 54 | import argparse 55 | import os 56 | 57 | from flip_loss import LDRFLIPLoss, HDRFLIPLoss, compute_start_stop_exposures, color_space_transform 58 | from data import * 59 | 60 | ############################################################################### 61 | # Autoencoder network definition 62 | ############################################################################### 63 | class Autoencoder(nn.Module): 64 | """ Autoencoder class """ 65 | def __init__(self): 66 | """ Init """ 67 | super(Autoencoder, self).__init__() 68 | # Encoder 69 | self.encoder = nn.Sequential( 70 | nn.Conv2d(3, 48, 3, padding=1), 71 | nn.ReLU(inplace=True), 72 | nn.Conv2d(48, 48, 3, padding=1), 73 | nn.ReLU(inplace=True), 74 | nn.MaxPool2d(2) 75 | ) 76 | # Decoder 77 | self.decoder = nn.Sequential( 78 | nn.Conv2d(48, 64, 3, padding=1), 79 | nn.ReLU(inplace=True), 80 | nn.Conv2d(64, 32, 3, padding=1), 81 | nn.ReLU(inplace=True), 82 | nn.Conv2d(32, 3, 3, padding=1), 83 | nn.ReLU(inplace=True), 84 | nn.Upsample(scale_factor=2) 85 | ) 86 | 87 | def forward(self, x): 88 | """ 89 | Autoencoder's forward function 90 | 91 | :param x: tensor with NxCxHxW layout 92 | :return: tensor with NxCxHxW layout 93 | """ 94 | x = self.encoder(x) 95 | return self.decoder(x) 96 | 97 | ############################################################################### 98 | # Simple Autoencoder training 99 | ############################################################################### 100 | parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) 101 | parser.add_argument('--reference', type=str, default='../images/reference.png') 102 | parser.add_argument('--test', type=str, default='../images/test.png') 103 | parser.add_argument('--directory', type=str, default='../images/out') 104 | parser.add_argument('--output_ldr_images', action='store_true') 105 | 106 | args = parser.parse_args() 107 | 108 | # Create output directory if it doesn't exist 109 | if not os.path.isdir(args.directory) and args.output_ldr_images: 110 | os.makedirs(args.directory) 111 | 112 | # Load images and setup loss. If images are HDR, use HDR-FLIP loss, otherwise use LDR-FLIP loss 113 | image_format = args.reference.split('.')[-1] 114 | if image_format == "exr" or image_format == "EXR": 115 | hdr = True 116 | reference = read_exr(args.reference) 117 | test = read_exr(args.test) 118 | 119 | # Compute start and stop exposures automatically for the given reference 120 | start_exposure, stop_exposure = compute_start_stop_exposures(reference, "aces", 0.85, 0.85) 121 | 122 | # Save the LDR versions of the reference that will be used for HDR-FLIP 123 | if args.output_ldr_images: 124 | save_ldr_image_range(args.directory + '/reference', reference, tone_mapper="aces", start_exposure=start_exposure, stop_exposure=stop_exposure) 125 | 126 | loss_fn = HDRFLIPLoss() 127 | elif image_format == "png" or image_format == "PNG": 128 | hdr = False 129 | reference = load_image_tensor(args.reference) 130 | test = load_image_tensor(args.test) 131 | 132 | loss_fn = LDRFLIPLoss() 133 | else: 134 | sys.exit("Error: Invalid image format. Please use png or exr.") 135 | 136 | # Initialize autoencoder and optimizer 137 | model = Autoencoder().cuda() 138 | optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) 139 | 140 | for epoch in range(1001): 141 | pred = model.forward(test) 142 | if not hdr: 143 | # LDR-FLIP expects sRGB input per default 144 | pred.data = color_space_transform(torch.clamp(pred.data, 0, 1), "linrgb2srgb") 145 | dist = loss_fn.forward(pred, reference) 146 | optimizer.zero_grad() 147 | dist.backward() 148 | optimizer.step() 149 | 150 | # Print loss every 10th epoch 151 | if epoch % 10 == 0: 152 | print('iter %d, dist %.3g' % (epoch, dist.item())) 153 | pred_img = pred.data 154 | if args.output_ldr_images: 155 | if hdr: 156 | if epoch % 100 == 0: 157 | # Loop over exposures used for HDR-FLIP - apply exposure compensation and tone mapping and save resulting test images 158 | # (Only do this once per 100 epochs to avoid large amount of images saved) 159 | save_ldr_image_range(args.directory + '/pred', pred_img, epoch=epoch, tone_mapper="aces", start_exposure=start_exposure, stop_exposure=stop_exposure) 160 | else: 161 | # Save output for arbitrary exposure between start and stop exposure 162 | exposure = 0.7 * (stop_exposure - start_exposure) + start_exposure 163 | pred_img_tone_mapped = color_space_transform(tone_map(pred_img, "aces", exposure), 'linrgb2srgb') 164 | save_image(args.directory + '/pred_epoch_%04d.png' % epoch, pred_img_tone_mapped) 165 | else: 166 | save_image(args.directory + '/pred_epoch_%04d.png' % epoch, pred_img) 167 | -------------------------------------------------------------------------------- /src/tests/correct_hdrflip_cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/src/tests/correct_hdrflip_cpp.png -------------------------------------------------------------------------------- /src/tests/correct_hdrflip_cuda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/src/tests/correct_hdrflip_cuda.png -------------------------------------------------------------------------------- /src/tests/correct_ldrflip_cpp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/src/tests/correct_ldrflip_cpp.png -------------------------------------------------------------------------------- /src/tests/correct_ldrflip_cuda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NVlabs/flip/9629de66b29663772d286c11321ae6eef140a580/src/tests/correct_ldrflip_cuda.png -------------------------------------------------------------------------------- /src/tests/test.py: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | # Visualizing and Communicating Errors in Rendered Images 34 | # Ray Tracing Gems II, 2021, 35 | # by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | # Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | # Visualizing Errors in Rendered High Dynamic Range Images 39 | # Eurographics 2021, 40 | # by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | # Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | # FLIP: A Difference Evaluator for Alternating Images 44 | # High Performance Graphics 2020, 45 | # by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | # Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | # Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | # Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 50 | 51 | import subprocess 52 | import os 53 | import sys 54 | import matplotlib.pyplot as plt 55 | import numpy as np 56 | import flip_evaluator as flip 57 | 58 | if __name__ == '__main__': 59 | """ 60 | Test script. Runs FLIP for both LDR and HDR using one of CUDA/CPP/PYTHON based on the commandline argument. 61 | Both the mean FLIP is tested and the pixel values from the resulting FLIP images. 62 | """ 63 | 64 | if(len(sys.argv) != 2): 65 | print("Usage: python test.py --{cuda|cpp|python}") 66 | print("Tip: do not forget to install the FLIP Python API through `pip install flip_evaluator`.") 67 | sys.exit() 68 | 69 | # Parse command line argument. 70 | if(sys.argv[1] == "--cuda" or sys.argv[1] == "cuda" or sys.argv[1] == "-cuda"): 71 | test_str = "CUDA" 72 | correct_ldr_image_filename = "correct_ldrflip_cuda.png" 73 | correct_hdr_image_filename = "correct_hdrflip_cuda.png" 74 | ldr_cmd = "../cpp/x64/release/flip-cuda.exe --reference ../../images/reference.png --test ../../images/test.png" 75 | hdr_cmd = "../cpp/x64/release/flip-cuda.exe --reference ../../images/reference.exr --test ../../images/test.exr --no-exposure-map" 76 | elif(sys.argv[1] == "--cpp" or sys.argv[1] == "cpp" or sys.argv[1] == "-cpp"): 77 | test_str = "CPP" 78 | correct_ldr_image_filename = "correct_ldrflip_cpp.png" 79 | correct_hdr_image_filename = "correct_hdrflip_cpp.png" 80 | ldr_cmd = "../cpp/x64/release/flip.exe --reference ../../images/reference.png --test ../../images/test.png" 81 | hdr_cmd = "../cpp/x64/release/flip.exe --reference ../../images/reference.exr --test ../../images/test.exr --no-exposure-map" 82 | elif(sys.argv[1] == "--python" or sys.argv[1] == "python" or sys.argv[1] == "-python"): 83 | test_str = "PYTHON" 84 | correct_ldr_image_filename = "correct_ldrflip_cpp.png" # Python and C++ should give the same results, 85 | correct_hdr_image_filename = "correct_hdrflip_cpp.png" # as the Python code runs the C++ code via pybind11. 86 | else: 87 | print("Error: the argument should be one of --cuda, --cpp, and --python.") 88 | sys.exit() 89 | 90 | print("Running " + test_str + " tests...") 91 | print("========================") 92 | 93 | # Load correct LDR/HDR FLIP images in the tests directory. 94 | ldr_correct_result = flip.load(correct_ldr_image_filename) # LDR, sRGB 95 | hdr_correct_result = flip.load(correct_hdr_image_filename) # HDR 96 | 97 | # Result file names 98 | result_ldr_file = "flip.reference.test.67ppd.ldr.png" 99 | result_hdr_file = "flip.reference.test.67ppd.hdr.aces.m12.5423_to_p0.9427.14.png" 100 | 101 | # Expected means 102 | expected_ldr_mean = 0.159691 103 | expected_hdr_mean = 0.283478 104 | 105 | if test_str == "CUDA" or test_str == "CPP": 106 | # Run FLIP on the reference/test image pairs in the ../../images directory. 107 | ldr_process = subprocess.run(ldr_cmd, stdout=subprocess.PIPE, text=True) 108 | hdr_process = subprocess.run(hdr_cmd, stdout=subprocess.PIPE, text=True) 109 | 110 | ldr_result_strings = ldr_process.stdout.split('\n') 111 | subpos = ldr_result_strings[4].find(':') 112 | ldr_mean = float(ldr_result_strings[4][subpos + 2 : len(ldr_result_strings[4])]) 113 | 114 | hdr_result_strings = hdr_process.stdout.split('\n') 115 | subpos = hdr_result_strings[8].find(':') 116 | hdr_mean = float(hdr_result_strings[8][subpos + 2 : len(hdr_result_strings[8])]) 117 | else: 118 | # Run FLIP on the reference/test image pairs in the ../../images directory. 119 | ldr_new_result, ldr_mean, _ = flip.evaluate("../../images/reference.png", "../../images/test.png", "LDR") 120 | hdr_new_result, hdr_mean, _ = flip.evaluate("../../images/reference.exr", "../../images/test.exr", "HDR") 121 | 122 | # Round to match the FLIP tool's output rounding 123 | ldr_mean = round(ldr_mean, 6) 124 | hdr_mean = round(hdr_mean, 6) 125 | plt.imsave(result_ldr_file, np.uint8(255 * ldr_new_result + 0.5), vmin=0, vmax=255) # Same rounding as is used to save 126 | plt.imsave(result_hdr_file, np.uint8(255 * hdr_new_result + 0.5), vmin=0, vmax=255) # images in the C++ code (cpp/FLIP.h#L1300). 127 | 128 | # Load the images that were just created 129 | ldr_new_result = flip.load(result_ldr_file) # LDR, sRGB 130 | hdr_new_result = flip.load(result_hdr_file) # HDR 131 | 132 | if((abs(ldr_correct_result - ldr_new_result) > 0).any()): 133 | print("LDR: FAILED per-pixel test on FLIP images.") 134 | else: 135 | print("LDR: PASSED per-pixel test on FLIP images.") 136 | 137 | if(ldr_mean != expected_ldr_mean): 138 | print("LDR: FAILED mean test.") 139 | else: 140 | print("LDR: PASSED mean test.") 141 | 142 | if((abs(hdr_correct_result - hdr_new_result) > 0).any()): 143 | print("HDR: FAILED per-pixel test on FLIP images.") 144 | else: 145 | print("HDR: PASSED per-pixel test on FLIP images.") 146 | 147 | if(hdr_mean != expected_hdr_mean): 148 | print("HDR: FAILED mean test.") 149 | else: 150 | print("HDR: PASSED mean test.") 151 | 152 | # Remove output created during tests. 153 | os.remove(result_ldr_file) 154 | os.remove(result_hdr_file) 155 | 156 | print("========================") 157 | print("Tests complete!") 158 | 159 | -------------------------------------------------------------------------------- /src/tests/test_pytorch.py: -------------------------------------------------------------------------------- 1 | ################################################################################# 2 | # Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are met: 6 | # 7 | # 1. Redistributions of source code must retain the above copyright notice, this 8 | # list of conditions and the following disclaimer. 9 | # 10 | # 2. Redistributions in binary form must reproduce the above copyright notice, 11 | # this list of conditions and the following disclaimer in the documentation 12 | # and/or other materials provided with the distribution. 13 | # 14 | # 3. Neither the name of the copyright holder nor the names of its 15 | # contributors may be used to endorse or promote products derived from 16 | # this software without specific prior written permission. 17 | # 18 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | # 29 | # SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES 30 | # SPDX-License-Identifier: BSD-3-Clause 31 | ################################################################################# 32 | 33 | # Visualizing and Communicating Errors in Rendered Images 34 | # Ray Tracing Gems II, 2021, 35 | # by Pontus Andersson, Jim Nilsson, and Tomas Akenine-Moller. 36 | # Pointer to the chapter: https://research.nvidia.com/publication/2021-08_Visualizing-and-Communicating. 37 | 38 | # Visualizing Errors in Rendered High Dynamic Range Images 39 | # Eurographics 2021, 40 | # by Pontus Andersson, Jim Nilsson, Peter Shirley, and Tomas Akenine-Moller. 41 | # Pointer to the paper: https://research.nvidia.com/publication/2021-05_HDR-FLIP. 42 | 43 | # FLIP: A Difference Evaluator for Alternating Images 44 | # High Performance Graphics 2020, 45 | # by Pontus Andersson, Jim Nilsson, Tomas Akenine-Moller, 46 | # Magnus Oskarsson, Kalle Astrom, and Mark D. Fairchild. 47 | # Pointer to the paper: https://research.nvidia.com/publication/2020-07_FLIP. 48 | 49 | # Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. 50 | 51 | import sys 52 | sys.path.insert(1, '../pytorch') 53 | from flip_loss import LDRFLIPLoss, HDRFLIPLoss 54 | from data import * 55 | 56 | if __name__ == '__main__': 57 | """ 58 | Test script. Add tests by appending descriptions to the test_description list, 59 | and set up tests (results should be booleans - True if test passed, False otherwise). 60 | Add results to the test_results list. Printed output will show which tests succeed and 61 | which fail. 62 | """ 63 | # Test descriptions 64 | test_descriptions = [] 65 | test_descriptions.append("HDR-FLIP output matches reference HDR-FLIP output") 66 | test_descriptions.append("LDR-FLIP output matches reference LDR-FLIP output") 67 | 68 | # Set up test results 69 | test_results = [] 70 | 71 | print("Running tests...") 72 | print("================") 73 | 74 | # HDR test 75 | # Run the image pairs in the images directory 76 | hdr_reference = read_exr('../../images/reference.exr') # EXR 77 | hdr_test = read_exr('../../images/test.exr') # EXR 78 | hdrflip_loss_fn = HDRFLIPLoss() 79 | hdrflip_loss = hdrflip_loss_fn(hdr_test, hdr_reference) 80 | test_results.append(round(hdrflip_loss.item(), 4) == 0.2835) 81 | 82 | # LDR test 83 | # Run the image pairs in the images directory 84 | ldr_reference = load_image_tensor('../../images/reference.png') # sRGB 85 | ldr_test = load_image_tensor('../../images/test.png') # sRGB 86 | ldrflip_loss_fn = LDRFLIPLoss() 87 | ldrflip_loss = ldrflip_loss_fn(ldr_test, ldr_reference) 88 | test_results.append(round(ldrflip_loss.item(), 4) == 0.1597) 89 | 90 | for idx, passed in enumerate(test_results): 91 | print(("PASSED " if passed else "FAILED ") + "test " + str(idx) + " - " + test_descriptions[idx]) 92 | 93 | print("================") 94 | print("Tests complete!") 95 | --------------------------------------------------------------------------------