From 79675788aa642fbb4732effe2b45b082ff4c4d52 Mon Sep 17 00:00:00 2001 From: pandersson94 Date: Mon, 11 Nov 2024 19:08:02 +0100 Subject: [PATCH] Update to FLIP v1.6 (#38) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Flipped the ꟻ in ꟻLIP. The entire name (FLIP) should now be readable on all devices. - Published Python version of FLIP to PyPI (URL: https://pypi.org/project/flip-evaluator/). - The Python version of FLIP (tool and API) is now installed by pip install flip-evaluator. - The distribution has been tested on Windows, Linux (Ubuntu 24.04), and OS X (≥ 10.15). Wheels are built for each (and various CPython versions ≥ 3.8) using 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. - After installation, the tool can be run directly in a shell by `flip --reference reference.{png|exr} --test test.{png|exr}`. - After installation, the FLIP API is available in Python by `import flip_evaluator as flip`. - Python version is now built using scikit instead of setuptools, and uses nanobind instead of pybind11. - Directory structure in the FLIP repository has been slightly altered to accomodate the Python version being published to PyPI. - Updated Python/C++/CUDA test script. - Various significant bugfixes. - NOTE: Skipped version 1.5 due to PyPI-related mistake. Version 1.6 is the same as version 1.5 was supposed to be. --- .github/workflows/flip-deploy.yml | 67 ++++++ .github/workflows/flip_ci.yml | 6 +- .gitignore | 4 +- CMakeLists.txt | 62 ++++-- LICENSE | 31 +++ README.md | 77 ++++--- misc/LICENSE.md | 28 --- misc/papersUsingFLIP.md | 11 +- misc/precision.md | 11 +- misc/versionList.md | 31 ++- python/pyproject.toml => pyproject.toml | 45 +++- python/README.md | 117 ----------- python/requirements.txt | 2 - python/setup.py | 77 ------- python/flip.py => src/CMakeLists.txt | 20 +- {cmake => src/cmake}/FLIPConfig.cmake | 0 {cpp => src/cpp}/CMakeLists.txt | 0 {cpp => src/cpp}/FLIP.h | 42 ++-- {cpp => src/cpp}/FLIP.sln | 0 {cpp => src/cpp}/README.md | 54 ++--- {cpp => src/cpp}/tool/CMakeLists.txt | 0 {cpp => src/cpp}/tool/CPP.vcxproj | 0 {cpp => src/cpp}/tool/CPP.vcxproj.filters | 0 {cpp => src/cpp}/tool/CUDA.vcxproj | 4 +- {cpp => src/cpp}/tool/CUDA.vcxproj.filters | 0 {cpp => src/cpp}/tool/FLIP-tool.cpp | 0 {cpp => src/cpp}/tool/FLIP-tool.cu | 0 {cpp => src/cpp}/tool/FLIPToolHelpers.h | 52 +++-- {cpp => src/cpp}/tool/commandline.h | 2 +- {cpp => src/cpp}/tool/filename.h | 3 +- {cpp => src/cpp}/tool/imagehelpers.h | 0 {cpp => src/cpp}/tool/pooling.h | 1 - {cpp => src/cpp}/tool/stb_image.h | 0 {cpp => src/cpp}/tool/stb_image_write.h | 0 {cpp => src/cpp}/tool/tinyexr.h | 0 .../flip => src/flip_evaluator}/__init__.py | 2 +- .../flip_evaluator/flip_python_api.py | 22 +- python/flip/main.cpp => src/nanobindFLIP.cpp | 195 +++++++++--------- src/python/README.md | 117 +++++++++++ {python => src/python}/api_example.py | 2 +- {pytorch => src/pytorch}/README.md | 32 +-- {pytorch => src/pytorch}/data.py | 0 {pytorch => src/pytorch}/flip_loss.py | 0 {pytorch => src/pytorch}/train.py | 0 {tests => src/tests}/correct_hdrflip_cpp.png | Bin {tests => src/tests}/correct_hdrflip_cuda.png | Bin {tests => src/tests}/correct_ldrflip_cpp.png | Bin {tests => src/tests}/correct_ldrflip_cuda.png | Bin {tests => src/tests}/test.py | 63 +++--- {tests => src/tests}/test_pytorch.py | 8 +- 50 files changed, 651 insertions(+), 537 deletions(-) create mode 100644 .github/workflows/flip-deploy.yml create mode 100644 LICENSE delete mode 100644 misc/LICENSE.md rename python/pyproject.toml => pyproject.toml (57%) delete mode 100644 python/README.md delete mode 100644 python/requirements.txt delete mode 100644 python/setup.py rename python/flip.py => src/CMakeLists.txt (80%) rename {cmake => src/cmake}/FLIPConfig.cmake (100%) rename {cpp => src/cpp}/CMakeLists.txt (100%) rename {cpp => src/cpp}/FLIP.h (98%) rename {cpp => src/cpp}/FLIP.sln (100%) rename {cpp => src/cpp}/README.md (65%) rename {cpp => src/cpp}/tool/CMakeLists.txt (100%) rename {cpp => src/cpp}/tool/CPP.vcxproj (100%) rename {cpp => src/cpp}/tool/CPP.vcxproj.filters (100%) rename {cpp => src/cpp}/tool/CUDA.vcxproj (99%) rename {cpp => src/cpp}/tool/CUDA.vcxproj.filters (100%) rename {cpp => src/cpp}/tool/FLIP-tool.cpp (100%) rename {cpp => src/cpp}/tool/FLIP-tool.cu (100%) rename {cpp => src/cpp}/tool/FLIPToolHelpers.h (92%) rename {cpp => src/cpp}/tool/commandline.h (99%) rename {cpp => src/cpp}/tool/filename.h (99%) rename {cpp => src/cpp}/tool/imagehelpers.h (100%) rename {cpp => src/cpp}/tool/pooling.h (99%) rename {cpp => src/cpp}/tool/stb_image.h (100%) rename {cpp => src/cpp}/tool/stb_image_write.h (100%) rename {cpp => src/cpp}/tool/tinyexr.h (100%) rename {python/flip => src/flip_evaluator}/__init__.py (97%) rename python/flip/main.py => src/flip_evaluator/flip_python_api.py (94%) rename python/flip/main.cpp => src/nanobindFLIP.cpp (58%) create mode 100644 src/python/README.md rename {python => src/python}/api_example.py (99%) rename {pytorch => src/pytorch}/README.md (64%) rename {pytorch => src/pytorch}/data.py (100%) rename {pytorch => src/pytorch}/flip_loss.py (100%) rename {pytorch => src/pytorch}/train.py (100%) rename {tests => src/tests}/correct_hdrflip_cpp.png (100%) rename {tests => src/tests}/correct_hdrflip_cuda.png (100%) rename {tests => src/tests}/correct_ldrflip_cpp.png (100%) rename {tests => src/tests}/correct_ldrflip_cuda.png (100%) rename {tests => src/tests}/test.py (72%) rename {tests => src/tests}/test_pytorch.py (94%) diff --git a/.github/workflows/flip-deploy.yml b/.github/workflows/flip-deploy.yml new file mode 100644 index 0000000..4e1fe7f --- /dev/null +++ b/.github/workflows/flip-deploy.yml @@ -0,0 +1,67 @@ +# Much of the code retrieved from Mitsuba3 (https://github.com/mitsuba-renderer/mitsuba3/blob/master/.github/workflows/wheels.yml.) + +name: Build wheels + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + release: + types: + - published + +jobs: + build_wheels: + strategy: + matrix: + # macos-13 is an intel runner, macos-14 is apple silicon + os: [ubuntu-latest, windows-latest, macos-13, macos-14] + python: [cp38, cp39, cp310, cp311, cp312, cp312_stable, cp313] + exclude: + # The first Python version to target Apple arm64 architectures is 3.9. + - os: macos-14 + python: cp38 + name: > + ${{ matrix.python }} wheel for ${{ matrix.os }} + ${{ (endsWith(matrix.python, '_stable') && '(stable ABI)') || '' }} + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v4 + name: Install Python + with: + python-version: '3.8' + + - name: Install cibuildwheel + run: | + python -m pip install cibuildwheel==2.20.0 + + ######################### + # Build and store wheels + ######################### + - name: Build wheel + run: | + python -m cibuildwheel --output-dir wheelhouse + + - uses: actions/upload-artifact@v3 + with: + name: wheels + path: ./wheelhouse/*.whl + + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build sdist + run: pipx run build --sdist + + - uses: actions/upload-artifact@v4 + with: + name: cibw-sdist + path: dist/*.tar.gz \ No newline at end of file diff --git a/.github/workflows/flip_ci.yml b/.github/workflows/flip_ci.yml index e380ddd..06f3d43 100644 --- a/.github/workflows/flip_ci.yml +++ b/.github/workflows/flip_ci.yml @@ -24,12 +24,12 @@ jobs: - name: Configure CMake run: > - cmake -LA -B ${{github.workspace}}/build + cmake -LA -B ${{github.workspace}}/src/build -S ${{github.workspace}}/src/ -DCMAKE_BUILD_TYPE=${{ matrix.config }} - -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/build/install + -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/src/build/install -DCMAKE_CUDA_ARCHITECTURES=all -DCMAKE_CUDA_HOST_COMPILER=g++-10 -DFLIP_ENABLE_CUDA=${{ matrix.os == 'ubuntu-latest' }} - name: Build - run: cmake --build ${{github.workspace}}/build --config ${{ matrix.config }} --target install + run: cmake --build ${{github.workspace}}/src/build --config ${{ matrix.config }} --target install diff --git a/.gitignore b/.gitignore index 10477af..b0f8112 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,8 @@ _ReSharper*/ # Python ignores *__pycache__* -python/build/ -python/*.egg-info/ +dist/ +*.egg-info/ # LaTeX ignores *.synctex.gz diff --git a/CMakeLists.txt b/CMakeLists.txt index 064beb6..6f4e8d5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,22 +30,60 @@ # SPDX-License-Identifier: BSD-3-Clause ################################################################################# -cmake_minimum_required(VERSION 3.9) +cmake_minimum_required(VERSION 3.15...3.27) +project(flip_evaluator LANGUAGES CXX) -set(CMAKE_DISABLE_SOURCE_CHANGES ON) -set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) +# Warn if the user invokes CMake directly +if (NOT SKBUILD) + message(WARNING "\ + This CMake file is meant to be executed using 'scikit-build-core'. + Running it directly will almost certainly not produce the desired + result. If you are a user trying to install this package, use the + command below, which will install all necessary build dependencies, + compile the package in an isolated environment, and then install it. + ===================================================================== + $ pip install . + ===================================================================== + If you are a software developer, and this is your own package, then + it is usually much more efficient to install the build dependencies + in your environment once and use the following command that avoids + a costly creation of a new virtual environment at every compilation: + ===================================================================== + $ pip install nanobind scikit-build-core[pyproject] + $ pip install --no-build-isolation -ve . + ===================================================================== + You may optionally add -Ceditable.rebuild=true to auto-rebuild when + the package is imported. Otherwise, you need to rerun the above + after editing C++ files.") +endif() -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_CXX_EXTENSIONS OFF) +if (CMAKE_VERSION VERSION_LESS 3.18) + set(DEV_MODULE Development) +else() + set(DEV_MODULE Development.Module) +endif() -set(CMAKE_BUILD_TYPE_INIT "Release") +if(MSVC) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -openmp") +elseif((NOT APPLE) AND UNIX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lgomp") +endif() -project(flip LANGUAGES CXX) +find_package(Python 3.8 + REQUIRED COMPONENTS Interpreter Development.Module + OPTIONAL_COMPONENTS Development.SABIModule) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) -include(GNUInstallDirs) +if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") +endif() -option(FLIP_ENABLE_CUDA "Include CUDA version of flip" OFF) +execute_process( + COMMAND "${Python_EXECUTABLE}" -m nanobind --cmake_dir + OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_VARIABLE nanobind_ROOT) +find_package(nanobind CONFIG REQUIRED) -add_subdirectory(cpp) +nanobind_add_module(pbflip STABLE_ABI src/nanobindFLIP.cpp) + +install(TARGETS pbflip LIBRARY DESTINATION flip_evaluator) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1590055 --- /dev/null +++ b/LICENSE @@ -0,0 +1,31 @@ +BSD 3-Clause License + +Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES +SPDX-License-Identifier: BSD-3-Clause \ No newline at end of file diff --git a/README.md b/README.md index 30f9c9a..efef05c 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ ![Teaser image](images/teaser.png "Teaser image") -# ꟻLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.4) +# FLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.6) By -[Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin), +[Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin) and [Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), with @@ -14,57 +14,68 @@ Jim Nilsson, and [Peter Shirley](https://research.nvidia.com/person/peter-shirley). -This repository holds implementations of the [LDR-ꟻLIP](https://research.nvidia.com/publication/2020-07_FLIP) -and [HDR-ꟻLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics. -It also holds code for the ꟻLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). +This repository holds implementations of the [LDR-FLIP](https://research.nvidia.com/publication/2020-07_FLIP) +and [HDR-FLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics. +It also holds code for the FLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). -The changes made for the different versions of ꟻLIP are summarized in the [version list](misc/versionList.md). +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). -[A list of papers](misc/papersUsingFLIP.md) that use/cite ꟻLIP. +[A list of papers](https://github.com/NVlabs/flip/blob/main/misc/papersUsingFLIP.md) that use/cite FLIP. -[A note](misc/precision.md) about the precision of ꟻLIP. +[A note](https://github.com/NVlabs/flip/blob/main/misc/precision.md) about the precision of FLIP. [An image gallery](https://research.nvidia.com/node/3525) displaying a large quantity of reference/test images and corresponding error maps from different metrics. -**Note**: in v1.3, we switched to a *single header* ([FLIP.h](https://github.com/NVlabs/flip/blob/singleheader_WIP/cpp/FLIP.h)) for C++/CUDA for easier integration. +**Note**: since v1.6, the Python version of FLIP can now be installed via `pip install flip-evaluator`. + +**Note**: in v1.3, we switched to a *single header* ([FLIP.h](src/cpp/FLIP.h)) for C++/CUDA for easier integration. # License Copyright © 2020-2024, NVIDIA Corporation & Affiliates. All rights reserved. -This work is made available under a [BSD 3-Clause License](misc/LICENSE.md). +This work is made available under a [BSD 3-Clause License](LICENSE). -The repository distributes code for `tinyexr`, which is subject to a [BSD 3-Clause License](misc/LICENSE-third-party.md#bsd-3-clause-license),
-and `stb_image`, which is subject to an [MIT License](misc/LICENSE-third-party.md#mit-license). +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),
+and `stb_image`, which is subject to an [MIT License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#mit-license). -For individual contributions to the project, please confer the [Individual Contributor License Agreement](misc/CLA.md). +For individual contributions to the project, please confer the [Individual Contributor License Agreement](https://github.com/NVlabs/flip/blob/main/misc/CLA.md). For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). +# Simplest Way To Get Started +The simplest way to run FLIP to compare a test image `testImage.png` to a reference image `referenceImage.png` is as follows: +``` +pip install flip-evaluator +flip -r referenceImage.png -t testImage.png +``` +For more information about the tool's capabilities, try running `flip -h`. + +If you wish to use FLIP in your Python or C++ evaluation scripts, please read the next sections. + # Python (API and Tool) **Setup** (with pip): ``` -cd python -pip install -r requirements.txt . +pip install flip-evaluator ``` **Usage:**
API:
-See the example script `python/api_example.py`. Note that the script requires `matplotlib`. +See the example script `src/python/api_example.py`. Tool: ``` -python flip.py --reference reference.{exr|png} --test test.{exr|png} [--options] +flip --reference reference.{exr|png} --test test.{exr|png} [--options] ``` -See the [README](python/README.md) in the `python` folder and run `python flip.py -h` for further information and usage instructions. +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. # C++ and CUDA (API and Tool) **Setup:** -The `FLIP.sln` solution contains one CUDA backend project and one pure C++ backend project. +The `src/cpp/FLIP.sln` solution contains one CUDA backend project and one pure C++ backend project. 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). @@ -84,17 +95,17 @@ CUDA support is enabled via the `FLIP_ENABLE_CUDA`, which can be passed to CMake **Usage:**
API:
-See the [README](cpp/README.md). +See the [README](https://github.com/NVlabs/flip/blob/main/src/cpp/README.md). Tool: ``` flip[-cuda].exe --reference reference.{exr|png} --test test.{exr|png} [options] ``` -See the [README](cpp/README.md) in the `cpp` folder and run `flip[-cuda].exe -h` for further information and usage instructions. +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. # PyTorch (Loss Function) -**Setup** (with Anaconda3): +**Setup** (with Anaconda3 or Miniconda): ``` conda create -n flip_dl python numpy matplotlib conda activate flip_dl @@ -106,22 +117,22 @@ conda install -c conda-forge openexr-python *Remember to activate the* `flip_dl` *environment through* `conda activate flip_dl` *before using the loss function.* -LDR- and HDR-ꟻLIP are implemented as loss modules in `flip_loss.py`. An example where the loss function is used to train a simple autoencoder is provided in `train.py`. +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`. -See the [README](pytorch/README.md) in the `pytorch` folder for further information and usage instructions. +See the [README](https://github.com/NVlabs/flip/blob/main/src/pytorch/README.md) in the `pytorch` folder for further information and usage instructions. # Citation -If your work uses the ꟻLIP tool to find the errors between *low dynamic range* images, -please cite the LDR-ꟻLIP paper:
-[Paper](https://research.nvidia.com/publication/2020-07_FLIP) | [BibTeX](misc/LDRFLIP.txt) +If your work uses the FLIP tool to find the errors between *low dynamic range* images, +please cite the LDR-FLIP paper:
+[Paper](https://research.nvidia.com/publication/2020-07_FLIP) | [BibTeX](https://github.com/NVlabs/flip/blob/main/misc/LDRFLIP.txt) -If it uses the ꟻLIP tool to find the errors between *high dynamic range* images, -instead cite the HDR-ꟻLIP paper:
-[Paper](https://research.nvidia.com/publication/2021-05_HDR-FLIP) | [BibTeX](misc/HDRFLIP.txt) +If it uses the FLIP tool to find the errors between *high dynamic range* images, +instead cite the HDR-FLIP paper:
+[Paper](https://research.nvidia.com/publication/2021-05_HDR-FLIP) | [BibTeX](https://github.com/NVlabs/flip/blob/main/misc/HDRFLIP.txt) -Should your work use the ꟻLIP tool in a more general fashion, please cite the Ray Tracing Gems II article:
-[Chapter](https://link.springer.com/chapter/10.1007%2F978-1-4842-7185-8_19) | [BibTeX](misc/FLIP.txt) +Should your work use the FLIP tool in a more general fashion, please cite the Ray Tracing Gems II article:
+[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) # Acknowledgements We appreciate the following peoples' contributions to this repository: -Jonathan Granskog, Jacob Munkberg, Jon Hasselgren, Jefferson Amstutz, Alan Wolfe, Killian Herveau, Vinh Truong, Philippe Dagobert, Hannes Hergeth, Matt Pharr, Tizian Zeltner, Jan Honsbrok, and Chris Zhang. +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. diff --git a/misc/LICENSE.md b/misc/LICENSE.md deleted file mode 100644 index 0bb5e47..0000000 --- a/misc/LICENSE.md +++ /dev/null @@ -1,28 +0,0 @@ -# BSD 3-Clause License - -Copyright (c) 2020-2024, NVIDIA Corporation & AFFILIATES. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/misc/papersUsingFLIP.md b/misc/papersUsingFLIP.md index 045eca8..28fe357 100644 --- a/misc/papersUsingFLIP.md +++ b/misc/papersUsingFLIP.md @@ -1,8 +1,8 @@ -# List of papers/code that are using/citing ꟻLIP +# List of papers/code that are using/citing FLIP -Please let us know if you know of more papers that use/cite ꟻLIP. +Please let us know if you know of more papers that use/cite FLIP. -**Using ꟻLIP:** +**Using FLIP:** 1. Jae-Ho Nah, ["QuickETC2: Fast ETC2 texture compression using Luma differences"](https://dl.acm.org/doi/abs/10.1145/3414685.3417787), *ACM Transactions on Graphics (SIGGRAPH Asia)*, November 2020 Article No. 270 1. Johanna Engman and Hanna Nilsson, ["A Novel Perceptual Metric in Machine Learning"](https://lup.lub.lu.se/luur/download?func=downloadFile&recordOId=9022150&fileOId=9022160), Master's thesis, Lund University, 2020. @@ -315,7 +315,6 @@ Distant Lighting of Heterogeneous Translucent Objects"](https://people.compute.d 1. Arturo Salmi, Szabolcs Cséfalvay, James Imber, ["Fast Local Neural Regression for Low-Cost, Path Traced Lambertian Global Illumination"](https://arxiv.org/abs/2410.11625), arXiv:2410.11625, 2024. -1. Yu-Ting Wu, ["Efficient Environment Map Rendering Based on Decomposition"](https://onlinelibrary.wiley.com/doi/abs/10.1111/cgf.15264), Computer Graphics Forum, October 2024. 1. Victor Stenvers and Peter Vangorp, ["Image-Based Material Editing Using Perceptual Attributes or Ground-Truth Parameters"](https://dl.acm.org/doi/10.1145/3697294.3697301), Proceedings of 21st ACM SIGGRAPH Conference on Visual Media Production, November 2024. @@ -327,7 +326,7 @@ Distant Lighting of Heterogeneous Translucent Objects"](https://people.compute.d -**Citing ꟻLIP (but not using):** +**Citing FLIP (but not using):** 1. Jim Nilsson and Tomas Akenine-Möller, ["Understanding SSIM"](https://arxiv.org/pdf/2006.13846.pdf), *arXiv:2006.13846v2*, 2020. 1. Michael N. Mishourovsky, "Visually Lossless Colour Compression Technology", in *Smart Algorithms for Multimedia and Imaging*, edited by Michael N. Rychagov, Ekaterina V. Tolstaya, and Mikhail Y. Sirotenko, 2021. @@ -396,7 +395,7 @@ ACM Transactions on Graphics (Proceedings of SIGGRAPH ASIA), 2021. 1. Rafail Nikou, Aristeidis Tsaknis, Paschalis Margaritis, Stylianos Alvanos, Konstantinos-Filippos Kollias, George S. Maraslidis, Nikolaos Asimopoulos, Panagiotis Sarigiannidis, Vasileios Argyriou, George F. Fragulis, ["Machine learning data-based approaches for autism spectrum disorder classification utilising facial images"](https://pubs.aip.org/aip/acp/article-abstract/3220/1/050013/3315891/Machine-learning-data-based-approaches-for-autism), AIP Conference Proceedings, October 2024. -**Code/frameworks/tools that use ꟻLIP:** +**Code/frameworks/tools that use FLIP:** 1. [Falcor](https://github.com/NVIDIAGameWorks/Falcor). 1. [The FLOꟼ tool](https://github.com/jeremyong/flop/releases/tag/v1.618). diff --git a/misc/precision.md b/misc/precision.md index ffe740c..0d64ecf 100644 --- a/misc/precision.md +++ b/misc/precision.md @@ -1,13 +1,13 @@ # A note about precision -We have several different implementations of ꟻLIP (Python, PyTorch, C++, and CUDA) and we have tried to make +We have several different implementations of FLIP (Python, PyTorch, C++, and CUDA) and we have tried to make the implementations as similar as possible. However, there are several facts about these that make it very hard to get perfect matches between the implementations. These include: 1. Our computations are made using 32-bit floating-point arithmetic. 2. The order of operations matter, with respect to the result. - * We are using functions from `numpy` and `pytorch`, and these may be implemented differently. Even computing the mean of an - array can give different results because of this. + * We are using different versions of functions in the different versions of FLIP, and these may not all use similar implementations. + Even computing the mean of an array can give different results because of this. * 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 of cases give a different floating-point result compared to have the outer loop be `y` and the inner `x`. 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 @@ -19,3 +19,8 @@ These include: we have therefore updated the `images/correct_{ldr|hdr}flip_{cpp|cuda}.{png|exr}` images. 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. + +Furthermore, the Python version of FLIP, installed using `pip install flip_evaluator`, runs on Windows, Linux (tested on Ubuntu 24.04), +and OS X ($\ge$ 10.15). However, its output sometimes differ slightly between the different operative systems. +The references used for `flip_evaluator/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. diff --git a/misc/versionList.md b/misc/versionList.md index 556cf61..7936ecb 100644 --- a/misc/versionList.md +++ b/misc/versionList.md @@ -1,21 +1,36 @@ -# ꟻLIP Version List +# FLIP Version List In addition to various minor changes, the following was -changed for the different versions of ꟻLIP: +changed for the different versions of FLIP: + +# Version 1.6 (commit ?) +- Flipped the ꟻ in ꟻLIP. The entire name (FLIP) should now be readable on all devices. +- Published Python version of FLIP to PyPI (URL: https://pypi.org/project/flip-evaluator/). + - The Python version of FLIP (tool and API) is now installed by `pip install flip-evaluator`. + - 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. + - After installation, the tool can be run directly in a shell by `flip --reference reference.{png|exr} --test test.{png|exr}`. + - After installation, the FLIP API is available in Python by `import flip_evaluator as flip`. +- 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). +- Directory structure in the FLIP repository has been slightly altered to accomodate the Python version being published to PyPI. +- Updated Python/C++/CUDA test script. +- Various significant bugfixes. + +# Version 1.5 (commit -) +- Skipped version 1.5 due to PyPI-related mistake. Version 1.6 is the same as version 1.5 was supposed to be. # Version 1.4 (commits 6265f80 to 0349494) -- Changed the Python version of ꟻLIP so that it leverages the C++ code through [pybind11](https://github.com/pybind/pybind11). +- Changed the Python version of FLIP so that it leverages the C++ code through [pybind11](https://github.com/pybind/pybind11). - 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): - 20-47x faster for LDR/HDR CPU. - Timings for 1920x1080 images: - Python/LDR: 77 ms - Python/HDR: 1007 ms - - **NOTE**: The Python version can currently _not_ run the CUDA version of ꟻLIP (see issue [#22](https://github.com/NVlabs/flip/issues/22)). + - **NOTE**: The Python version can currently _not_ run the CUDA version of FLIP (see issue [#22](https://github.com/NVlabs/flip/issues/22)). - **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`). -- The Python version of ꟻLIP can now be installed using `pip` (run `pip install -r requirements.txt .` from the `python` folder). +- The Python version of FLIP can now be installed using `pip` (run `pip install -r requirements.txt .` from the `python` folder). - The code for the C++/CUDA tool is now in `FLIPToolHelpers.h`. - **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. -- **NOTE**: The median computation (used for automatic start and stop expsoure computations in HDR-ꟻLIP) 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. +- **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. - 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): - CPP/LDR: 86 ms - CPP/HDR: 1179 ms @@ -28,7 +43,7 @@ changed for the different versions of ꟻLIP: # Version 1.3 (commit a00bc7d) - Changed to CUDA 12.3. -- Rewrote C++ code so that ꟻLIP is in a single header (both CPP/CUDA). +- Rewrote C++ code so that FLIP is in a single header (both CPP/CUDA). - Rewrote `FLIP-tool.cpp` to use many more local functions to make the code easier to read. - 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. @@ -50,7 +65,7 @@ changed for the different versions of ꟻLIP: - CUDA/HDR: 136 ms # Version 1.1 (commit 4ed59e9) -- NVIDIA Source Code License changed to a 3-Clause BSD License +- NVIDIA Source Code License changed to a BSD 3-Clause License - Precision updates: - Constants use nine decimal digits (a float32 number has the same bit representation if stored with nine or more decimals in NumPy diff --git a/python/pyproject.toml b/pyproject.toml similarity index 57% rename from python/pyproject.toml rename to pyproject.toml index e6a7982..08710ba 100644 --- a/python/pyproject.toml +++ b/pyproject.toml @@ -31,10 +31,45 @@ ################################################################################# [build-system] -requires = [ - "setuptools", "pybind11>=2.10.1" -] +requires = ["scikit-build-core >=0.4.3", "nanobind >=1.3.2"] +build-backend = "scikit_build_core.build" [project] -name = "flip" -dynamic = ['version', 'description', 'readme', 'requires-python', 'license', 'authors'] \ No newline at end of file +name = "flip_evaluator" +version = "1.6" +description = "A Difference Evaluator for Alternating Images" +readme = "README.md" +requires-python = ">=3.8" +authors = [ + { name = "Pontus Ebelin" }, + { name = "Tomas Akenine-Möller" } +] +classifiers = [ + "License :: OSI Approved :: BSD License" +] + +[project.scripts] +flip = "flip_evaluator.flip_python_api:main" + +[project.urls] +Homepage = "https://github.com/nvlabs/flip" + +[tool.cibuildwheel] +build-verbosity = 1 +build = ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*", "cp313-*"] +test-command = ["flip -h"] + +[tool.cibuildwheel.macos.environment] +MACOSX_DEPLOYMENT_TARGET = "10.15" + +[tool.scikit-build] +minimum-version = "0.4" +sdist.include = ["src/flip_evaluator/__init__.py", "src/flip_evaluator/flip_python_api.py", "src/cpp/FLIP.h", "src/cpp/tool/*.h"] +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"] + +[tool.hatch.metadata.hooks.fancy-pypi-readme] +content-type = "text/markdown" + +build-dir = "build/{wheel_tag}" + +wheel.py-api = "cp312" \ No newline at end of file diff --git a/python/README.md b/python/README.md deleted file mode 100644 index dca085a..0000000 --- a/python/README.md +++ /dev/null @@ -1,117 +0,0 @@ -# ꟻLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.4) - -By -[Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin), -and -[Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), -with -Jim Nilsson, -[Magnus Oskarsson](https://www1.maths.lth.se/matematiklth/personal/magnuso/), -[Kalle Åström](https://www.maths.lu.se/staff/kalleastrom/), -[Mark D. Fairchild](https://www.rit.edu/directory/mdfpph-mark-fairchild), -and -[Peter Shirley](https://research.nvidia.com/person/peter-shirley). - -This [repository](https://github.com/NVlabs/flip) implements the [LDR-ꟻLIP](https://research.nvidia.com/publication/2020-07_FLIP) -and [HDR-ꟻLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics in Python, using the C++ implementation through [pybind11](https://github.com/pybind/pybind11). -Similarly, it implements the ꟻLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). - -# License - -Copyright © 2020-2024, NVIDIA Corporation & Affiliates. All rights reserved. - -This work is made available under a [BSD 3-Clause License](../misc/LICENSE.md). - -The repository distributes code for `tinyexr`, which is subject to a [BSD 3-Clause License](../misc/LICENSE-third-party.md#bsd-3-clause-license),
-and `stb_image`, which is subject to an [MIT License](../misc/LICENSE-third-party.md#mit-license). - -For individual contributions to the project, please confer the [Individual Contributor License Agreement](../misc/CLA.md). - -For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). - -# Python (API and Tool) -- **Setup** (with pip, from root directory): - ``` - cd python - pip install -r requirements.txt . - ``` - After this, you may `import flip` from any Python script. -- Usage (API): See example in the script `flip/api_example.py`. Note that the script requires `matplotlib`. -- Usage (tool): `python flip.py --reference reference.{exr|png} --test test.{exr|png} [--options]`, where the list of options can be seen by `python flip.py -h`. -- Tested with pip 24.0, Python 3.11.8, pybind11 2.11.1, and C++20. -- The code that implements ꟻLIP metrics and the ꟻLIP tool is available in [FLIP.h](https://github.com/NVlabs/flip/blob/main/cpp/FLIP.h) and [flip/cpp/tool](https://github.com/NVlabs/flip/blob/main/cpp/tool), respectively. - The Python API is provided in `flip/python/flip/main.py`. - `../tests/test.py` contains simple tests used to test whether code updates alter results. -- Weighted histograms are output as Python scripts. Running the script will create a PDF version of the histogram. Notice that those scripts require `numpy` and `matplotlib`, both of which are automatically installed during setup. -- The naming convention used for the ꟻLIP tool's output is as follows (where `ppd` is the assumed number of pixels per degree, - `tm` is the tone mapper assumed by HDR-ꟻLIP, `cstart` and `cstop` are the shortest and longest exposures, respectively, assumed by HDR-ꟻLIP, - with `p` indicating a positive value and `m` indicating a negative value, - `N` is the number of exposures used in the HDR-ꟻLIP calculation, `nnn` is a counter used to sort the intermediate results, - and `exp` is the exposure used for the intermediate LDR image / ꟻLIP map): - - **Default:** - - *Low dynamic range images:*
- - LDR-ꟻLIP: `flip...ppd.ldr.png`
- Weighted histogram: `weighted_histogram.reference>..ppd.ldr.py`
- Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.ldr.py`
- Text file: `pooled_values...ppd.ldr.txt`
- - *High dynamic range images:*
- - HDR-ꟻLIP: `flip...ppd.hdr.._to_..png`
- Exposure map: `exposure_map...ppd.hdr.._to_..png`
- Intermediate LDR-ꟻLIP maps: `flip...ppd.ldr....png`
- Intermediate LDR images: `....png`
- Weighted histogram: `weighted_histogram...ppd.hdr.._to_..py`
- Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.hdr.._to_..py`
- Text file: `pooled_values...ppd.hdr.._to_..txt`
- - **With** `--basename ` **(note: not applicable if more than one test image is evaluated):** - - *Low dynamic range images:*
- - LDR-ꟻLIP: `.png`
- Weighted histogram: `.py`
- Overlapping weighted histogram: N/A
- Text file: `.txt`
- - *High dynamic range images:*
- - HDR-ꟻLIP: `.png`
- Exposure map: `.exposure_map.png`
- Intermediate LDR-ꟻLIP maps: `..png`
- Intermediate LDR images: `.reference|test..png`
- Weighted histogram: `.py`
- Overlapping weighted histogram: N/A
- Text file: `.txt`
- -**Example usage:** -To test the API, please inspect the `flip/api_example.py` script. This shows how the available API commands may be used. Note that the script requires `matplotlib`. -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-ꟻLIP. For that, use the tool or the C++ API in [FLIP.h](https://github.com/NVlabs/flip/blob/main/cpp/FLIP.h). - -To test the tool, first navigate to the directory containing the `flip.py` script. Then start a shell and try: - ``` - python flip.py -r ../images/reference.exr -t ../images/test.exr - ``` -The result should be: - ``` -Invoking HDR-FLIP - Pixels per degree: 67 - Assumed tone mapper: ACES - Start exposure: -12.5423 - Stop exposure: 0.9427 - Number of exposures: 14 - -FLIP between reference image and test image : - Mean: 0.283478 - Weighted median: 0.339430 - 1st weighted quartile: 0.251122 - 3rd weighted quartile: 0.434673 - Min: 0.003123 - Max: 0.962022 - Evaluation time: seconds - ``` -where `` is the time it took to evaluate HDR-ꟻLIP. 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` -in the directory containing the `flip.py` script, and we urge you to inspect those, which will reveal where the errors in the test image are located. diff --git a/python/requirements.txt b/python/requirements.txt deleted file mode 100644 index 8248f97..0000000 --- a/python/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -matplotlib -numpy \ No newline at end of file diff --git a/python/setup.py b/python/setup.py deleted file mode 100644 index e6c4200..0000000 --- a/python/setup.py +++ /dev/null @@ -1,77 +0,0 @@ -################################################################################# -# Copyright (c) 2020-2024, NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# SPDX-FileCopyrightText: Copyright (c) 2020-2024 NVIDIA CORPORATION & AFFILIATES -# SPDX-License-Identifier: BSD-3-Clause -################################################################################# - -from pybind11.setup_helpers import Pybind11Extension -from setuptools import setup -import os -import sys - -__version__ = "1.4" - -# Separate compiler options for Windows. -extra_compile_args = ["-DNDEBUG"] -if sys.platform.startswith("win"): - extra_compile_args = ["-openmp"] - extra_link_args = [] -# Use OpenMP if environment variable is set or not on a Mac. -elif os.environ.get("USEOPENMP") or not sys.platform.startswith("darwin"): - extra_compile_args = ["-fopenmp"] - extra_link_args = ["-lgomp"] - -ext_modules = [ - Pybind11Extension( - "pbflip", # Name of pybind11 module. - ["flip/main.cpp"], - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - cxx_std=20 - ), -] - -# Get description from README. -with open(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.md'), encoding='utf-8') as f: - long_description = f.read() - -# Run setup. -setup( - name="flip", - version=__version__, - author="NVIDIA", - author_email="pandersson@nvidia.com", - description="A Difference Evaluator for Alternating Images", - url="https://github.com/nvlabs/flip", - license="BSD", - long_description=long_description, - long_description_content_type='text/markdown', - ext_modules=ext_modules, - python_requires=">=3.7" -) \ No newline at end of file diff --git a/python/flip.py b/src/CMakeLists.txt similarity index 80% rename from python/flip.py rename to src/CMakeLists.txt index 4421d01..064beb6 100644 --- a/python/flip.py +++ b/src/CMakeLists.txt @@ -30,10 +30,22 @@ # SPDX-License-Identifier: BSD-3-Clause ################################################################################# -import flip -import sys +cmake_minimum_required(VERSION 3.9) -if __name__ == '__main__': - flip.execute(sys.argv) +set(CMAKE_DISABLE_SOURCE_CHANGES ON) +set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_BUILD_TYPE_INIT "Release") + +project(flip LANGUAGES CXX) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}) +include(GNUInstallDirs) + +option(FLIP_ENABLE_CUDA "Include CUDA version of flip" OFF) + +add_subdirectory(cpp) diff --git a/cmake/FLIPConfig.cmake b/src/cmake/FLIPConfig.cmake similarity index 100% rename from cmake/FLIPConfig.cmake rename to src/cmake/FLIPConfig.cmake diff --git a/cpp/CMakeLists.txt b/src/cpp/CMakeLists.txt similarity index 100% rename from cpp/CMakeLists.txt rename to src/cpp/CMakeLists.txt diff --git a/cpp/FLIP.h b/src/cpp/FLIP.h similarity index 98% rename from cpp/FLIP.h rename to src/cpp/FLIP.h index c700032..232d72a 100644 --- a/cpp/FLIP.h +++ b/src/cpp/FLIP.h @@ -52,8 +52,8 @@ // // 1. FLIP::evaluate(const bool useHDR, FLIP::Parameters& parameters, FLIP::image& referenceImageInput, FLIP::image& testImageInput, // FLIP::image& errorMapFLIPOutput, FLIP::image& maxErrorExposureMapOutput, -// const bool returnLDRFLIPImages, std::vector*>& hdrOutputFlipLDRImages, -// const bool returnLDRImages, std::vector*>& hdrOutputLDRImages) +// const bool returnIntermediateLDRFLIPImages, std::vector*>& intermediateLDRFLIPImages, +// const bool returnIntermediateLDRImages, std::vector*>& intermediateLDRImages) // // # This is the one with most parameters and is used by FLIP-tool.cpp in main(). // # See the function at the bottom of this file for detailed description of the parameters. @@ -1137,7 +1137,7 @@ namespace FLIP this->init({ size, 1, 1 }); if (this->mvpHostData != nullptr) { - memcpy(this->mvpHostData, pColorMap, size * sizeof(color3)); + memcpy((void*) this->mvpHostData, pColorMap, size * sizeof(color3)); } } #else // FLIP_ENABLE_CUDA @@ -1235,7 +1235,7 @@ namespace FLIP void setPixels(const float* pPixels, const int width, const int height) { this->init({ width, height, 1 }); - memcpy(this->mvpHostData, pPixels, size_t(width) * height * sizeof(T)); + memcpy((void*) this->mvpHostData, pPixels, size_t(width) * height * sizeof(T)); } #else T get(int x, int y, int z) @@ -1262,7 +1262,7 @@ namespace FLIP { this->init({ width, height, 1}); } - memcpy(this->mvpHostData, pPixels, size_t(width) * height * sizeof(T)); + memcpy((void*) this->mvpHostData, pPixels, size_t(width) * height * sizeof(T)); this->mState = CudaTensorState::HOST_ONLY; } #endif @@ -1439,7 +1439,7 @@ namespace FLIP { if (this->mDim.x == srcImage.getWidth() && this->mDim.y == srcImage.getHeight() && this->mDim.z == srcImage.getDepth()) { - memcpy(this->mvpHostData, srcImage.getHostData(), this->mVolume * sizeof(T)); + memcpy((void*) this->mvpHostData, srcImage.getHostData(), this->mVolume * sizeof(T)); } } @@ -2321,16 +2321,16 @@ namespace FLIP * @param[out] errorMapFLIPOutput The FLIP error image in [0,1], a single channel (grayscale). The user should map it using MapMagma if that is desired (with: errorMapWithMagma.colorMap(errorMapFLIP, FLIP::magmaMap);) * @param[out] maxErrorExposureMapOutput Exposure map output (only for HDR content). - * @param[in] returnLDRFLIPImages True if the next argument should be filled in by FLIP::evaluate(). - * @param[out] hdrOutputFlipLDRImages A list of temporary output LDR-FLIP error maps (in grayscale) from HDR-FLIP. + * @param[in] returnIntermediateLDRFLIPImages True if the next argument should be filled in by FLIP::evaluate(). + * @param[out] intermediateLDRFLIPImages A list of temporary output LDR-FLIP error maps (in grayscale) from HDR-FLIP. See explanation of the errorMapFLIPOutput parameter for how to convert the maps to magma. - * @param[in] returnLDRImages True if the next argument should be filled in by FLIP::evaluate(). - * @param[out] hdrOutputLDRImages A list of temporary tonemapped output LDR images (in linear RGB) from HDR-FLIP. Images in this order: Ref0, Test0, Ref1, Test1,... + * @param[in] returnIntermediateLDRImages True if the next argument should be filled in by FLIP::evaluate(). + * @param[out] intermediateLDRImages A list of temporary tonemapped output LDR images (in linear RGB) from HDR-FLIP. Images in this order: Ref0, Test0, Ref1, Test1,... */ static void evaluate(FLIP::image& referenceImageInput, FLIP::image& testImageInput, const bool useHDR, FLIP::Parameters& parameters, FLIP::image& errorMapFLIPOutput, FLIP::image& maxErrorExposureMapOutput, - const bool returnLDRFLIPImages, std::vector*>& hdrOutputFlipLDRImages, - const bool returnLDRImages, std::vector*>& hdrOutputLDRImages) + const bool returnIntermediateLDRFLIPImages, std::vector*>& intermediateLDRFLIPImages, + const bool returnIntermediateLDRImages, std::vector*>& intermediateLDRImages) { FLIP::image referenceImage(referenceImageInput.getWidth(), referenceImageInput.getHeight()); FLIP::image testImage(referenceImageInput.getWidth(), referenceImageInput.getHeight()); @@ -2383,15 +2383,15 @@ namespace FLIP tImage.toneMap(parameters.tonemapper); rImage.clamp(); tImage.clamp(); - if (returnLDRImages) + if (returnIntermediateLDRImages) { - hdrOutputLDRImages.push_back(new FLIP::image(rImage)); - hdrOutputLDRImages.push_back(new FLIP::image(tImage)); + intermediateLDRImages.push_back(new FLIP::image(rImage)); + intermediateLDRImages.push_back(new FLIP::image(tImage)); } tmpErrorMap.LDR_FLIP(rImage, tImage, parameters.PPD); - if (returnLDRFLIPImages) + if (returnIntermediateLDRFLIPImages) { - hdrOutputFlipLDRImages.push_back(new FLIP::image(tmpErrorMap)); + intermediateLDRFLIPImages.push_back(new FLIP::image(tmpErrorMap)); } errorMapFLIPOutput.setMaxExposure(tmpErrorMap, maxErrorExposureMapOutput, float(i) / (parameters.numExposures - 1)); } @@ -2408,9 +2408,9 @@ namespace FLIP static void evaluate(FLIP::image& referenceImageInput, FLIP::image& testImageInput, const bool useHDR, FLIP::Parameters& parameters, FLIP::image& errorMapFLIPOutput, FLIP::image& maxErrorExposureMapOutput) { - std::vector*> hdrOutputFlipLDRImages; - std::vector*> hdrOutputLDRImages; - FLIP::evaluate(referenceImageInput, testImageInput, useHDR, parameters, errorMapFLIPOutput, maxErrorExposureMapOutput, false, hdrOutputFlipLDRImages, false, hdrOutputLDRImages); + std::vector*> intermediateLDRFLIPImages; + std::vector*> intermediateLDRImages; + FLIP::evaluate(referenceImageInput, testImageInput, useHDR, parameters, errorMapFLIPOutput, maxErrorExposureMapOutput, false, intermediateLDRFLIPImages, false, intermediateLDRImages); } // This variant does not return the exposure map, which may also be used quite seldom. @@ -2447,7 +2447,7 @@ namespace FLIP { FLIP::image referenceImage; FLIP::image testImage; - FLIP::image errorMapFLIPOutputImage(imageWidth, imageHeight); + FLIP::image errorMapFLIPOutputImage(imageWidth, imageHeight, 0.0f); referenceImage.setPixels(referenceThreeChannelImage, imageWidth, imageHeight); testImage.setPixels(testThreeChannelImage, imageWidth, imageHeight); diff --git a/cpp/FLIP.sln b/src/cpp/FLIP.sln similarity index 100% rename from cpp/FLIP.sln rename to src/cpp/FLIP.sln diff --git a/cpp/README.md b/src/cpp/README.md similarity index 65% rename from cpp/README.md rename to src/cpp/README.md index 6419130..fd0e769 100644 --- a/cpp/README.md +++ b/src/cpp/README.md @@ -1,7 +1,7 @@ -# ꟻLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.4) +# FLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.6) By -[Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin), +[Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin) and [Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), with @@ -12,12 +12,12 @@ Jim Nilsson, and [Peter Shirley](https://research.nvidia.com/person/peter-shirley). -This [repository](https://github.com/NVlabs/flip) holds implementations of the [LDR-ꟻLIP](https://research.nvidia.com/publication/2020-07_FLIP) -and [HDR-ꟻLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics in C++ and CUDA. -It also holds code for the ꟻLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). +This [repository](https://github.com/NVlabs/flip) holds implementations of the [LDR-FLIP](https://research.nvidia.com/publication/2020-07_FLIP) +and [HDR-FLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics in C++ and CUDA. +It also holds code for the FLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). -Note that since v1.2, we use separated convolutions for the C++ and CUDA versions of ꟻLIP. A note explaining those -can be found [here](misc/separatedConvolutions.pdf). +Note that since v1.2, we use separated convolutions for the C++ and CUDA versions of FLIP. A note explaining those +can be found [here](https://github.com/NVlabs/flip/blob/main/misc/separatedConvolutions.pdf). With v1.3, we have switched to a single header [FLIP.h](FLIP.h) for easier integration into other projects. @@ -28,12 +28,12 @@ Since v1.4, the majority of the code for the tool is contained in [FLIPToolHelpe Copyright © 2020-2024, NVIDIA Corporation & Affiliates. All rights reserved. -This work is made available under a [BSD 3-Clause License](../misc/LICENSE.md). +This work is made available under a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/LICENSE). -The repository distributes code for `tinyexr`, which is subject to a [BSD 3-Clause License](../misc/LICENSE-third-party.md#bsd-3-clause-license),
-and `stb_image`, which is subject to an [MIT License](../misc/LICENSE-third-party.md#mit-license). +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),
+and `stb_image`, which is subject to an [MIT License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#mit-license). -For individual contributions to the project, please confer the [Individual Contributor License Agreement](../misc/CLA.md). +For individual contributions to the project, please confer the [Individual Contributor License Agreement](https://github.com/NVlabs/flip/blob/main/misc/CLA.md). For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). @@ -62,29 +62,29 @@ For business inquiries, please visit our website and submit the form: [NVIDIA Re 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`. `FLIP_LIBRARY` option allows to output a library rather than an executable. - 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`. -- Tested on Windows 10 version 22H2 and Windows 11 version 23H2 with CUDA 12.3. Compiled with Visual Studio 2022. If you use another version of CUDA, you will need to change the `CUDA 12.3` strings in the `CUDA.vcxproj` file accordingly. -- `../tests/test.py` contains simple tests used to test whether code updates alter results. -- Weighted histograms are output as Python scripts. Running the script will create a PDF version of the histogram. Notice that those scripts require `numpy` and `matplotlib`, both of which may be installed using pip. These are automantically installed when installing the Python version of ꟻLIP (see [README.md](https://github.com/NVlabs/flip/blob/main/python/README.md)). -- The naming convention used for the ꟻLIP tool's output is as follows (where `ppd` is the assumed number of pixels per degree, - `tm` is the tone mapper assumed by HDR-ꟻLIP, `cstart` and `cstop` are the shortest and longest exposures, respectively, assumed by HDR-ꟻLIP, +- 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. +- `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. +- 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. +- The naming convention used for the FLIP tool's output is as follows (where `ppd` is the assumed number of pixels per degree, + `tm` is the tone mapper assumed by HDR-FLIP, `cstart` and `cstop` are the shortest and longest exposures, respectively, assumed by HDR-FLIP, with `p` indicating a positive value and `m` indicating a negative value, - `N` is the number of exposures used in the HDR-ꟻLIP calculation, `nnn` is a counter used to sort the intermediate results, - and `exp` is the exposure used for the intermediate LDR image / ꟻLIP map): + `N` is the number of exposures used in the HDR-FLIP calculation, `nnn` is a counter used to sort the intermediate results, + and `exp` is the exposure used for the intermediate LDR image / FLIP map): **Default:** *Low dynamic range images:*
- LDR-ꟻLIP: `flip...ppd.ldr.png`
+ LDR-FLIP: `flip...ppd.ldr.png`
Weighted histogram: `weighted_histogram.reference>..ppd.ldr.py`
Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.ldr.py`
Text file: `pooled_values...ppd.ldr.txt`
*High dynamic range images:*
- HDR-ꟻLIP: `flip...ppd.hdr.._to_..png`
+ HDR-FLIP: `flip...ppd.hdr.._to_..png`
Exposure map: `exposure_map...ppd.hdr.._to_..png`
- Intermediate LDR-ꟻLIP maps: `flip...ppd.ldr....png`
+ Intermediate LDR-FLIP maps: `flip...ppd.ldr....png`
Intermediate LDR images: `....png`
Weighted histogram: `weighted_histogram...ppd.hdr.._to_..py`
Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.hdr.._to_..py`
@@ -94,23 +94,23 @@ For business inquiries, please visit our website and submit the form: [NVIDIA Re *Low dynamic range images:*
- LDR-ꟻLIP: `.png`
+ LDR-FLIP: `.png`
Weighted histogram: `.py`
Overlapping weighted histogram: N/A
Text file: `.txt`
*High dynamic range images:*
- HDR-ꟻLIP: `.png`
+ HDR-FLIP: `.png`
Exposure map: `.exposure_map.png`
- Intermediate LDR-ꟻLIP maps: `..png`
+ Intermediate LDR-FLIP maps: `..png`
Intermediate LDR images: `.reference|test..png`
Weighted histogram: `.py`
Overlapping weighted histogram: N/A
Text file: `.txt`
**Example usage:** -After compiling the `FLIP.sln` project, navigate to the `flip[-cuda].exe` executable and try: +After compiling the `src/cpp/FLIP.sln` project, navigate to the `flip[-cuda].exe` executable and try: ``` flip[-cuda].exe -r ../../../images/reference.exr -t ../../../images/test.exr ``` @@ -131,6 +131,8 @@ FLIP between reference image and test image : Min: 0.003123 Max: 0.962022 Evaluation time: seconds + FLIP error map location: + FLIP exposure map location: ``` -where `` is the time it took to evaluate HDR-ꟻLIP. 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` +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` 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. diff --git a/cpp/tool/CMakeLists.txt b/src/cpp/tool/CMakeLists.txt similarity index 100% rename from cpp/tool/CMakeLists.txt rename to src/cpp/tool/CMakeLists.txt diff --git a/cpp/tool/CPP.vcxproj b/src/cpp/tool/CPP.vcxproj similarity index 100% rename from cpp/tool/CPP.vcxproj rename to src/cpp/tool/CPP.vcxproj diff --git a/cpp/tool/CPP.vcxproj.filters b/src/cpp/tool/CPP.vcxproj.filters similarity index 100% rename from cpp/tool/CPP.vcxproj.filters rename to src/cpp/tool/CPP.vcxproj.filters diff --git a/cpp/tool/CUDA.vcxproj b/src/cpp/tool/CUDA.vcxproj similarity index 99% rename from cpp/tool/CUDA.vcxproj rename to src/cpp/tool/CUDA.vcxproj index 444caa8..f13caee 100644 --- a/cpp/tool/CUDA.vcxproj +++ b/src/cpp/tool/CUDA.vcxproj @@ -51,7 +51,7 @@ - + @@ -113,6 +113,6 @@ - + \ No newline at end of file diff --git a/cpp/tool/CUDA.vcxproj.filters b/src/cpp/tool/CUDA.vcxproj.filters similarity index 100% rename from cpp/tool/CUDA.vcxproj.filters rename to src/cpp/tool/CUDA.vcxproj.filters diff --git a/cpp/tool/FLIP-tool.cpp b/src/cpp/tool/FLIP-tool.cpp similarity index 100% rename from cpp/tool/FLIP-tool.cpp rename to src/cpp/tool/FLIP-tool.cpp diff --git a/cpp/tool/FLIP-tool.cu b/src/cpp/tool/FLIP-tool.cu similarity index 100% rename from cpp/tool/FLIP-tool.cu rename to src/cpp/tool/FLIP-tool.cu diff --git a/cpp/tool/FLIPToolHelpers.h b/src/cpp/tool/FLIPToolHelpers.h similarity index 92% rename from cpp/tool/FLIPToolHelpers.h rename to src/cpp/tool/FLIPToolHelpers.h index 57a558a..d5a4719 100644 --- a/cpp/tool/FLIPToolHelpers.h +++ b/src/cpp/tool/FLIPToolHelpers.h @@ -48,6 +48,7 @@ // Code by Pontus Ebelin (formerly Andersson), Jim Nilsson, and Tomas Akenine-Moller. +#pragma once #include #include #include @@ -232,15 +233,15 @@ namespace FLIPTool } // Optionally store the intermediate LDR images and LDR-FLIP error maps produced during the evaluation of HDR-FLIP. - static void saveHDROutputLDRImages(commandline& commandLine, const FLIP::Parameters& parameters, const std::string& basename, const FLIP::filename& flipFileName, + static void saveIntermediateHDRFLIPOutput(commandline& commandLine, const FLIP::Parameters& parameters, const std::string& basename, const FLIP::filename& flipFileName, const FLIP::filename& referenceFileName, const FLIP::filename& testFileName, const std::string& destinationDirectory, - std::vector*> hdrOutputFlipLDRImages, std::vector*> hdrOutputLDRImages) + std::vector*> intermediateLDRFLIPImages, std::vector*> intermediateLDRImages) { - if (hdrOutputLDRImages.size() > 0) + if (intermediateLDRImages.size() > 0) { FLIP::filename rFileName(".png"); FLIP::filename tFileName(".png"); - if (hdrOutputLDRImages.size() != parameters.numExposures * 2) + if (intermediateLDRImages.size() != size_t(parameters.numExposures * 2)) { std::cout << "FLIP tool error: the number of LDR images from HDR-FLIP is not the expected number.\nExiting.\n"; exit(EXIT_FAILURE); @@ -262,10 +263,10 @@ namespace FLIPTool rFileName.setName(basename + ".reference." + "." + expCount); tFileName.setName(basename + ".test." + "." + expCount); } - FLIP::image* rImage = hdrOutputLDRImages[0]; - FLIP::image* tImage = hdrOutputLDRImages[1]; - hdrOutputLDRImages.erase(hdrOutputLDRImages.begin()); - hdrOutputLDRImages.erase(hdrOutputLDRImages.begin()); + FLIP::image* rImage = intermediateLDRImages[0]; + FLIP::image* tImage = intermediateLDRImages[1]; + intermediateLDRImages.erase(intermediateLDRImages.begin()); + intermediateLDRImages.erase(intermediateLDRImages.begin()); rImage->LinearRGBTosRGB(); tImage->LinearRGBTosRGB(); ImageHelpers::pngSave(destinationDirectory + "/" + rFileName.toString(), *rImage); @@ -274,9 +275,9 @@ namespace FLIPTool delete tImage; } } - if (hdrOutputFlipLDRImages.size() > 0) + if (intermediateLDRFLIPImages.size() > 0) { - if (hdrOutputFlipLDRImages.size() != parameters.numExposures) + if (intermediateLDRFLIPImages.size() != size_t(parameters.numExposures)) { std::cout << "FLIP tool error: the number of FLIP LDR images from HDR-FLIP is not the expected number.\nExiting.\n"; exit(EXIT_FAILURE); @@ -288,8 +289,8 @@ namespace FLIPTool std::string expCount, expString; setExposureStrings(i, parameters.startExposure + i * exposureStepSize, expCount, expString); - FLIP::image* flipImage = hdrOutputFlipLDRImages[0]; - hdrOutputFlipLDRImages.erase(hdrOutputFlipLDRImages.begin()); + FLIP::image* flipImage = intermediateLDRFLIPImages[0]; + intermediateLDRFLIPImages.erase(intermediateLDRFLIPImages.begin()); FLIP::image pngResult(flipImage->getWidth(), flipImage->getHeight()); @@ -317,7 +318,8 @@ namespace FLIPTool static void gatherStatisticsAndSaveOutput(commandline& commandLine, FLIP::image& errorMapFLIP, FLIPPooling::pooling& pooledValues, const std::string& destinationDirectory, const FLIP::filename& referenceFileName, const FLIP::filename& testFileName, const FLIP::filename& histogramFileName, - const FLIP::filename& txtFileName, const std::string& FLIPString, const float time, const uint32_t testFileCount, const bool saveOverlappedHistogram, const size_t verbosity) + const FLIP::filename& txtFileName, const FLIP::filename& flipFileName, const FLIP::filename& exposureFileName, const std::string& FLIPString, const float time, + const uint32_t testFileCount, const bool saveOverlappedHistogram, const bool useHDR, const size_t verbosity) { for (int y = 0; y < errorMapFLIP.getHeight(); y++) { @@ -412,6 +414,14 @@ namespace FLIPTool std::cout << " Min: " << FIXED_DECIMAL_DIGITS(minValue, 6) << "\n"; std::cout << " Max: " << FIXED_DECIMAL_DIGITS(maxValue, 6) << "\n"; std::cout << " Evaluation time: " << FIXED_DECIMAL_DIGITS(time, 4) << " seconds\n"; + if (!commandLine.optionSet("no-error-map")) + { + std::cout << " FLIP error map location: " << destinationDirectory + "/" + flipFileName.toString() << "\n"; + } + if (!commandLine.optionSet("no-exposure-map") && useHDR) + { + std::cout << " FLIP exposure map location: " << destinationDirectory + "/" + exposureFileName.toString() << "\n"; + } std::cout << ((testFileCount < commandLine.getOptionValues("test").size()) ? "\n" : ""); } } @@ -470,13 +480,13 @@ namespace FLIPTool std::string FLIPString = "FLIP"; int MajorVersion = 1; - int MinorVersion = 4; + int MinorVersion = 6; if (commandLine.optionSet("help")) { std::cout << "FLIP v" << MajorVersion << "." << MinorVersion << ".\n"; commandLine.print(); - exit(EXIT_FAILURE); + exit(EXIT_SUCCESS); } if (!commandLine.optionSet("reference")) { @@ -493,7 +503,7 @@ namespace FLIPTool std::cout << "Error: you need to set a test image filename.\n Typically done with '-t testimg.{png,exr}' or '--test testimg.{png,exr}'.\n Use -h or --help for help message. Exiting\n"; exit(EXIT_FAILURE); } - if (commandLine.optionSet("help") || (commandLine.optionSet("basename") && commandLine.getOptionValues("test").size() != 1) || commandLine.getError()) + if ((commandLine.optionSet("basename") && commandLine.getOptionValues("test").size() != 1) || commandLine.getError()) { if (commandLine.getError()) { @@ -553,8 +563,8 @@ namespace FLIPTool for (auto& testFileNameString : commandLine.getOptionValues("test")) { pooledValues = FLIPPooling::pooling(100); // Reset pooledValues to remove accumulation issues. - std::vector*> hdrOutputFlipLDRImages; - std::vector*> hdrOutputLDRImages; + std::vector*> intermediateLDRFLIPImages; + std::vector*> intermediateLDRImages; testFileName = testFileNameString; if (!std::filesystem::exists(testFileName.toString())) @@ -585,12 +595,12 @@ namespace FLIPTool FLIP::image maxErrorExposureMap(referenceImage.getWidth(), referenceImage.getHeight()); auto t0 = std::chrono::high_resolution_clock::now(); - FLIP::evaluate(referenceImage, testImage, bUseHDR, parameters, errorMapFLIP, maxErrorExposureMap, returnLDRFLIPImages, hdrOutputFlipLDRImages, returnLDRImages, hdrOutputLDRImages); + FLIP::evaluate(referenceImage, testImage, bUseHDR, parameters, errorMapFLIP, maxErrorExposureMap, returnLDRFLIPImages, intermediateLDRFLIPImages, returnLDRImages, intermediateLDRImages); float time = std::chrono::duration_cast(std::chrono::high_resolution_clock::now() - t0).count() / 1000000.0f; saveErrorAndExposureMaps(bUseHDR, commandLine, parameters, basename, errorMapFLIP, maxErrorExposureMap, destinationDirectory, referenceFileName, testFileName, histogramFileName, txtFileName, flipFileName, exposureFileName, verbosity, testFileCount); - saveHDROutputLDRImages(commandLine, parameters, basename, flipFileName, referenceFileName, testFileName, destinationDirectory, hdrOutputFlipLDRImages, hdrOutputLDRImages); - gatherStatisticsAndSaveOutput(commandLine, errorMapFLIP, pooledValues, destinationDirectory, referenceFileName, testFileName, histogramFileName, txtFileName, FLIPString, time, ++testFileCount, saveOverlappedHistogram, verbosity); + saveIntermediateHDRFLIPOutput(commandLine, parameters, basename, flipFileName, referenceFileName, testFileName, destinationDirectory, intermediateLDRFLIPImages, intermediateLDRImages); + gatherStatisticsAndSaveOutput(commandLine, errorMapFLIP, pooledValues, destinationDirectory, referenceFileName, testFileName, histogramFileName, txtFileName, flipFileName, exposureFileName, FLIPString, time, ++testFileCount, saveOverlappedHistogram, bUseHDR, verbosity); // Save first set of results for overlapped histogram. if (saveOverlappedHistogram && testFileCount == 1) diff --git a/cpp/tool/commandline.h b/src/cpp/tool/commandline.h similarity index 99% rename from cpp/tool/commandline.h rename to src/cpp/tool/commandline.h index e4631d3..2e03c63 100644 --- a/cpp/tool/commandline.h +++ b/src/cpp/tool/commandline.h @@ -234,7 +234,7 @@ class commandline { longOptionName = allowedOption.longName; shortOptionName = allowedOption.shortName; - if (bIsLong && longOptionName == optionString || !bIsLong && shortOptionName == optionString) + if ((bIsLong && longOptionName == optionString) || (!bIsLong && shortOptionName == optionString)) { bFound = true; atOption = true; diff --git a/cpp/tool/filename.h b/src/cpp/tool/filename.h similarity index 99% rename from cpp/tool/filename.h rename to src/cpp/tool/filename.h index 61e718b..f0cd6f6 100644 --- a/cpp/tool/filename.h +++ b/src/cpp/tool/filename.h @@ -170,7 +170,7 @@ namespace FLIP int fileNameStringLength = int(fileNameString.length()); int totalLength = directoryStringLength + fileNameStringLength; - if (totalLength > maxLen) + if (size_t(totalLength) > maxLen) { // First shorten the directory. int overflow = totalLength - int(maxLen); @@ -419,7 +419,6 @@ namespace FLIP return true; } - // Must be at least one slash to contain a directory. size_t iLastSlash = path.find_last_of("\\/"); if (iLastSlash != std::string::npos) diff --git a/cpp/tool/imagehelpers.h b/src/cpp/tool/imagehelpers.h similarity index 100% rename from cpp/tool/imagehelpers.h rename to src/cpp/tool/imagehelpers.h diff --git a/cpp/tool/pooling.h b/src/cpp/tool/pooling.h similarity index 99% rename from cpp/tool/pooling.h rename to src/cpp/tool/pooling.h index 7b5f6ff..5edeafe 100644 --- a/cpp/tool/pooling.h +++ b/src/cpp/tool/pooling.h @@ -412,7 +412,6 @@ namespace FLIPPooling size_t numPixels = size_t(imgWidth) * size_t(imgHeight); size_t bucketsSize = firstPooledValues.getHistogram().size(); - size_t bucketsSize2 = this->mHistogram.size(); T bucketStep = this->mHistogram.getBucketStep(); float firstPooledValuesMean = firstPooledValues.getMean(); diff --git a/cpp/tool/stb_image.h b/src/cpp/tool/stb_image.h similarity index 100% rename from cpp/tool/stb_image.h rename to src/cpp/tool/stb_image.h diff --git a/cpp/tool/stb_image_write.h b/src/cpp/tool/stb_image_write.h similarity index 100% rename from cpp/tool/stb_image_write.h rename to src/cpp/tool/stb_image_write.h diff --git a/cpp/tool/tinyexr.h b/src/cpp/tool/tinyexr.h similarity index 100% rename from cpp/tool/tinyexr.h rename to src/cpp/tool/tinyexr.h diff --git a/python/flip/__init__.py b/src/flip_evaluator/__init__.py similarity index 97% rename from python/flip/__init__.py rename to src/flip_evaluator/__init__.py index 22745d2..ae18cc7 100644 --- a/python/flip/__init__.py +++ b/src/flip_evaluator/__init__.py @@ -30,4 +30,4 @@ # SPDX-License-Identifier: BSD-3-Clause ################################################################################# -from .main import evaluate, execute, load \ No newline at end of file +from .flip_python_api import evaluate, load \ No newline at end of file diff --git a/python/flip/main.py b/src/flip_evaluator/flip_python_api.py similarity index 94% rename from python/flip/main.py rename to src/flip_evaluator/flip_python_api.py index 6cdde90..e340f20 100644 --- a/python/flip/main.py +++ b/src/flip_evaluator/flip_python_api.py @@ -30,7 +30,7 @@ # SPDX-License-Identifier: BSD-3-Clause ################################################################################# -import pbflip +from flip_evaluator import pbflip import sys, os def evaluate(reference, test, dynamicRangeString, inputsRGB=True, applyMagma=True, computeMeanError=True, parameters={}): @@ -96,16 +96,8 @@ def evaluate(reference, test, dynamicRangeString, inputsRGB=True, applyMagma=Tru sys.exit(1) test = pbflip.load(test) - # Evaluate FLIP. Return error map. - return pbflip.evaluate(reference, test, useHDR, inputsRGB, computeMeanError, applyMagma, parameters) - -def execute(cmdline): - """ - Run the FLIP tool, based on the C++ tool code. - - :param cmdline: string containing the command line for the FLIP tool (run python flip.py to see all available input) - """ - pbflip.execute(cmdline) + # Evaluate FLIP. Return error map, mean FLIP error, and dictionary containing the parameters used. + return pbflip.evaluate(reference, test, useHDR, inputsRGB, applyMagma, computeMeanError, parameters) def load(imgpath): """ @@ -114,4 +106,10 @@ def load(imgpath): :param imgpath: string containing the relative or absolute path to an image, allowed file types are png, exr, bmp, and tga :return: numpy array containing the image (with HxWxC layout) """ - return pbflip.load(imgpath) \ No newline at end of file + return pbflip.load(imgpath) + +def main(): + pbflip.execute(sys.argv) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/python/flip/main.cpp b/src/nanobindFLIP.cpp similarity index 58% rename from python/flip/main.cpp rename to src/nanobindFLIP.cpp index f4df95a..d2f7d92 100644 --- a/python/flip/main.cpp +++ b/src/nanobindFLIP.cpp @@ -48,97 +48,109 @@ // Code by Pontus Ebelin (formerly Andersson) and Tomas Akenine-Moller. -#pragma once -#include -#include -#include +#include +#include +#include -#include "../../cpp/FLIP.h" -#include "../../cpp/tool/FLIPToolHelpers.h" +#include "cpp/FLIP.h" +#include "cpp/tool/FLIPToolHelpers.h" -namespace py = pybind11; +namespace nb = nanobind; + +using Array3D = nb::ndarray, nb::device::cpu>; + +Array3D createThreeChannelImage(float*& data, const size_t imageHeight, const size_t imageWidth, const size_t numChannels) +{ + // Create an ndarray from the raw data and an owner to make sure the data is deallocated when no longer used. + nb::capsule owner(data, [](void* p) noexcept { + delete[](float*) p; + }); + + // Allocate 3D array + Array3D image(data, { imageHeight, imageWidth, numChannels }, owner); + + return image; +} // Load the .exr, .png, .bmp, or .tga file with path fileName. -py::array_t load(std::string fileName) +Array3D load(std::string fileName) { FLIP::image image; - ImageHelpers::load(image, fileName); + bool imageOk = ImageHelpers::load(image, fileName); + if (!imageOk) + { + std::cout << "Error: could not read image file <" << fileName << ">. Exiting\n"; + exit(EXIT_FAILURE); + } - const int imageWidth = image.getWidth(); - const int imageHeight = image.getHeight(); + const size_t imageWidth = image.getWidth(); + const size_t imageHeight = image.getHeight(); + const size_t channels = 3; - py::array_t numpyImage = py::array_t({ imageHeight, imageWidth, 3 });; + // Allocate memory for the ndarray. + float* data = new float[imageWidth * imageHeight * channels * sizeof(float)]; - py::buffer_info numpyImage_buf = numpyImage.request(); - float* ptr_numpyImage = static_cast(numpyImage_buf.ptr); + // Copy the image data to the allocated memory. + memcpy(data, image.getHostData(), imageWidth * imageHeight * sizeof(float) * channels); - memcpy(ptr_numpyImage, image.getHostData(), size_t(image.getWidth()) * image.getHeight() * sizeof(float) * 3); + // Create an ndarray from the raw data and an owner to make sure the data is deallocated when no longer used. + Array3D numpyImage = createThreeChannelImage(data, imageHeight, imageWidth, channels); return numpyImage; } -// Convert linear RGB channel value to sRGB. -float sRGBToLinearRGB(float sC) -{ - if (sC <= 0.04045f) - { - return sC / 12.92f; - } - return powf((sC + 0.055f) / 1.055f, 2.4f); -} - // Convert linear RGB image to sRGB. -void sRGBToLinearRGB(float* image, const int imageWidth, const int imageHeight) +void sRGBToLinearRGB(float* image, const size_t imageWidth, const size_t imageHeight) { #pragma omp parallel for for (int y = 0; y < imageHeight; y++) { for (int x = 0; x < imageWidth; x++) { - int idx = (y * imageWidth + x) * 3; - image[idx] = sRGBToLinearRGB(image[idx]); - image[idx + 1] = sRGBToLinearRGB(image[idx + 1]); - image[idx + 2] = sRGBToLinearRGB(image[idx + 2]); + size_t idx = (y * imageWidth + x) * 3; + image[idx] = FLIP::color3::sRGBToLinearRGB(image[idx]); + image[idx + 1] = FLIP::color3::sRGBToLinearRGB(image[idx + 1]); + image[idx + 2] = FLIP::color3::sRGBToLinearRGB(image[idx + 2]); } } } // Set parameters for evaluate function based on input settings. -FLIP::Parameters setParameters(py::dict inputParameters) +FLIP::Parameters setParameters(nb::dict inputParameters) { FLIP::Parameters parameters; for (auto item : inputParameters) { - std::string key = py::cast(item.first); + std::string key = nb::cast(item.first); 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)."; if (key == "ppd") { - parameters.PPD = py::cast(item.second); + parameters.PPD = nb::cast(item.second); } else if (key == "vc") { - auto vc = py::cast(item.second); - float distanceToDisplay = py::cast(vc[0]); - float displayWidthPixels = py::cast(vc[1]); - float displayWidthMeters = py::cast(vc[2]); - parameters.PPD = FLIP::calculatePPD(distanceToDisplay, displayWidthPixels, displayWidthPixels); + auto vc = nb::cast(item.second); + float distanceToDisplay = nb::cast(vc[0]); + float displayWidthPixels = nb::cast(vc[1]); + float displayWidthMeters = nb::cast(vc[2]); + parameters.PPD = FLIP::calculatePPD(distanceToDisplay, displayWidthPixels, displayWidthMeters); } else if (key == "startExposure") { - parameters.startExposure = py::cast(item.second); + parameters.startExposure = nb::cast(item.second); } else if (key == "stopExposure") { - parameters.stopExposure = py::cast(item.second); + parameters.stopExposure = nb::cast(item.second); } else if (key == "numExposures") { - parameters.numExposures = py::cast(item.second); + parameters.numExposures = nb::cast(item.second); } else if (key == "tonemapper") { - parameters.tonemapper = py::cast(item.second); + parameters.tonemapper = nb::cast(item.second); } else { @@ -150,7 +162,7 @@ FLIP::Parameters setParameters(py::dict inputParameters) } // Update parameter dictionary that is returned to the Python side. -void updateInputParameters(const FLIP::Parameters& parameters, py::dict& inputParameters) +void updateInputParameters(const FLIP::Parameters& parameters, nb::dict& inputParameters) { inputParameters["ppd"] = parameters.PPD; inputParameters["startExposure"] = parameters.startExposure; @@ -162,7 +174,7 @@ void updateInputParameters(const FLIP::Parameters& parameters, py::dict& inputPa /** 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. * * @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. -* @param[in] testInput Test input image. For LDR, the content should be in [0,1]. +* @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. * @param[in] useHDR Set to true if the input images are to be considered containing HDR content, i.e., not necessarily in [0,1]. * @param[in] inputsRGB Set to true if the input images are given in the sRGB color space. * @param[in] applyMagma A boolean indicating whether the output should have the Magma map applied to it before the image is returned. @@ -170,86 +182,64 @@ void updateInputParameters(const FLIP::Parameters& parameters, py::dict& inputPa * @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). * @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. */ -std::tuple, float, py::dict> evaluate(const py::array_t referenceInput, const py::array_t testInput, const bool useHDR, const bool inputsRGB = true, const bool applyMagma = true, const bool computeMeanFLIPError = true, py::dict inputParameters = {}) -{ - py::buffer_info r_buf = referenceInput.request(), t_buf = testInput.request(); - +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 = {}) +{ + size_t r_ndim = referenceInput.ndim(); + size_t t_ndim = testInput.ndim(); + // Check number of dimensions and resolution. - if (r_buf.ndim != 3 || t_buf.ndim != 3) + if (r_ndim != 3 || t_ndim != 3) { std::stringstream message; - message << "Number of dimensions must be three. The reference image has " << r_buf.ndim << " dimensions, while the test image has "<< t_buf.ndim << " dimensions."; + message << "Number of dimensions must be three. The reference image has " << r_ndim << " dimensions, while the test image has "<< t_ndim << " dimensions."; throw std::runtime_error(message.str()); } - - if (r_buf.shape[0] != t_buf.shape[0] || r_buf.shape[1] != t_buf.shape[1]) + + if (referenceInput.shape(0) != testInput.shape(0) || referenceInput.shape(1) != testInput.shape(1)) { std::stringstream message; - message << "Reference and test image resolutions differ.\nReference image resolution: " << r_buf.shape[0] << "x" << r_buf.shape[1] << "\nTest image resolution: "<< t_buf.shape[0] << "x" << t_buf.shape[1]; + 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); throw std::runtime_error(message.str()); } - - // Arrays for reference and test. - float* ptr_r = static_cast(r_buf.ptr); - float* ptr_t = static_cast(t_buf.ptr); // Image size. - const int nRows = int(r_buf.shape[0]), nCols = int(r_buf.shape[1]), nChannels = int(r_buf.shape[2]); + const size_t imageHeight = referenceInput.shape(0), imageWidth = referenceInput.shape(1); + const size_t nChannelsOut = applyMagma ? 3 : 1; // FLIP - float* flip; + float* flip = nullptr; // Allocated in FLIP::evaluate(). - // Create NumPy output array. - py::array_t flipNumpy; - int nChannelsOut; - if (applyMagma) - { - flipNumpy = py::array_t({ r_buf.shape[0], r_buf.shape[1], r_buf.shape[2] }); - nChannelsOut = 3; - } - else - { - flipNumpy = py::array_t({ r_buf.shape[0], r_buf.shape[1] }); - nChannelsOut = 1; - } - py::buffer_info flipNumpy_buf = flipNumpy.request(); - float* ptr_flipNumpy = static_cast(flipNumpy_buf.ptr); + // Arrays for reference and test. + float* referenceImage = new float[imageHeight * imageWidth * 3 * sizeof(float)]; + float* testImage = new float[imageHeight * imageWidth * 3 * sizeof(float)]; + memcpy(referenceImage, referenceInput.data(), imageHeight * imageWidth * sizeof(float) * 3); + memcpy(testImage, testInput.data(), imageHeight * imageWidth * sizeof(float) * 3); // Transform to linear RGB if desired. if (inputsRGB) { - sRGBToLinearRGB(ptr_r, nCols, nRows); - sRGBToLinearRGB(ptr_t, nCols, nRows); + sRGBToLinearRGB(referenceImage, imageWidth, imageHeight); + sRGBToLinearRGB(testImage, imageWidth, imageHeight); } // Run FLIP. FLIP::Parameters parameters = setParameters(inputParameters); float meanError = -1; - FLIP::evaluate(ptr_r, ptr_t, nCols, nRows, useHDR, parameters, applyMagma, computeMeanFLIPError, meanError, &flip); - - // Move output array info to correct buffer. -#pragma omp parallel for - for (int i = 0; i < nRows; i++) - { - for (int j = 0; j < nCols; j++) - { - for (int c = 0; c < nChannelsOut; c++) - { - int idx = (i * nCols + j) * nChannelsOut + c; - ptr_flipNumpy[idx] = flip[idx]; - } - } - } - delete flip; + FLIP::evaluate(referenceImage, testImage, int(imageWidth), int(imageHeight), useHDR, parameters, applyMagma, computeMeanFLIPError, meanError, &flip); - py::dict returnParams = {}; + nb::dict returnParams = {}; updateInputParameters(parameters, returnParams); - return std::make_tuple(flipNumpy, meanError, returnParams); + Array3D flipNumpy = createThreeChannelImage(flip, imageHeight, imageWidth, nChannelsOut); + + delete [] referenceImage; + delete [] testImage; + + return nb::make_tuple(flipNumpy, meanError, returnParams); } // Create command line, based on the Python command line string, for the FLIP tool to parse. -commandline generateCommandLine(const py::list argvPy) +commandline generateCommandLine(const nb::list argvPy) { size_t argc = argvPy.size(); char** argv = new char* [argc]; @@ -257,9 +247,8 @@ commandline generateCommandLine(const py::list argvPy) int counter = 0; for (auto item : argvPy) { - const std::string it = py::reinterpret_steal(item); - argv[counter] = new char[it.length()]; - std::strcpy(argv[counter], it.c_str()); + const std::string it = nb::steal(item).c_str(); + argv[counter] = strdup(it.c_str()); counter++; } @@ -275,19 +264,19 @@ commandline generateCommandLine(const py::list argvPy) } // Run the FLIP tool based on Python command line string. -int execute(const py::list argvPy) +int execute(const nb::list argvPy) { commandline commandLine = generateCommandLine(argvPy); FLIPTool::execute(commandLine); - return 0; + return EXIT_SUCCESS; } // Setup the pybind11 module. -PYBIND11_MODULE(pbflip, handle) +NB_MODULE(pbflip, handle) { - handle.doc() = "Load images (load), evaluate FLIP (evaluate), or run the full FLIP tool (run_tool)."; - handle.def("evaluate", &evaluate); + handle.doc() = "Load images (load), evaluate FLIP (evaluate), or run the full FLIP tool (execute)."; handle.def("load", &load); + handle.def("evaluate", &evaluate); handle.def("execute", &execute); } \ No newline at end of file diff --git a/src/python/README.md b/src/python/README.md new file mode 100644 index 0000000..cad5b12 --- /dev/null +++ b/src/python/README.md @@ -0,0 +1,117 @@ +# FLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.6) + +By +[Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin) +and +[Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), +with +Jim Nilsson, +[Magnus Oskarsson](https://www1.maths.lth.se/matematiklth/personal/magnuso/), +[Kalle Åström](https://www.maths.lu.se/staff/kalleastrom/), +[Mark D. Fairchild](https://www.rit.edu/directory/mdfpph-mark-fairchild), +and +[Peter Shirley](https://research.nvidia.com/person/peter-shirley). + +This [repository](https://github.com/NVlabs/flip) implements the [LDR-FLIP](https://research.nvidia.com/publication/2020-07_FLIP) +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). +Similarly, it implements the FLIP tool, presented in [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html). + +# License + +Copyright © 2020-2024, NVIDIA Corporation & Affiliates. All rights reserved. + +This work is made available under a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/LICENSE). + +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),
+and `stb_image`, which is subject to an [MIT License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#mit-license). + +For individual contributions to the project, please confer the [Individual Contributor License Agreement](https://github.com/NVlabs/flip/blob/main/misc/CLA.md). + +For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). + +# Python (API and Tool) +- **Setup** (with pip): + ``` + pip install flip-evaluator + ``` +- Usage (API): See example in the script `src/python/api_example.py`. +- Usage (tool): `flip --reference reference.{exr|png} --test test.{exr|png} [--options]`, where the list of options can be seen by `flip -h`. +- Tested with pip 24.0, Python 3.11.8, pybind11 2.11.1, and C++20. +- 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. +- 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`. + `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. +- 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. +- The naming convention used for the FLIP tool's output is as follows (where `ppd` is the assumed number of pixels per degree, + `tm` is the tone mapper assumed by HDR-FLIP, `cstart` and `cstop` are the shortest and longest exposures, respectively, assumed by HDR-FLIP, + with `p` indicating a positive value and `m` indicating a negative value, + `N` is the number of exposures used in the HDR-FLIP calculation, `nnn` is a counter used to sort the intermediate results, + and `exp` is the exposure used for the intermediate LDR image / FLIP map): + + **Default:** + + *Low dynamic range images:*
+ + LDR-FLIP: `flip...ppd.ldr.png`
+ Weighted histogram: `weighted_histogram.reference>..ppd.ldr.py`
+ Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.ldr.py`
+ Text file: `pooled_values...ppd.ldr.txt`
+ + *High dynamic range images:*
+ + HDR-FLIP: `flip...ppd.hdr.._to_..png`
+ Exposure map: `exposure_map...ppd.hdr.._to_..png`
+ Intermediate LDR-FLIP maps: `flip...ppd.ldr....png`
+ Intermediate LDR images: `....png`
+ Weighted histogram: `weighted_histogram...ppd.hdr.._to_..py`
+ Overlapping weighted histogram: `overlapping_weighted_histogram....ppd.hdr.._to_..py`
+ Text file: `pooled_values...ppd.hdr.._to_..txt`
+ + **With** `--basename ` **(note: not applicable if more than one test image is evaluated):** + + *Low dynamic range images:*
+ + LDR-FLIP: `.png`
+ Weighted histogram: `.py`
+ Overlapping weighted histogram: N/A
+ Text file: `.txt`
+ + *High dynamic range images:*
+ + HDR-FLIP: `.png`
+ Exposure map: `.exposure_map.png`
+ Intermediate LDR-FLIP maps: `..png`
+ Intermediate LDR images: `.reference|test..png`
+ Weighted histogram: `.py`
+ Overlapping weighted histogram: N/A
+ Text file: `.txt`
+ +**Example usage:** +To test the API, please inspect the `src/python/api_example.py` script. This shows how the available API commands may be used. +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). + +To test the tool, start a shell, navigate to `images/` and try: + ``` + flip -r reference.exr -t test.exr + ``` +The result should be: + ``` +Invoking HDR-FLIP + Pixels per degree: 67 + Assumed tone mapper: ACES + Start exposure: -12.5423 + Stop exposure: 0.9427 + Number of exposures: 14 + +FLIP between reference image and test image : + Mean: 0.283478 + Weighted median: 0.339430 + 1st weighted quartile: 0.251122 + 3rd weighted quartile: 0.434673 + Min: 0.003123 + Max: 0.962022 + Evaluation time: seconds + FLIP error map location: + FLIP exposure map location: + ``` +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` +in the working directory, and we urge you to inspect those, which will reveal where the errors in the test image are located. diff --git a/python/api_example.py b/src/python/api_example.py similarity index 99% rename from python/api_example.py rename to src/python/api_example.py index d0848ec..37872ca 100644 --- a/python/api_example.py +++ b/src/python/api_example.py @@ -30,7 +30,7 @@ # SPDX-License-Identifier: BSD-3-Clause ################################################################################# -import flip +import flip_evaluator as flip import matplotlib.pyplot as plt if __name__ == '__main__': diff --git a/pytorch/README.md b/src/pytorch/README.md similarity index 64% rename from pytorch/README.md rename to src/pytorch/README.md index a1eec2e..5de1ec7 100644 --- a/pytorch/README.md +++ b/src/pytorch/README.md @@ -1,7 +1,7 @@ -# ꟻLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.4) +# FLIP: A Tool for Visualizing and Communicating Errors in Rendered Images (v1.6) By -[Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin), +[Pontus Ebelin](https://research.nvidia.com/person/pontus-ebelin) and [Tomas Akenine-Möller](https://research.nvidia.com/person/tomas-akenine-m%C3%B6ller), with @@ -12,19 +12,19 @@ Jim Nilsson, and [Peter Shirley](https://research.nvidia.com/person/peter-shirley). -This [repository](https://github.com/NVlabs/flip) holds implementations of the [LDR-ꟻLIP](https://research.nvidia.com/publication/2020-07_FLIP) -and [HDR-ꟻLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics as loss modules in PyTorch. +This [repository](https://github.com/NVlabs/flip) holds implementations of the [LDR-FLIP](https://research.nvidia.com/publication/2020-07_FLIP) +and [HDR-FLIP](https://research.nvidia.com/publication/2021-05_HDR-FLIP) image error metrics as loss modules in PyTorch. # License Copyright © 2020-2024, NVIDIA Corporation & Affiliates. All rights reserved. -This work is made available under a [BSD 3-Clause License](../misc/LICENSE.md). +This work is made available under a [BSD 3-Clause License](https://github.com/NVlabs/flip/blob/main/LICENSE). -The repository distributes code for `tinyexr`, which is subject to a [BSD 3-Clause License](../misc/LICENSE-third-party.md#bsd-3-clause-license),
-and `stb_image`, which is subject to an [MIT License](../misc/LICENSE-third-party.md#mit-license). +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),
+and `stb_image`, which is subject to an [MIT License](https://github.com/NVlabs/flip/blob/main/misc/LICENSE-third-party.md#mit-license). -For individual contributions to the project, please confer the [Individual Contributor License Agreement](../misc/CLA.md). +For individual contributions to the project, please confer the [Individual Contributor License Agreement](https://github.com/NVlabs/flip/blob/main/misc/CLA.md). For business inquiries, please visit our website and submit the form: [NVIDIA Research Licensing](https://www.nvidia.com/en-us/research/inquiries/). @@ -38,25 +38,25 @@ For business inquiries, please visit our website and submit the form: [NVIDIA Re conda install -c conda-forge openexr-python ``` - *Remember to activate the* `flip_dl` *environment through* `conda activate flip_dl` *before using the loss function.* -- LDR- and HDR-ꟻLIP are implemented as loss modules in `flip_loss.py`. - An example where the loss function is used to train a simple autoencoder is provided in `train.py`. +- LDR- and HDR-FLIP are implemented as loss modules in `flip_evaluator/pytorch/flip_loss.py`. + An example where the loss function is used to train a simple autoencoder is provided in `flip_evaluator/pytorch/train.py`. - 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. - Per default, the loss function returns the mean of the error maps. To return the full error maps, remove `torch.mean()` from the `forward()` function. -- For LDR-ꟻLIP, the images are assumed to be in sRGB space +- For LDR-FLIP, the images are assumed to be in sRGB space (change the color space transform in `LDRFLIPLoss`'s `forward()` function to `linrgb2ycxcz` if your network's output is in linear RGB), in the [0,1] range. -- Both LDR- and HDR-ꟻLIP takes an optional argument describing the assumed number of pixels per +- Both LDR- and HDR-FLIP takes an optional argument describing the assumed number of pixels per degree of the observer. Per default, it is assume that the images are viewed at a distance 0.7 m from a 0.7 m wide 4K monitor. - The `HDRFLIPLoss` can take three additional, optional arguments: `tone_mapper`, `start_exposure`, and `stop_exposure`. - `tone_mapper` is a string describing the tone mapper that HDR-ꟻLIP 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. + `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. `start_exposure`, and `stop_exposure` should have `Nx1x1x1` layout and hold the start and stop exposures, respectively, used for each of the `N` reference/test pairs in the batch. Per default, `HDRFLIPLoss` computes start and stop exposures as described in the [paper](https://d1qx31qr3h6wln.cloudfront.net/publications/HDRFLIP-paper.pdf). - **NOTE:** When start and/or stop exposures are not provided, HDR-ꟻLIP is not symmetric. The + **NOTE:** When start and/or stop exposures are not provided, HDR-FLIP is not symmetric. The user should therefore make sure to input the test images as the *first* argument and the reference image as the *second* argument to the `HDRFLIPLoss`'s `forward()` function. -- `../tests/test_pytorch.py` contains simple tests used to test whether code updates alter results and - `data.py` contains image loading/saving functions. +- `flip_evaluator/tests/test_pytorch.py` contains simple tests used to test whether code updates alter results and + `flip_evaluator/pytorch/data.py` contains image loading/saving functions. diff --git a/pytorch/data.py b/src/pytorch/data.py similarity index 100% rename from pytorch/data.py rename to src/pytorch/data.py diff --git a/pytorch/flip_loss.py b/src/pytorch/flip_loss.py similarity index 100% rename from pytorch/flip_loss.py rename to src/pytorch/flip_loss.py diff --git a/pytorch/train.py b/src/pytorch/train.py similarity index 100% rename from pytorch/train.py rename to src/pytorch/train.py diff --git a/tests/correct_hdrflip_cpp.png b/src/tests/correct_hdrflip_cpp.png similarity index 100% rename from tests/correct_hdrflip_cpp.png rename to src/tests/correct_hdrflip_cpp.png diff --git a/tests/correct_hdrflip_cuda.png b/src/tests/correct_hdrflip_cuda.png similarity index 100% rename from tests/correct_hdrflip_cuda.png rename to src/tests/correct_hdrflip_cuda.png diff --git a/tests/correct_ldrflip_cpp.png b/src/tests/correct_ldrflip_cpp.png similarity index 100% rename from tests/correct_ldrflip_cpp.png rename to src/tests/correct_ldrflip_cpp.png diff --git a/tests/correct_ldrflip_cuda.png b/src/tests/correct_ldrflip_cuda.png similarity index 100% rename from tests/correct_ldrflip_cuda.png rename to src/tests/correct_ldrflip_cuda.png diff --git a/tests/test.py b/src/tests/test.py similarity index 72% rename from tests/test.py rename to src/tests/test.py index bb7f092..4b9af4e 100644 --- a/tests/test.py +++ b/src/tests/test.py @@ -51,7 +51,9 @@ import subprocess import os import sys -import flip +import matplotlib.pyplot as plt +import numpy as np +import flip_evaluator as flip if __name__ == '__main__': """ @@ -61,7 +63,7 @@ if(len(sys.argv) != 2): print("Usage: python test.py --{cuda|cpp|python}") - print("Tip: do not forget to install the FLIP Python API through `pip install .` in `flip/python`.") + print("Tip: do not forget to install the FLIP Python API through `pip install flip_evaluator`.") sys.exit() # Parse command line argument. @@ -69,26 +71,18 @@ test_str = "CUDA" correct_ldr_image_filename = "correct_ldrflip_cuda.png" correct_hdr_image_filename = "correct_hdrflip_cuda.png" - ldr_cmd = "../cpp/x64/release/flip-cuda.exe --reference ../images/reference.png --test ../images/test.png" - hdr_cmd = "../cpp/x64/release/flip-cuda.exe --reference ../images/reference.exr --test ../images/test.exr --no-exposure-map" - expected_ldr_mean = 0.159691 - expected_hdr_mean = 0.283478 + ldr_cmd = "../cpp/x64/release/flip-cuda.exe --reference ../../images/reference.png --test ../../images/test.png" + hdr_cmd = "../cpp/x64/release/flip-cuda.exe --reference ../../images/reference.exr --test ../../images/test.exr --no-exposure-map" elif(sys.argv[1] == "--cpp" or sys.argv[1] == "cpp" or sys.argv[1] == "-cpp"): test_str = "CPP" correct_ldr_image_filename = "correct_ldrflip_cpp.png" correct_hdr_image_filename = "correct_hdrflip_cpp.png" - ldr_cmd = "../cpp/x64/release/flip.exe --reference ../images/reference.png --test ../images/test.png" - hdr_cmd = "../cpp/x64/release/flip.exe --reference ../images/reference.exr --test ../images/test.exr --no-exposure-map" - expected_ldr_mean = 0.159691 - expected_hdr_mean = 0.283478 + ldr_cmd = "../cpp/x64/release/flip.exe --reference ../../images/reference.png --test ../../images/test.png" + hdr_cmd = "../cpp/x64/release/flip.exe --reference ../../images/reference.exr --test ../../images/test.exr --no-exposure-map" elif(sys.argv[1] == "--python" or sys.argv[1] == "python" or sys.argv[1] == "-python"): test_str = "PYTHON" correct_ldr_image_filename = "correct_ldrflip_cpp.png" # Python and C++ should give the same results, correct_hdr_image_filename = "correct_hdrflip_cpp.png" # as the Python code runs the C++ code via pybind11. - ldr_cmd = "python ../python/flip.py --reference ../images/reference.png --test ../images/test.png" - hdr_cmd = "python ../python/flip.py --reference ../images/reference.exr --test ../images/test.exr --no-exposure-map" - expected_ldr_mean = 0.159691 - expected_hdr_mean = 0.283478 else: print("Error: the argument should be one of --cuda, --cpp, and --python.") sys.exit() @@ -100,21 +94,38 @@ ldr_correct_result = flip.load(correct_ldr_image_filename) # LDR, sRGB hdr_correct_result = flip.load(correct_hdr_image_filename) # HDR - # Run FLIP on the reference/test image pairs in the ../images directory. - ldr_process = subprocess.run(ldr_cmd, stdout=subprocess.PIPE, text=True) - hdr_process = subprocess.run(hdr_cmd, stdout=subprocess.PIPE, text=True) + # Result file names + result_ldr_file = "flip.reference.test.67ppd.ldr.png" + result_hdr_file = "flip.reference.test.67ppd.hdr.aces.m12.5423_to_p0.9427.14.png" - ldr_result_strings = ldr_process.stdout.split('\n') - subpos = ldr_result_strings[4].find(':') - ldr_mean = float(ldr_result_strings[4][subpos + 2 : len(ldr_result_strings[4])]) + # Expected means + expected_ldr_mean = 0.159691 + expected_hdr_mean = 0.283478 - hdr_result_strings = hdr_process.stdout.split('\n') - subpos = hdr_result_strings[8].find(':') - hdr_mean = float(hdr_result_strings[8][subpos + 2 : len(hdr_result_strings[4])]) + if test_str == "CUDA" or test_str == "CPP": + # Run FLIP on the reference/test image pairs in the ../../images directory. + ldr_process = subprocess.run(ldr_cmd, stdout=subprocess.PIPE, text=True) + hdr_process = subprocess.run(hdr_cmd, stdout=subprocess.PIPE, text=True) - # Load the images that were just created. - result_ldr_file = "flip.reference.test.67ppd.ldr.png" - result_hdr_file = "flip.reference.test.67ppd.hdr.aces.m12.5423_to_p0.9427.14.png" + ldr_result_strings = ldr_process.stdout.split('\n') + subpos = ldr_result_strings[4].find(':') + ldr_mean = float(ldr_result_strings[4][subpos + 2 : len(ldr_result_strings[4])]) + + hdr_result_strings = hdr_process.stdout.split('\n') + subpos = hdr_result_strings[8].find(':') + hdr_mean = float(hdr_result_strings[8][subpos + 2 : len(hdr_result_strings[8])]) + else: + # Run FLIP on the reference/test image pairs in the ../../images directory. + ldr_new_result, ldr_mean, _ = flip.evaluate("../../images/reference.png", "../../images/test.png", "LDR") + hdr_new_result, hdr_mean, _ = flip.evaluate("../../images/reference.exr", "../../images/test.exr", "HDR") + + # Round to match the FLIP tool's output rounding + ldr_mean = round(ldr_mean, 6) + hdr_mean = round(hdr_mean, 6) + plt.imsave(result_ldr_file, np.uint8(255 * ldr_new_result + 0.5), vmin=0, vmax=255) # Same rounding as is used to save + 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). + + # Load the images that were just created ldr_new_result = flip.load(result_ldr_file) # LDR, sRGB hdr_new_result = flip.load(result_hdr_file) # HDR diff --git a/tests/test_pytorch.py b/src/tests/test_pytorch.py similarity index 94% rename from tests/test_pytorch.py rename to src/tests/test_pytorch.py index 8558380..d874c6a 100644 --- a/tests/test_pytorch.py +++ b/src/tests/test_pytorch.py @@ -73,16 +73,16 @@ # HDR test # Run the image pairs in the images directory - hdr_reference = read_exr('../images/reference.exr') # EXR - hdr_test = read_exr('../images/test.exr') # EXR + hdr_reference = read_exr('../../images/reference.exr') # EXR + hdr_test = read_exr('../../images/test.exr') # EXR hdrflip_loss_fn = HDRFLIPLoss() hdrflip_loss = hdrflip_loss_fn(hdr_test, hdr_reference) test_results.append(round(hdrflip_loss.item(), 4) == 0.2835) # LDR test # Run the image pairs in the images directory - ldr_reference = load_image_tensor('../images/reference.png') # sRGB - ldr_test = load_image_tensor('../images/test.png') # sRGB + ldr_reference = load_image_tensor('../../images/reference.png') # sRGB + ldr_test = load_image_tensor('../../images/test.png') # sRGB ldrflip_loss_fn = LDRFLIPLoss() ldrflip_loss = ldrflip_loss_fn(ldr_test, ldr_reference) test_results.append(round(ldrflip_loss.item(), 4) == 0.1597)