├── .clang-format ├── .github └── workflows │ └── cmake.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── app ├── CMakeLists.txt ├── export-reradiation │ ├── CMakeLists.txt │ └── main.cpp ├── export-spectrum │ ├── CMakeLists.txt │ └── main.cpp ├── fluo-exr │ ├── CMakeLists.txt │ ├── fluo_data.h │ └── main.cpp ├── macbeth │ ├── CMakeLists.txt │ ├── macbeth_data.h │ └── main.cpp ├── merge-exr │ ├── CMakeLists.txt │ └── main.cpp ├── spectrum-to-exr │ ├── CMakeLists.txt │ └── main.cpp └── split-channels │ ├── CMakeLists.txt │ └── main.cpp ├── format-all.sh ├── lib ├── BiSpectralImage.cpp ├── CMakeLists.txt ├── EXRBiSpectralImage.cpp ├── EXRSpectralImage.cpp ├── EXRSpectralImage.pc.in ├── SpectralImage.cpp ├── SpectrumAttribute.cpp ├── SpectrumConverter.cpp ├── SpectrumConverter.h ├── Util.h ├── cmake │ └── FindOpenEXR.cmake ├── include │ ├── BiSpectralImage.h │ ├── EXRBiSpectralImage.h │ ├── EXRSpectralImage.h │ ├── SpectralImage.h │ ├── SpectrumAttribute.h │ └── SpectrumType.h └── spectrum_data.h └── python ├── Read Spectral EXR.ipynb ├── data ├── cmf │ └── ciexyz31.csv └── image │ ├── D65.exr │ ├── Macbeth.exr │ └── PolarisedRendering.exr └── radiometry └── cmf.py /.clang-format: -------------------------------------------------------------------------------- 1 | Language: Cpp 2 | AlignConsecutiveMacros: true 3 | AlignConsecutiveAssignments: true 4 | AlignConsecutiveDeclarations: true 5 | AlignEscapedNewlines: Right 6 | AlignOperands: true 7 | AlignTrailingComments: true 8 | AllowAllArgumentsOnNextLine: false 9 | AllowAllConstructorInitializersOnNextLine: false 10 | AllowShortFunctionsOnASingleLine: Inline 11 | AllowShortIfStatementsOnASingleLine: WithoutElse 12 | AllowShortLambdasOnASingleLine: None 13 | AllowShortLoopsOnASingleLine: false 14 | AlwaysBreakAfterReturnType: None 15 | AlwaysBreakBeforeMultilineStrings: true 16 | AlwaysBreakTemplateDeclarations: Yes 17 | 18 | AllowAllParametersOfDeclarationOnNextLine: true 19 | BinPackArguments: false 20 | BinPackParameters: false 21 | AlignAfterOpenBracket: AlwaysBreak 22 | 23 | BreakBeforeBraces: Custom 24 | BraceWrapping: 25 | AfterClass: true 26 | AfterCaseLabel: false 27 | AfterControlStatement: false 28 | AfterEnum: true 29 | AfterFunction: true 30 | AfterNamespace: true 31 | AfterStruct: false 32 | AfterUnion: true 33 | AfterExternBlock: true 34 | BeforeCatch: false 35 | BeforeElse: false 36 | IndentBraces: false 37 | SplitEmptyFunction: false 38 | SplitEmptyRecord: false 39 | SplitEmptyNamespace: false 40 | 41 | BreakBeforeBinaryOperators: All 42 | BreakBeforeTernaryOperators: true 43 | BreakConstructorInitializers: BeforeComma 44 | BreakInheritanceList: BeforeComma 45 | BreakStringLiterals: true 46 | ColumnLimit: 80 47 | CompactNamespaces: false 48 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 49 | ConstructorInitializerIndentWidth: 2 50 | ContinuationIndentWidth: 2 51 | Cpp11BracedListStyle: true 52 | DerivePointerAlignment: false 53 | FixNamespaceComments: true 54 | ForEachMacros: 55 | - foreach 56 | - Q_FOREACH 57 | - BOOST_FOREACH 58 | IncludeBlocks: Preserve 59 | IncludeCategories: 60 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 61 | Priority: 2 62 | SortPriority: 0 63 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 64 | Priority: 3 65 | SortPriority: 0 66 | - Regex: '.*' 67 | Priority: 1 68 | SortPriority: 0 69 | IncludeBlocks: Regroup 70 | IndentCaseLabels: true 71 | IndentPPDirectives: AfterHash 72 | IndentWidth: 4 73 | IndentWrappedFunctionNames: false 74 | KeepEmptyLinesAtTheStartOfBlocks: false 75 | MaxEmptyLinesToKeep: 4 76 | NamespaceIndentation: All 77 | PointerAlignment: Right 78 | 79 | ReflowComments: true 80 | CommentPragmas: '.*' 81 | 82 | SortIncludes: false 83 | SortUsingDeclarations: true 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterLogicalNot: false 86 | SpaceAfterTemplateKeyword: false 87 | SpaceBeforeAssignmentOperators: true 88 | SpaceBeforeCpp11BracedList: true 89 | SpaceBeforeCtorInitializerColon: false 90 | SpaceBeforeInheritanceColon: false 91 | SpaceBeforeParens: ControlStatements 92 | SpaceBeforeRangeBasedForLoopColon: true 93 | SpaceInEmptyParentheses: false 94 | SpacesBeforeTrailingComments: 3 95 | SpacesInAngles: false 96 | SpacesInCStyleCastParentheses: false 97 | SpacesInContainerLiterals: false 98 | SpacesInParentheses: false 99 | SpacesInSquareBrackets: false 100 | #SpaceBeforeSquareBrackets: false 101 | Standard: Cpp11 102 | StatementMacros: 103 | - Q_UNUSED 104 | - QT_REQUIRE_VERSION 105 | TabWidth: 2 106 | UseTab: Never 107 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: [push] 4 | 5 | env: 6 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 7 | BUILD_TYPE: Release 8 | 9 | jobs: 10 | build: 11 | # The CMake configure and build commands are platform agnostic and should work equally 12 | # well on Windows or Mac. You can convert this to a matrix build if you need 13 | # cross-platform coverage. 14 | # See: https://docs.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow#configuring-a-build-matrix 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Install OpenEXR dependencies 21 | run: sudo apt install libilmbase-dev libopenexr-dev 22 | 23 | - name: Create Build Environment 24 | # Some projects don't allow in-source building, so create a separate build directory 25 | # We'll use this as our working directory for all subsequent commands 26 | run: cmake -E make_directory ${{runner.workspace}}/build 27 | 28 | - name: Configure CMake 29 | # Use a bash shell so we can use the same syntax for environment variable 30 | # access regardless of the host operating system 31 | shell: bash 32 | working-directory: ${{runner.workspace}}/build 33 | # Note the current convention is to use the -S and -B options here to specify source 34 | # and build directories, but this is only available with CMake 3.13 and higher. 35 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 36 | run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 37 | 38 | - name: Build 39 | working-directory: ${{runner.workspace}}/build 40 | shell: bash 41 | # Execute the build. You can specify a specific target with "--target " 42 | run: cmake --build . --config $BUILD_TYPE 43 | 44 | - name: Test 45 | working-directory: ${{runner.workspace}}/build 46 | shell: bash 47 | # Execute tests defined by the CMake configuration. 48 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 49 | run: ctest -C $BUILD_TYPE 50 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.1) 2 | project(SpectralImage CXX) 3 | 4 | include(GNUInstallDirs) 5 | 6 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 7 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 9 | 10 | set(CMAKE_CXX_STANDARD 11) 11 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 12 | 13 | add_subdirectory(lib) 14 | add_subdirectory(app) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2021, Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenEXR Spectral Image 2 | 3 | This repository contains the supporting code to read and write OpenEXR spectral images. 4 | 5 | Our article [*An OpenEXR Layout for Spectral Images*](https://jcgt.org/published/0010/03/01/) published in the [Journal of Computer Graphics Techniques](http://jcgt.org) provides a description of the layout. 6 | 7 | You may also find sample spectral images useful: [ZIP Archive](https://jcgt.org/published/0010/03/01/sample-images.zip). 8 | 9 | To load OpenEXR spectral image, you can use [Spectral Viewer](https://mrf-devteam.gitlab.io/spectral-viewer/). 10 | 11 | ## Content 12 | 13 | - `lib` contains the main C++ code. 14 | - `app` contains sample C++ applications using the provided C++ library. 15 | - `python` contains a Python example to load spectral OpenEXR files using [OpenImageIO](https://openimageio.org/). 16 | 17 | ## Compilation 18 | 19 | To compile this code, you need a C++11 compliant compiler, the OpenEXR 20 | library installed on your system and CMake. 21 | 22 | ```bash 23 | mkdir build 24 | cd build 25 | cmake .. 26 | make 27 | ``` 28 | 29 | This will compile an example program. 30 | 31 | # Sample programs 32 | 33 | You can find in `app` several sample programs using spectral OpenEXR. 34 | 35 | ## Macbeth 36 | 37 | Macbeth executable (`macbeth`) generates a Macbeth colour chart using 38 | the spectral data from: 39 | http://www.babelcolor.com/colorchecker-2.htm. It will generate a file 40 | `Macbeth.exr` from the execution place. 41 | 42 | ```bash 43 | ./bin/macbeth 44 | ``` 45 | 46 | ## Fluo EXR 47 | 48 | Fluo EXR executable (`fluo-exr`) generates a simple checker-board made 49 | of two fluorescent patches: 3M fluorescent yellow Post-It (R) sticker 50 | and 3M fluorescent pink Post-It (R) sticker. Data courtesy of 51 | Labsphere Inc. It will generate a file `BiSpectral.exr` 52 | 53 | ```bash 54 | ./bin/fluo-exr 55 | ``` 56 | 57 | ## Export spectrum 58 | 59 | Export spectrum executable (`export-spectrum`) extracts from the given 60 | pixel location the stored spectrum in an ASCII file. Each columns 61 | correspond to a polarisation component if present in the image. `S_0`, 62 | `S_1`, `S_2`, `S_3` for emissive images and `T` for reflective 63 | images. First column is the wavelength in manometers. Each column is 64 | separated by a space. 65 | 66 | It takes as arguments: 67 | - A spectral EXR 68 | - The x coordinate of the pixel to extract 69 | - The y coordinate of the pixel to extract 70 | - The output file 71 | 72 | ```bash 73 | ./bin/export-spectrum Macbeth.exr 15 15 Macbeth.txt 74 | ``` 75 | 76 | You can plot the spectrum using `gnuplot` 77 | ```gnuplot 78 | plot "Macbeth.txt" u 1:2 w line 79 | ``` 80 | 81 | ## Export reradiation 82 | 83 | Export reradiation executable (`export-reradiation`) extracts from the 84 | give pixel location the stored reradiation matrix in a ASCII file. 85 | 86 | It takes as arguments: 87 | - A bi-spectral EXR 88 | - The x coordinate of the pixel to extract 89 | - The y coordinate of the pixel to extract 90 | - The output file 91 | 92 | ```bash 93 | ./bin/export-spectrum BiSpectral.exr 15 15 reradiation.txt 94 | ``` 95 | 96 | You can plot the reradiation matrix using `gnuplot` 97 | ```gnuplot 98 | plot "reradiation.txt" matrix w image 99 | ``` 100 | 101 | ## Merge EXR 102 | 103 | Merge EXR executable (`merge-exr`) creates an emissive spectral EXR 104 | from a folder containing collection of monochromatic EXRs. You can use 105 | the Cornell box data as input 106 | http://www.graphics.cornell.edu/online/box/data.html. 107 | 108 | It takes as arguments: 109 | - Folder path containing the images 110 | - Starting wavelength (in manometers) 111 | - Increment of wavelength between images (in manometers) 112 | - Output file 113 | - Optional additional arguments: 114 | - Camera response in CSV format (comma separated) 115 | - Lens transmission in CSV format (comma separated) 116 | - Each filter corresponding to each channel transmissions in CSV format (comma separated) 117 | 118 | To convert the Matlab matrices of the Cornell data in CSV format, we 119 | use the following GNU Octave code: 120 | 121 | ```Matlab load filters.mat 122 | 123 | csvwrite("F1.csv", [wavelen F1]) 124 | csvwrite("F2.csv", [wavelen F2]) 125 | csvwrite("F3.csv", [wavelen F3]) 126 | csvwrite("F4.csv", [wavelen F4]) 127 | csvwrite("F5.csv", [wavelen F5]) 128 | csvwrite("F6.csv", [wavelen F6]) 129 | csvwrite("F7.csv", [wavelen F7]) 130 | 131 | load lens.mat 132 | csvwrite("lens.csv", [wavelen lens]) 133 | 134 | load camera.mat 135 | csvwrite("camera.csv", [wavelen_cam.' response.']) 136 | ``` 137 | 138 | Then, we execute the program as follows: 139 | 140 | ```bash 141 | ./bin/merge-exr \ 142 | data/cornell 400 50 output/CornellBox.exr \ 143 | data/cornell/camera.csv \ 144 | data/cornell/lens.csv \ 145 | data/cornell/F1.csv \ 146 | data/cornell/F2.csv \ 147 | data/cornell/F3.csv \ 148 | data/cornell/F4.csv \ 149 | data/cornell/F5.csv \ 150 | data/cornell/F6.csv \ 151 | data/cornell/F7.csv 152 | ``` 153 | 154 | ## Spectrum to EXR 155 | 156 | Spectrum to EXR executable (`spectrum-to-exr`) creates a 1x1px 157 | spectral OpenEXR from a given spectrum. 158 | 159 | It takes as arguments: 160 | - A spectrum in CSV format (comma separated) 161 | - Type of spectrum (either `reflective` or `emissive`) 162 | - Output file 163 | 164 | For example: 165 | ```bash 166 | ./bin/spectrum-to-exr data/D65.csv emissive output/D65.exr 167 | ``` 168 | 169 | ## Split channels 170 | 171 | Split channels executable (`split-channels`) splits a spectral EXR is 172 | separate single monochromatic EXR files. 173 | 174 | It takes as arguments: 175 | - A spectral EXR file 176 | - An output folder 177 | 178 | For example: 179 | ```bash 180 | ./bin/split-channels Macbeth.exr output 181 | ``` 182 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.1) 2 | project(SpectralImage) 3 | 4 | add_subdirectory(macbeth) 5 | add_subdirectory(fluo-exr) 6 | 7 | add_subdirectory(spectrum-to-exr) 8 | 9 | add_subdirectory(split-channels) 10 | #add_subdirectory(merge-exr) 11 | add_subdirectory(export-spectrum) 12 | add_subdirectory(export-reradiation) 13 | -------------------------------------------------------------------------------- /app/export-reradiation/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(export-reradiation main.cpp) 2 | 3 | target_link_libraries(export-reradiation PUBLIC EXRSpectralImage) 4 | 5 | install(TARGETS export-reradiation RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -------------------------------------------------------------------------------- /app/export-reradiation/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | using namespace SEXR; 43 | 44 | int main(int argc, char *argv[]) 45 | { 46 | if (argc < 5) { 47 | std::cout << "Usage:" << std::endl 48 | << "------" << std::endl 49 | << argv[0] 50 | << " [] " 51 | << std::endl 52 | << std::endl; 53 | 54 | return 0; 55 | } 56 | 57 | const bool matrixMode = argc == 5; 58 | 59 | const EXRBiSpectralImage image(argv[1]); 60 | const size_t x = std::stoi(argv[2]); 61 | const size_t y = std::stoi(argv[3]); 62 | const size_t wl_idx = (matrixMode) ? 0 : std::stoi(argv[4]); 63 | std::ofstream tabularOut(argv[matrixMode ? 4 : 5]); 64 | 65 | if (x >= image.width() || y >= image.height()) { 66 | std::cerr << "Coordinates out of bounds." << std::endl; 67 | return 0; 68 | } 69 | 70 | if (matrixMode) { 71 | tabularOut << "# "; 72 | for (size_t wl_i_idx = 0; wl_i_idx < image.nSpectralBands(); 73 | wl_i_idx++) { 74 | tabularOut << image.wavelength_nm(wl_i_idx) << " "; 75 | } 76 | 77 | tabularOut << "\n"; 78 | 79 | for (size_t wl_i_idx = 0; wl_i_idx < image.nSpectralBands(); 80 | wl_i_idx++) { 81 | for (size_t wl_o_idx = 0; wl_o_idx < image.nSpectralBands(); 82 | wl_o_idx++) { 83 | tabularOut << image.getReflectiveValue(x, y, wl_i_idx, wl_o_idx) 84 | << " "; 85 | } 86 | 87 | tabularOut << "\n"; 88 | } 89 | } else { 90 | if (wl_idx >= image.nSpectralBands()) { 91 | std::cerr << "Wavelength index out of bounds." << std::endl; 92 | return 0; 93 | } 94 | 95 | tabularOut << "# Remission for wl_i=" << image.wavelength_nm(wl_idx) 96 | << "nm\n"; 97 | 98 | for (size_t wl_o_idx = 0; wl_o_idx < image.nSpectralBands(); 99 | wl_o_idx++) { 100 | tabularOut << image.wavelength_nm(wl_o_idx) << " " 101 | << image.getReflectiveValue(x, y, wl_idx, wl_o_idx) 102 | << "\n"; 103 | } 104 | } 105 | 106 | return 0; 107 | } -------------------------------------------------------------------------------- /app/export-spectrum/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(export-spectrum main.cpp) 2 | 3 | target_link_libraries(export-spectrum PUBLIC EXRSpectralImage) 4 | 5 | install(TARGETS export-spectrum RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -------------------------------------------------------------------------------- /app/export-spectrum/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | using namespace SEXR; 43 | 44 | int main(int argc, char *argv[]) 45 | { 46 | if (argc < 5) { 47 | std::cout << "Usage:" << std::endl 48 | << "------" << std::endl 49 | << argv[0] << " " 50 | << std::endl 51 | << std::endl; 52 | 53 | return 0; 54 | } 55 | 56 | const EXRSpectralImage image(argv[1]); 57 | const size_t x = std::stoi(argv[2]); 58 | const size_t y = std::stoi(argv[3]); 59 | std::ofstream tabularOut(argv[4]); 60 | 61 | tabularOut << "# lambda(nm) "; 62 | 63 | for (size_t s = 0; s < image.nStokesComponents(); s++) { 64 | tabularOut << "S" << s << " "; 65 | } 66 | 67 | if (image.isReflective()) { 68 | tabularOut << "T"; 69 | } 70 | 71 | for (size_t wl_idx = 0; wl_idx < image.nSpectralBands(); wl_idx++) { 72 | tabularOut << image.wavelength_nm(wl_idx); 73 | 74 | for (size_t s = 0; s < image.nStokesComponents(); s++) { 75 | tabularOut << " " << image.emissive(x, y, wl_idx, s); 76 | } 77 | 78 | if (image.isReflective()) { 79 | tabularOut << " " << image.reflective(x, y, wl_idx); 80 | } 81 | 82 | tabularOut << "\n"; 83 | } 84 | 85 | return 0; 86 | } -------------------------------------------------------------------------------- /app/fluo-exr/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(fluo-exr main.cpp) 2 | 3 | target_link_libraries(fluo-exr PUBLIC EXRSpectralImage) 4 | 5 | install(TARGETS fluo-exr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -------------------------------------------------------------------------------- /app/fluo-exr/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | #include 42 | 43 | #include "fluo_data.h" 44 | 45 | using namespace SEXR; 46 | 47 | int main(/*int argc, char *argv[]*/) 48 | { 49 | const size_t width = 150; 50 | const size_t height = 150; 51 | 52 | // Make it square 53 | const float wl_start = std::max(wi_start, wo_start); 54 | assert(wi_inc == wo_inc); 55 | const float wl_inc = wi_inc; 56 | 57 | const size_t wl_i_idx_start = (wl_start - wi_start) / wi_inc; 58 | const size_t wl_o_idx_start = (wl_start - wo_start) / wo_inc; 59 | 60 | const size_t wl_size = std::min(wi_size, wo_size); 61 | 62 | std::vector wavelengths_nm(wl_size); 63 | 64 | for (size_t i = 0; i < wl_size; i++) { 65 | wavelengths_nm[i] = wl_start + i * wl_inc; 66 | } 67 | 68 | EXRBiSpectralImage fluoImage(width, height, wavelengths_nm, BISPECTRAL); 69 | 70 | size_t x_sz = 50; 71 | size_t y_sz = 50; 72 | 73 | for (size_t y = 0; y < height; y++) { 74 | size_t c_id = y / y_sz; 75 | 76 | for (size_t x = 0; x < width; x++) { 77 | size_t r_id = x / x_sz; 78 | 79 | // Checkerboard patern 80 | const auto ptr = (((c_id % 2) + (r_id % 2)) % 2 == 0) 81 | ? fluorescent_pink 82 | : fluorescent_yellow; 83 | 84 | for (size_t wl_i_idx = 0; wl_i_idx < wl_size; wl_i_idx++) { 85 | const size_t db_i = wl_i_idx + wl_i_idx_start; 86 | assert(db_i < wi_size); 87 | 88 | for (size_t wl_o_idx = wl_i_idx; wl_o_idx < wl_size; 89 | wl_o_idx++) { 90 | const size_t db_o = wl_o_idx + wl_o_idx_start; 91 | assert(db_o < wo_size); 92 | 93 | fluoImage.reflective(x, y, wl_i_idx, wl_o_idx) 94 | = std::max(0.F, ptr[db_o][db_i]); 95 | } 96 | } 97 | } 98 | } 99 | 100 | fluoImage.save("BiSpectral.exr"); 101 | } -------------------------------------------------------------------------------- /app/macbeth/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(macbeth main.cpp) 2 | 3 | target_link_libraries(macbeth PUBLIC EXRSpectralImage) 4 | 5 | install(TARGETS macbeth RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -------------------------------------------------------------------------------- /app/macbeth/macbeth_data.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | // Data from http://www.babelcolor.com/colorchecker-2.htm 40 | 41 | const float macbeth_wavelengths[36] 42 | = {380, 390, 400, 410, 420, 430, 440, 450, 460, 470, 480, 490, 43 | 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 44 | 620, 630, 640, 650, 660, 670, 680, 690, 700, 710, 720, 730}; 45 | const float macbeth_patches[24][36] = { 46 | {0.055, 0.058, 0.061, 0.062, 0.062, 0.062, 0.062, 0.062, 0.062, 47 | 0.062, 0.062, 0.063, 0.065, 0.070, 0.076, 0.079, 0.081, 0.084, 48 | 0.091, 0.103, 0.119, 0.134, 0.143, 0.147, 0.151, 0.158, 0.168, 49 | 0.179, 0.188, 0.190, 0.186, 0.181, 0.182, 0.187, 0.196, 0.209}, 50 | {0.117, 0.143, 0.175, 0.191, 0.196, 0.199, 0.204, 0.213, 0.228, 51 | 0.251, 0.280, 0.309, 0.329, 0.333, 0.315, 0.286, 0.273, 0.276, 52 | 0.277, 0.289, 0.339, 0.420, 0.488, 0.525, 0.546, 0.562, 0.578, 53 | 0.595, 0.612, 0.625, 0.638, 0.656, 0.678, 0.700, 0.717, 0.734}, 54 | {0.130, 0.177, 0.251, 0.306, 0.324, 0.330, 0.333, 0.331, 0.323, 55 | 0.311, 0.298, 0.285, 0.269, 0.250, 0.231, 0.214, 0.199, 0.185, 56 | 0.169, 0.157, 0.149, 0.145, 0.142, 0.141, 0.141, 0.141, 0.143, 57 | 0.147, 0.152, 0.154, 0.150, 0.144, 0.136, 0.132, 0.135, 0.147}, 58 | {0.051, 0.054, 0.056, 0.057, 0.058, 0.059, 0.060, 0.061, 0.062, 59 | 0.063, 0.065, 0.067, 0.075, 0.101, 0.145, 0.178, 0.184, 0.170, 60 | 0.149, 0.133, 0.122, 0.115, 0.109, 0.105, 0.104, 0.106, 0.109, 61 | 0.112, 0.114, 0.114, 0.112, 0.112, 0.115, 0.120, 0.125, 0.130}, 62 | {0.144, 0.198, 0.294, 0.375, 0.408, 0.421, 0.426, 0.426, 0.419, 63 | 0.403, 0.379, 0.346, 0.311, 0.281, 0.254, 0.229, 0.214, 0.208, 64 | 0.202, 0.194, 0.193, 0.200, 0.214, 0.230, 0.241, 0.254, 0.279, 65 | 0.313, 0.348, 0.366, 0.366, 0.359, 0.358, 0.365, 0.377, 0.398}, 66 | {0.136, 0.179, 0.247, 0.297, 0.320, 0.337, 0.355, 0.381, 0.419, 67 | 0.466, 0.510, 0.546, 0.567, 0.574, 0.569, 0.551, 0.524, 0.488, 68 | 0.445, 0.400, 0.350, 0.299, 0.252, 0.221, 0.204, 0.196, 0.191, 69 | 0.188, 0.191, 0.199, 0.212, 0.223, 0.232, 0.233, 0.229, 0.229}, 70 | {0.054, 0.054, 0.053, 0.054, 0.054, 0.055, 0.055, 0.055, 0.056, 71 | 0.057, 0.058, 0.061, 0.068, 0.089, 0.125, 0.154, 0.174, 0.199, 72 | 0.248, 0.335, 0.444, 0.538, 0.587, 0.595, 0.591, 0.587, 0.584, 73 | 0.584, 0.590, 0.603, 0.620, 0.639, 0.655, 0.663, 0.663, 0.667}, 74 | {0.122, 0.164, 0.229, 0.286, 0.327, 0.361, 0.388, 0.400, 0.392, 75 | 0.362, 0.316, 0.260, 0.209, 0.168, 0.138, 0.117, 0.104, 0.096, 76 | 0.090, 0.086, 0.084, 0.084, 0.084, 0.084, 0.084, 0.085, 0.090, 77 | 0.098, 0.109, 0.123, 0.143, 0.169, 0.205, 0.244, 0.287, 0.332}, 78 | {0.096, 0.115, 0.131, 0.135, 0.133, 0.132, 0.130, 0.128, 0.125, 79 | 0.120, 0.115, 0.110, 0.105, 0.100, 0.095, 0.093, 0.092, 0.093, 80 | 0.096, 0.108, 0.156, 0.265, 0.399, 0.500, 0.556, 0.579, 0.588, 81 | 0.591, 0.593, 0.594, 0.598, 0.602, 0.607, 0.609, 0.609, 0.610}, 82 | {0.092, 0.116, 0.146, 0.169, 0.178, 0.173, 0.158, 0.139, 0.119, 83 | 0.101, 0.087, 0.075, 0.066, 0.060, 0.056, 0.053, 0.051, 0.051, 84 | 0.052, 0.052, 0.051, 0.052, 0.058, 0.073, 0.096, 0.119, 0.141, 85 | 0.166, 0.194, 0.227, 0.265, 0.309, 0.355, 0.396, 0.436, 0.478}, 86 | {0.061, 0.061, 0.062, 0.063, 0.064, 0.066, 0.069, 0.075, 0.085, 87 | 0.105, 0.139, 0.192, 0.271, 0.376, 0.476, 0.531, 0.549, 0.546, 88 | 0.528, 0.504, 0.471, 0.428, 0.381, 0.347, 0.327, 0.318, 0.312, 89 | 0.310, 0.314, 0.327, 0.345, 0.363, 0.376, 0.381, 0.378, 0.379}, 90 | {0.063, 0.063, 0.063, 0.064, 0.064, 0.064, 0.065, 0.066, 0.067, 91 | 0.068, 0.071, 0.076, 0.087, 0.125, 0.206, 0.305, 0.383, 0.431, 92 | 0.469, 0.518, 0.568, 0.607, 0.628, 0.637, 0.640, 0.642, 0.645, 93 | 0.648, 0.651, 0.653, 0.657, 0.664, 0.673, 0.680, 0.684, 0.688}, 94 | {0.066, 0.079, 0.102, 0.146, 0.200, 0.244, 0.282, 0.309, 0.308, 95 | 0.278, 0.231, 0.178, 0.130, 0.094, 0.070, 0.054, 0.046, 0.042, 96 | 0.039, 0.038, 0.038, 0.038, 0.038, 0.039, 0.039, 0.040, 0.041, 97 | 0.042, 0.044, 0.045, 0.046, 0.046, 0.048, 0.052, 0.057, 0.065}, 98 | {0.052, 0.053, 0.054, 0.055, 0.057, 0.059, 0.061, 0.066, 0.075, 99 | 0.093, 0.125, 0.178, 0.246, 0.307, 0.337, 0.334, 0.317, 0.293, 100 | 0.262, 0.230, 0.198, 0.165, 0.135, 0.115, 0.104, 0.098, 0.094, 101 | 0.092, 0.093, 0.097, 0.102, 0.108, 0.113, 0.115, 0.114, 0.114}, 102 | {0.050, 0.049, 0.048, 0.047, 0.047, 0.047, 0.047, 0.047, 0.046, 103 | 0.045, 0.044, 0.044, 0.045, 0.046, 0.047, 0.048, 0.049, 0.050, 104 | 0.054, 0.060, 0.072, 0.104, 0.178, 0.312, 0.467, 0.581, 0.644, 105 | 0.675, 0.690, 0.698, 0.706, 0.715, 0.724, 0.730, 0.734, 0.738}, 106 | {0.058, 0.054, 0.052, 0.052, 0.053, 0.054, 0.056, 0.059, 0.067, 107 | 0.081, 0.107, 0.152, 0.225, 0.336, 0.462, 0.559, 0.616, 0.650, 108 | 0.672, 0.694, 0.710, 0.723, 0.731, 0.739, 0.746, 0.752, 0.758, 109 | 0.764, 0.769, 0.771, 0.776, 0.782, 0.790, 0.796, 0.799, 0.804}, 110 | {0.145, 0.195, 0.283, 0.346, 0.362, 0.354, 0.334, 0.306, 0.276, 111 | 0.248, 0.218, 0.190, 0.168, 0.149, 0.127, 0.107, 0.100, 0.102, 112 | 0.104, 0.109, 0.137, 0.200, 0.290, 0.400, 0.516, 0.615, 0.687, 113 | 0.732, 0.760, 0.774, 0.783, 0.793, 0.803, 0.812, 0.817, 0.825}, 114 | {0.108, 0.141, 0.192, 0.236, 0.261, 0.286, 0.317, 0.353, 0.390, 115 | 0.426, 0.446, 0.444, 0.423, 0.385, 0.337, 0.283, 0.231, 0.185, 116 | 0.146, 0.118, 0.101, 0.090, 0.082, 0.076, 0.074, 0.073, 0.073, 117 | 0.074, 0.076, 0.077, 0.076, 0.075, 0.073, 0.072, 0.074, 0.079}, 118 | {0.189, 0.255, 0.423, 0.660, 0.811, 0.862, 0.877, 0.884, 0.891, 119 | 0.896, 0.899, 0.904, 0.907, 0.909, 0.911, 0.910, 0.911, 0.914, 120 | 0.913, 0.916, 0.915, 0.916, 0.914, 0.915, 0.918, 0.919, 0.921, 121 | 0.923, 0.924, 0.922, 0.922, 0.925, 0.927, 0.930, 0.930, 0.933}, 122 | {0.171, 0.232, 0.365, 0.507, 0.567, 0.583, 0.588, 0.590, 0.591, 123 | 0.590, 0.588, 0.588, 0.589, 0.589, 0.591, 0.590, 0.590, 0.590, 124 | 0.589, 0.591, 0.590, 0.590, 0.587, 0.585, 0.583, 0.580, 0.578, 125 | 0.576, 0.574, 0.572, 0.571, 0.569, 0.568, 0.568, 0.566, 0.566}, 126 | {0.144, 0.192, 0.272, 0.331, 0.350, 0.357, 0.361, 0.363, 0.363, 127 | 0.361, 0.359, 0.358, 0.358, 0.359, 0.360, 0.360, 0.361, 0.361, 128 | 0.360, 0.362, 0.362, 0.361, 0.359, 0.358, 0.355, 0.352, 0.350, 129 | 0.348, 0.345, 0.343, 0.340, 0.338, 0.335, 0.334, 0.332, 0.331}, 130 | {0.105, 0.131, 0.163, 0.180, 0.186, 0.190, 0.193, 0.194, 0.194, 131 | 0.192, 0.191, 0.191, 0.191, 0.192, 0.192, 0.192, 0.192, 0.192, 132 | 0.192, 0.193, 0.192, 0.192, 0.191, 0.189, 0.188, 0.186, 0.184, 133 | 0.182, 0.181, 0.179, 0.178, 0.176, 0.174, 0.173, 0.172, 0.171}, 134 | {0.068, 0.077, 0.084, 0.087, 0.089, 0.090, 0.092, 0.092, 0.091, 135 | 0.090, 0.090, 0.090, 0.090, 0.090, 0.090, 0.090, 0.090, 0.090, 136 | 0.090, 0.090, 0.090, 0.089, 0.089, 0.088, 0.087, 0.086, 0.086, 137 | 0.085, 0.084, 0.084, 0.083, 0.083, 0.082, 0.081, 0.081, 0.081}, 138 | {0.031, 0.032, 0.032, 0.033, 0.033, 0.033, 0.033, 0.033, 0.032, 139 | 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 140 | 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 141 | 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.032, 0.033}}; -------------------------------------------------------------------------------- /app/macbeth/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | #include "macbeth_data.h" 43 | 44 | using namespace SEXR; 45 | 46 | int main(int argc, char *argv[]) 47 | { 48 | (void)argc; 49 | (void)argv; 50 | 51 | const size_t width = 600; 52 | const size_t height = 400; 53 | 54 | std::vector wavelengths( 55 | std::begin(macbeth_wavelengths), 56 | std::end(macbeth_wavelengths)); 57 | 58 | EXRSpectralImage spectralImage(width, height, wavelengths, REFLECTIVE); 59 | 60 | for (size_t y = 0; y < height; y++) { 61 | const float v_idx = 4.F * float(y) / float(height); 62 | const float f_v = v_idx - std::floor(v_idx); 63 | 64 | for (size_t x = 0; x < width; x++) { 65 | const float u_idx = 6.F * float(x) / float(width); 66 | const float f_u = u_idx - std::floor(u_idx); 67 | 68 | const float s_v = 0.1; 69 | const float s_u = 0.1; 70 | 71 | int idx = int(u_idx) + 6 * int(v_idx); 72 | 73 | if ( 74 | ((int(v_idx) == 0 && f_v > s_v && f_v < 1. - s_v / 2.) 75 | || (int(v_idx) > 0 && int(v_idx) < 3 && f_v > s_v / 2. && f_v < 1. - s_v / 2.) 76 | || (int(v_idx) == 3 && f_v > s_v / 2. && f_v < 1. - s_v)) 77 | && ((int(u_idx) == 0 && f_u > s_u && f_u < 1. - s_u / 2.) || (int(u_idx) > 0 && int(u_idx) < 5 && f_u > s_u / 2. && f_u < 1. - s_u / 2.) || (int(u_idx) == 5 && f_u > s_u / 2. && f_u < 1. - s_u))) { 78 | memcpy( 79 | &spectralImage.reflective(x, y, 0), 80 | &macbeth_patches[idx][0], 81 | spectralImage.nSpectralBands() * sizeof(float)); 82 | } 83 | } 84 | } 85 | 86 | spectralImage.save("Macbeth.exr"); 87 | 88 | return 0; 89 | } -------------------------------------------------------------------------------- /app/merge-exr/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # We need C++17 for filesystem library 2 | set(CMAKE_CXX_STANDARD 17) 3 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 4 | 5 | add_executable(merge-exr main.cpp) 6 | 7 | target_link_libraries(merge-exr PUBLIC EXRSpectralImage) 8 | 9 | install(TARGETS merge-exr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) 10 | -------------------------------------------------------------------------------- /app/merge-exr/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #include 48 | #include 49 | 50 | using namespace SEXR; 51 | 52 | bool checkExtension(const std::string &path) 53 | { 54 | if (path.length() < 4) { 55 | return false; 56 | } 57 | 58 | const char *c[2] = {".EXR", ".exr"}; 59 | 60 | for (int i = 0; i < 5; i++) { 61 | if ( 62 | path[path.length() - i] != c[0][4 - i] 63 | && path[path.length() - i] != c[1][4 - i]) { 64 | return false; 65 | } 66 | } 67 | 68 | return true; 69 | } 70 | 71 | 72 | void load_csv( 73 | const std::string & filename, 74 | std::vector &wavelengths_nm, 75 | std::vector &values) 76 | { 77 | wavelengths_nm.clear(); 78 | values.clear(); 79 | 80 | const std::string floatRegex = " *(\\d*\\.?\\d*([Ee][+-]?\\d+)?) *"; 81 | const std::regex e(floatRegex + "," + floatRegex); 82 | 83 | std::ifstream inFile(filename); 84 | std::string line; 85 | 86 | while (std::getline(inFile, line)) { 87 | std::smatch matches; 88 | if (std::regex_search(line, matches, e)) { 89 | wavelengths_nm.push_back(std::stof(matches[1])); 90 | values.push_back(std::stof(matches[3])); 91 | } 92 | } 93 | } 94 | 95 | 96 | int main(int argc, char *argv[]) 97 | { 98 | if (argc < 5) { 99 | std::cout 100 | << "Usage:" << std::endl 101 | << "------" << std::endl 102 | << argv[0] 103 | << " " 104 | " " 105 | << std::endl 106 | << std::endl; 107 | 108 | return 0; 109 | } 110 | 111 | // List files in folder 112 | std::vector files; 113 | const std::string allowed_extension = ".exr"; 114 | 115 | for (auto &p : std::filesystem::directory_iterator(argv[1])) { 116 | std::filesystem::path path = p.path(); 117 | 118 | if (std::filesystem::is_regular_file(path) && checkExtension(path)) { 119 | files.push_back(path); 120 | } 121 | } 122 | 123 | // Sort files 124 | std::sort(files.begin(), files.end()); 125 | 126 | // Get wavelength info 127 | const float start_wl_nm = std::stof(argv[2]); 128 | const float increment_wl_nm = std::stof(argv[3]); 129 | 130 | size_t width(0), height(0); 131 | std::vector pixels; 132 | 133 | std::vector wavelengths; 134 | wavelengths.reserve(files.size()); 135 | 136 | std::vector spectralFramebuffer; 137 | 138 | std::cout << "Using images:" << std::endl; 139 | 140 | for (size_t i = 0; i < files.size(); i++) { 141 | wavelengths.push_back(start_wl_nm + i * increment_wl_nm); 142 | Imf::RgbaInputFile file(files[i].c_str()); 143 | std::cout << "\tAt " << wavelengths[i] << "nm: [" << files[i] << "]" 144 | << std::endl; 145 | 146 | Imath::Box2i dw = file.dataWindow(); 147 | const size_t c_width = dw.max.x - dw.min.x + 1; 148 | const size_t c_height = dw.max.y - dw.min.y + 1; 149 | 150 | if (i == 0) { 151 | width = c_width; 152 | height = c_height; 153 | spectralFramebuffer.resize(width * height * files.size()); 154 | } else if (width != c_width || height != c_height) { 155 | std::cerr << "Image sizes does not match" << std::endl; 156 | return -1; 157 | } 158 | 159 | pixels.resize(height * width); 160 | file.setFrameBuffer(&pixels[0], 1, width); 161 | file.readPixels(dw.min.y, dw.max.y); 162 | 163 | // We can't memcpy: half to float conversion and taking every channel 164 | for (size_t j = 0; j < width * height; j++) { 165 | spectralFramebuffer[files.size() * j + i] 166 | = (pixels[j].r + pixels[j].g + pixels[j].b) / 3.F; 167 | } 168 | } 169 | 170 | // Now, create the spectral image 171 | EXRSpectralImage spectralImage(width, height, wavelengths, EMISSIVE); 172 | 173 | memcpy( 174 | &spectralImage.emissive(0, 0, 0, 0), 175 | &spectralFramebuffer[0], 176 | width * height * wavelengths.size() * sizeof(float)); 177 | 178 | std::cout << std::endl; 179 | 180 | if (argc >= 6) { 181 | std::cout << "Using transmission / response spectrum information:" 182 | << std::endl; 183 | } 184 | 185 | if (argc >= 6) { 186 | std::vector camera_wavelengths_nm; 187 | std::vector camera_response; 188 | 189 | std::cout << "\tCamera response: [" << argv[5] << "]" << std::endl; 190 | 191 | load_csv(argv[5], camera_wavelengths_nm, camera_response); 192 | spectralImage.setCameraResponse(camera_wavelengths_nm, camera_response); 193 | } 194 | 195 | if (argc >= 7) { 196 | std::vector lens_wavelengths_nm; 197 | std::vector lens_transmission; 198 | 199 | std::cout << "\tLens transmission: [" << argv[6] << "]" << std::endl; 200 | 201 | load_csv(argv[6], lens_wavelengths_nm, lens_transmission); 202 | spectralImage.setLensTransmission( 203 | lens_wavelengths_nm, 204 | lens_transmission); 205 | } 206 | 207 | if (argc >= 8) { 208 | for (int c = 7; c < argc; c++) { 209 | std::vector filter_wavelengths_nm; 210 | std::vector filter_transmission; 211 | 212 | std::cout << "\tFilter transmission at: " << wavelengths[c - 7] 213 | << "nm: [" << argv[c] << "]" << std::endl; 214 | 215 | load_csv(argv[c], filter_wavelengths_nm, filter_transmission); 216 | spectralImage.setChannelSensitivity( 217 | c - 7, 218 | filter_wavelengths_nm, 219 | filter_transmission); 220 | } 221 | } 222 | 223 | spectralImage.save(argv[4]); 224 | std::cout << std::endl << "File saved as: [" << argv[4] << "]" << std::endl; 225 | 226 | return 0; 227 | } -------------------------------------------------------------------------------- /app/spectrum-to-exr/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(spectrum-to-exr main.cpp) 2 | 3 | target_link_libraries(spectrum-to-exr PUBLIC EXRSpectralImage) 4 | 5 | install(TARGETS spectrum-to-exr RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -------------------------------------------------------------------------------- /app/spectrum-to-exr/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | #include 43 | 44 | using namespace SEXR; 45 | 46 | int main(int argc, char *argv[]) 47 | { 48 | if (argc < 4) { 49 | std::cout << "Usage:" << std::endl 50 | << "------" << std::endl 51 | << argv[0] << " " << std::endl 52 | << std::endl 53 | << " A spectrum in comma separated values with " 54 | "wavelength_nm, value." 55 | << std::endl 56 | << " Can be \"reflective\" or \"emissive\"." 57 | << std::endl 58 | << " The path to the spectral EXR to create." 59 | << std::endl 60 | << std::endl; 61 | 62 | return 0; 63 | } 64 | 65 | std::vector wavelengths_nm; 66 | std::vector values; 67 | 68 | // Load the CSV file 69 | const std::string numberRegex = " *(\\d*\\.?\\d*([Ee][+-]?\\d+)?) *"; 70 | const std::regex e(numberRegex + "," + numberRegex); 71 | 72 | const std::string fileIn = argv[1]; 73 | const std::string fileOut = argv[3]; 74 | std::cout << "Reading: [" << fileIn << "]" << std::endl; 75 | 76 | std::ifstream inFile(fileIn); 77 | std::string line; 78 | 79 | while (std::getline(inFile, line)) { 80 | std::smatch matches; 81 | if (std::regex_search(line, matches, e)) { 82 | wavelengths_nm.push_back(std::stof(matches[1])); 83 | values.push_back(std::stof(matches[3])); 84 | } 85 | } 86 | 87 | std::cout << "Found " << wavelengths_nm.size() << " samples" << std::endl; 88 | if (wavelengths_nm.size() == 0) { 89 | std::cerr << "The provided spectrum is empty!" << std::endl; 90 | 91 | return -1; 92 | } 93 | 94 | SpectrumType type; 95 | 96 | if (strcmp(argv[2], "reflective") == 0) { 97 | type = REFLECTIVE; 98 | } else if (strcmp(argv[2], "emissive") == 0) { 99 | type = EMISSIVE; 100 | } else { 101 | std::cerr << "Invalid argument for spectrum type!" << std::endl; 102 | std::cerr 103 | << "The spectrum type can either be \"emissive\" or \"reflective\"." 104 | << std::endl; 105 | 106 | return -1; 107 | } 108 | 109 | EXRSpectralImage image(1, 1, wavelengths_nm, type); 110 | if (type == REFLECTIVE) { 111 | memcpy( 112 | &image.reflective(0, 0, 0), 113 | &values[0], 114 | values.size() * sizeof(float)); 115 | } else { 116 | memcpy( 117 | &image.emissive(0, 0, 0, 0), 118 | &values[0], 119 | values.size() * sizeof(float)); 120 | } 121 | image.save(fileOut); 122 | 123 | std::cout << "File saved as: [" << fileOut << "]" << std::endl; 124 | 125 | return 0; 126 | } -------------------------------------------------------------------------------- /app/split-channels/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(split-channels main.cpp) 2 | 3 | target_link_libraries(split-channels PUBLIC EXRSpectralImage) 4 | 5 | install(TARGETS split-channels RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -------------------------------------------------------------------------------- /app/split-channels/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | #include 42 | #include 43 | 44 | using namespace SEXR; 45 | 46 | void writeAttributeCSVIfExists( 47 | const SpectrumAttribute &attr, const std::string &filename) 48 | { 49 | if (attr.size() > 0) { 50 | std::cout << "Exporting metadata: [" << filename << "]" << std::endl; 51 | std::ofstream cameraCSV(filename); 52 | 53 | for (size_t i = 0; i < attr.size(); i++) { 54 | cameraCSV << attr.wavelength_nm(i) << "," << attr.value(i) 55 | << std::endl; 56 | } 57 | } 58 | } 59 | 60 | 61 | int main(int argc, char *argv[]) 62 | { 63 | if (argc < 3) { 64 | std::cout << "Usage:" << std::endl 65 | << "------" << std::endl 66 | << argv[0] << " " << std::endl 67 | << std::endl 68 | << "The must have been created prior to the " 69 | << "execution and with the correct rights." << std::endl; 70 | 71 | return 0; 72 | } 73 | 74 | const std::string outputFolder = argv[2]; 75 | const std::string spectralImageFilename = argv[1]; 76 | 77 | EXRBiSpectralImage spectralImage(spectralImageFilename); 78 | 79 | // Export individual channels as separate files 80 | std::cout << "Writing spectral channels in: [" << outputFolder << "]" 81 | << std::endl; 82 | spectralImage.exportChannels(outputFolder); 83 | 84 | // If present, write the additional response and transmission 85 | writeAttributeCSVIfExists( 86 | spectralImage.cameraResponse(), 87 | outputFolder + "/camera.csv"); 88 | writeAttributeCSVIfExists( 89 | spectralImage.lensTransmission(), 90 | outputFolder + "/lens.csv"); 91 | 92 | for (size_t wl_idx = 0; wl_idx < spectralImage.nSpectralBands(); wl_idx++) { 93 | const float & wavelength_nm = spectralImage.wavelength_nm(wl_idx); 94 | std::stringstream filename; 95 | filename << outputFolder << "/" << wavelength_nm << ".csv"; 96 | 97 | writeAttributeCSVIfExists( 98 | spectralImage.channelSensitivity(wl_idx), 99 | filename.str()); 100 | } 101 | 102 | // Create a simple text file for additional information 103 | const std::string additionalInfoFilename(outputFolder + "/" + "info.txt"); 104 | std::cout << "Writing image informations: [" << additionalInfoFilename 105 | << "]" << std::endl; 106 | std::ofstream additionalInfo(additionalInfoFilename); 107 | 108 | additionalInfo << "Spectral Image: " << spectralImageFilename << " " 109 | << spectralImage.width() << "x" << spectralImage.height() 110 | << "px" << std::endl; 111 | 112 | 113 | additionalInfo << "\tType: "; 114 | 115 | switch (spectralImage.type()) { 116 | case EMISSIVE: 117 | additionalInfo << "emissive" << std::endl; 118 | break; 119 | 120 | case REFLECTIVE: 121 | additionalInfo << "reflective" << std::endl; 122 | break; 123 | 124 | default: 125 | additionalInfo << "unknown" << std::endl; 126 | break; 127 | } 128 | 129 | 130 | additionalInfo << "\tPolarised: "; 131 | 132 | if (spectralImage.isPolarised()) { 133 | additionalInfo << "YES" << std::endl; 134 | } else { 135 | additionalInfo << "NO" << std::endl; 136 | } 137 | 138 | 139 | additionalInfo << "\tSpectral bands: " << spectralImage.nSpectralBands() 140 | << std::endl; 141 | 142 | for (size_t wl_idx = 0; wl_idx < spectralImage.nSpectralBands(); wl_idx++) { 143 | additionalInfo << "\t\t" << spectralImage.wavelength_nm(wl_idx) << "nm" 144 | << std::endl; 145 | } 146 | 147 | additionalInfo << "Metadata:" << std::endl; 148 | bool haveMetadata = false; 149 | 150 | if (spectralImage.cameraResponse().size() > 0) { 151 | additionalInfo << "\tHave camera response information" << std::endl; 152 | haveMetadata = true; 153 | } 154 | 155 | if (spectralImage.lensTransmission().size() > 0) { 156 | additionalInfo << "\tHave lens transmission information" << std::endl; 157 | haveMetadata = true; 158 | } 159 | 160 | for (size_t wl_idx = 0; wl_idx < spectralImage.nSpectralBands(); wl_idx++) { 161 | if (spectralImage.channelSensitivity(wl_idx).size() > 0) { 162 | const float &wavelength_nm = spectralImage.wavelength_nm(wl_idx); 163 | additionalInfo << "\tFilter response for " << wavelength_nm << "nm" 164 | << std::endl; 165 | haveMetadata = true; 166 | } 167 | } 168 | 169 | if (!haveMetadata) { 170 | std::cout << "\tNone" << std::endl; 171 | } 172 | 173 | return 0; 174 | } -------------------------------------------------------------------------------- /format-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for folder in lib app 4 | do 5 | find ${folder} -regex '.*\.\(c\|cpp\|h\)' -exec sed -i "s/#pragma omp/\\/\\/#pragma omp/g" {} \; 6 | find ${folder} -regex '.*\.\(c\|cpp\|h\)' -exec clang-format -style=file -i {} \; 7 | find ${folder} -regex '.*\.\(c\|cpp\|h\)' -exec sed -i "s/\\/\\/ *#pragma omp/#pragma omp/g" {} \; 8 | done 9 | 10 | -------------------------------------------------------------------------------- /lib/BiSpectralImage.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | #include 46 | 47 | #include "SpectrumConverter.h" 48 | 49 | namespace SEXR 50 | { 51 | BiSpectralImage::BiSpectralImage( 52 | size_t width, 53 | size_t height, 54 | const std::vector &wavelengths_nm, 55 | SpectrumType type, 56 | PolarisationHandedness handedness) 57 | : SpectralImage(width, height, wavelengths_nm, type, handedness) 58 | { 59 | if (isBispectral()) { 60 | _reradiation.resize(reradiationSize() * _width * _height); 61 | } 62 | } 63 | 64 | 65 | void BiSpectralImage::exportChannels(const std::string &path) const 66 | { 67 | // Export the diagonal 68 | SpectralImage::exportChannels(path); 69 | 70 | if (isBispectral()) { 71 | const size_t xStride = sizeof(float) * reradiationSize(); 72 | const size_t yStride = xStride * width(); 73 | 74 | // Export the reradiation 75 | for (size_t rr = 0; rr < reradiationSize(); rr++) { 76 | for (size_t wl_i_idx = 0; wl_i_idx < nSpectralBands(); 77 | wl_i_idx++) { 78 | const float &wavelength_i = _wavelengths_nm[wl_i_idx]; 79 | 80 | for (size_t wl_o_idx = wl_i_idx + 1; 81 | wl_o_idx < nSpectralBands(); 82 | wl_o_idx++) { 83 | const float &wavelength_o = _wavelengths_nm[wl_o_idx]; 84 | 85 | std::stringstream filepath; 86 | filepath << path << "/" 87 | << "T - " << wavelength_i << "nm - " 88 | << wavelength_o << "nm.exr"; 89 | 90 | Imf::Header exrHeader(width(), height()); 91 | Imf::ChannelList &exrChannels = exrHeader.channels(); 92 | Imf::FrameBuffer exrFrameBuffer; 93 | 94 | exrChannels.insert("Y", Imf::Channel(Imf::FLOAT)); 95 | exrFrameBuffer.insert( 96 | "Y", 97 | Imf::Slice( 98 | Imf::FLOAT, 99 | (char *)(&_reradiation[rr]), 100 | xStride, 101 | yStride)); 102 | 103 | Imf::OutputFile exrOut( 104 | filepath.str().c_str(), 105 | exrHeader); 106 | exrOut.setFrameBuffer(exrFrameBuffer); 107 | exrOut.writePixels(height()); 108 | } 109 | } 110 | } 111 | } 112 | } 113 | 114 | 115 | void BiSpectralImage::getRGBImage(std::vector &rgbImage) const 116 | { 117 | if (!isBispectral()) { 118 | SpectralImage::getRGBImage(rgbImage); 119 | } else { 120 | rgbImage.resize(3 * width() * height()); 121 | SpectrumConverter sc(isEmissive()); 122 | 123 | std::array rgb; 124 | 125 | if (isEmissive() && isReflective()) { 126 | for (size_t i = 0; i < width() * height(); i++) { 127 | sc.spectraToRGB( 128 | _wavelengths_nm, 129 | &_reflectivePixelBuffer[nSpectralBands() * i], 130 | &_reradiation[reradiationSize() * i], 131 | &_emissivePixelBuffers[0][nSpectralBands() * i], 132 | rgb); 133 | 134 | memcpy(&rgbImage[3 * i], &rgb[0], 3 * sizeof(float)); 135 | } 136 | 137 | // Exposure compensation 138 | for (size_t i = 0; i < width() * height(); i++) { 139 | for (size_t c = 0; c < 3; c++) { 140 | rgbImage[3 * i + c] *= std::pow(2.F, _ev); 141 | } 142 | } 143 | } else if (isReflective()) { 144 | for (size_t i = 0; i < width() * height(); i++) { 145 | sc.spectrumToRGB( 146 | _wavelengths_nm, 147 | &_reflectivePixelBuffer[nSpectralBands() * i], 148 | &_reradiation[reradiationSize() * i], 149 | rgb); 150 | 151 | memcpy(&rgbImage[3 * i], &rgb[0], 3 * sizeof(float)); 152 | } 153 | 154 | // Exposure compensation 155 | for (size_t i = 0; i < width() * height(); i++) { 156 | for (size_t c = 0; c < 3; c++) { 157 | rgbImage[3 * i + c] *= std::pow(2.F, _ev); 158 | } 159 | } 160 | } 161 | } 162 | } 163 | 164 | 165 | size_t 166 | BiSpectralImage::idxFromWavelengthIdx(size_t wlFrom_idx, size_t wlTo_idx) 167 | { 168 | if (wlFrom_idx < wlTo_idx) { 169 | return wlTo_idx * (wlTo_idx - 1) / 2 + wlFrom_idx; 170 | } else { 171 | return -1; 172 | } 173 | } 174 | 175 | 176 | void BiSpectralImage::wavelengthsIdxFromIdx( 177 | size_t rerad_idx, size_t &wlFrom_idx, size_t &wlTo_idx) 178 | { 179 | float k 180 | = std::floor((std::sqrt(1.F + 8.F * float(rerad_idx)) - 1.F) / 2.F); 181 | float j = rerad_idx - k * (k + 1) / 2.F; 182 | 183 | wlFrom_idx = j; 184 | wlTo_idx = k + 1; 185 | } 186 | 187 | 188 | float BiSpectralImage::getReflectiveValue( 189 | size_t x, 190 | size_t y, 191 | size_t wavelengthFrom_idx, 192 | size_t wavelengthTo_idx) const 193 | { 194 | assert(x < width()); 195 | assert(y < height()); 196 | assert(wavelengthFrom_idx < nSpectralBands()); 197 | assert(wavelengthTo_idx < nSpectralBands()); 198 | 199 | if (!isReflective() || wavelengthFrom_idx > wavelengthTo_idx) { 200 | return 0.F; 201 | } 202 | 203 | if (!isBispectral()) { 204 | return 0.F; 205 | } 206 | 207 | return reflective(x, y, wavelengthFrom_idx, wavelengthTo_idx); 208 | } 209 | 210 | 211 | float &BiSpectralImage::reflective( 212 | size_t x, size_t y, size_t wavelengthFrom_idx, size_t wavelengthTo_idx) 213 | { 214 | assert(isReflective()); 215 | assert(x < width()); 216 | assert(y < height()); 217 | assert(wavelengthFrom_idx < nSpectralBands()); 218 | assert(wavelengthTo_idx < nSpectralBands()); 219 | assert(wavelengthFrom_idx <= wavelengthTo_idx); 220 | 221 | if (wavelengthFrom_idx == wavelengthTo_idx) { 222 | return SpectralImage::reflective(x, y, wavelengthFrom_idx); 223 | } 224 | 225 | assert(isBispectral()); 226 | assert(_reradiation.size() == reradiationSize() * width() * height()); 227 | 228 | size_t reradIdx 229 | = idxFromWavelengthIdx(wavelengthFrom_idx, wavelengthTo_idx); 230 | 231 | return _reradiation[reradiationSize() * (y * width() + x) + reradIdx]; 232 | } 233 | 234 | 235 | const float &BiSpectralImage::reflective( 236 | size_t x, 237 | size_t y, 238 | size_t wavelengthFrom_idx, 239 | size_t wavelengthTo_idx) const 240 | { 241 | assert(isReflective()); 242 | assert(x < width()); 243 | assert(y < height()); 244 | assert(wavelengthFrom_idx < nSpectralBands()); 245 | assert(wavelengthTo_idx < nSpectralBands()); 246 | 247 | if (wavelengthFrom_idx == wavelengthTo_idx) { 248 | return SpectralImage::reflective(x, y, wavelengthFrom_idx); 249 | } 250 | 251 | assert(isBispectral()); 252 | assert(_reradiation.size() == reradiationSize() * width() * height()); 253 | 254 | size_t reradIdx 255 | = idxFromWavelengthIdx(wavelengthFrom_idx, wavelengthTo_idx); 256 | 257 | return _reradiation[reradiationSize() * (y * width() + x) + reradIdx]; 258 | } 259 | 260 | } // namespace SEXR 261 | -------------------------------------------------------------------------------- /lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.1) 2 | 3 | project(EXRSpectralImage 4 | VERSION 1.0.0 5 | DESCRIPTION "Library for loading and writing OpenEXR spectral images" 6 | ) 7 | 8 | set(CMAKE_CXX_STANDARD 11) 9 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 10 | 11 | # OpenEXR 3.0 provides its own find_package 12 | find_package(OpenEXR CONFIG) 13 | find_package(Imath CONFIG) 14 | 15 | if (OpenEXR_FOUND) 16 | set(OpenEXR_LIBRARIES OpenEXR::OpenEXR Imath::Imath) 17 | else() 18 | # For OpenEXR 2.0 we rely on our own find_package 19 | set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) 20 | find_package(OpenEXR REQUIRED) 21 | endif() 22 | 23 | if (OpenEXR_FOUND) 24 | set(PUBLIC_HEADERS 25 | include/SpectrumType.h 26 | include/SpectrumAttribute.h 27 | 28 | include/SpectralImage.h 29 | include/EXRSpectralImage.h 30 | 31 | # Optional bi spectral variants 32 | include/BiSpectralImage.h 33 | include/EXRBiSpectralImage.h 34 | ) 35 | 36 | add_library(EXRSpectralImage SHARED 37 | SpectralImage.cpp 38 | EXRSpectralImage.cpp 39 | 40 | SpectrumConverter.cpp 41 | SpectrumAttribute.cpp 42 | 43 | # Optional bi spectral variants 44 | BiSpectralImage.cpp 45 | EXRBiSpectralImage.cpp 46 | ) 47 | 48 | set_target_properties(EXRSpectralImage PROPERTIES VERSION ${PROJECT_VERSION}) 49 | set_target_properties(EXRSpectralImage PROPERTIES SOVERSION 1) 50 | set_target_properties(EXRSpectralImage PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") 51 | 52 | target_include_directories(EXRSpectralImage PUBLIC include) 53 | target_include_directories(EXRSpectralImage PUBLIC ${OpenEXR_INCLUDE_DIR}) 54 | 55 | target_link_libraries(EXRSpectralImage PUBLIC ${OpenEXR_LIBRARIES}) 56 | 57 | if (MSVC) 58 | target_compile_options(EXRSpectralImage PUBLIC /W3) 59 | else() 60 | target_compile_options(EXRSpectralImage PUBLIC -Wall -Wextra -Wpedantic) 61 | endif() 62 | 63 | configure_file(EXRSpectralImage.pc.in ${CMAKE_BINARY_DIR}/EXRSpectralImage.pc @ONLY) 64 | 65 | install(TARGETS EXRSpectralImage 66 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 67 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 68 | ) 69 | 70 | install(FILES ${CMAKE_BINARY_DIR}/EXRSpectralImage.pc DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) 71 | else() 72 | message("You need OpenEXR libraries to compile this code.") 73 | endif() 74 | -------------------------------------------------------------------------------- /lib/EXRBiSpectralImage.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include "Util.h" 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | namespace SEXR 52 | { 53 | EXRBiSpectralImage::EXRBiSpectralImage( 54 | size_t width, 55 | size_t height, 56 | const std::vector &wavelengths_nm, 57 | SpectrumType type, 58 | PolarisationHandedness handedness) 59 | : BiSpectralImage(width, height, wavelengths_nm, type, handedness) 60 | {} 61 | 62 | 63 | EXRBiSpectralImage::EXRBiSpectralImage(const std::string &filename) 64 | : BiSpectralImage() 65 | { 66 | Imf::InputFile exrIn(filename.c_str()); 67 | const Imf::Header & exrHeader = exrIn.header(); 68 | const Imath::Box2i &exrDataWindow = exrHeader.dataWindow(); 69 | 70 | _width = exrDataWindow.max.x - exrDataWindow.min.x + 1; 71 | _height = exrDataWindow.max.y - exrDataWindow.min.y + 1; 72 | _spectrumType = SpectrumType::UNDEFINED; 73 | 74 | // -------------------------------------------------------------------- 75 | // Determine channels' position 76 | // -------------------------------------------------------------------- 77 | 78 | const Imf::ChannelList &exrChannels = exrHeader.channels(); 79 | 80 | std::array>, 4> 81 | wavelengths_nm_S; 82 | std::vector> wavelengths_nm_diagonal; 83 | std::vector, std::string>> 84 | reradiation_wavelengths_nm; 85 | 86 | for (Imf::ChannelList::ConstIterator channel = exrChannels.begin(); 87 | channel != exrChannels.end(); 88 | channel++) { 89 | // Check if the channel is a spectral or a bispectral one 90 | int polarisationComponent; 91 | double in_wavelength_nm, out_wavelength_nm; 92 | 93 | SpectrumType currChannelType = channelType( 94 | channel.name(), 95 | polarisationComponent, 96 | in_wavelength_nm, 97 | out_wavelength_nm); 98 | 99 | if (currChannelType != SpectrumType::UNDEFINED) { 100 | _spectrumType = _spectrumType | currChannelType; 101 | 102 | if (isReflectiveSpectrum(currChannelType)) { 103 | if (!isBispectralSpectrum(currChannelType)) { 104 | wavelengths_nm_diagonal.push_back( 105 | std::make_pair(in_wavelength_nm, channel.name())); 106 | } else { 107 | reradiation_wavelengths_nm.push_back(std::make_pair( 108 | std::make_pair(in_wavelength_nm, out_wavelength_nm), 109 | channel.name())); 110 | } 111 | } else if (isEmissiveSpectrum(currChannelType)) { 112 | assert(polarisationComponent < 4); 113 | wavelengths_nm_S[polarisationComponent].push_back( 114 | std::make_pair(in_wavelength_nm, channel.name())); 115 | } 116 | } 117 | } 118 | 119 | // Sort by ascending wavelengths 120 | for (size_t s = 0; s < nStokesComponents(); s++) { 121 | std::sort(wavelengths_nm_S[s].begin(), wavelengths_nm_S[s].end()); 122 | } 123 | 124 | std::sort( 125 | wavelengths_nm_diagonal.begin(), 126 | wavelengths_nm_diagonal.end()); 127 | 128 | struct { 129 | bool operator()( 130 | std::pair, std::string> a, 131 | std::pair, std::string> b) const 132 | { 133 | if (a.first.first == b.first.first) { 134 | return a.first.second < b.first.second; 135 | } else { 136 | return a.first.first < b.first.first; 137 | } 138 | } 139 | } sortBispectral; 140 | 141 | std::sort( 142 | reradiation_wavelengths_nm.begin(), 143 | reradiation_wavelengths_nm.end(), 144 | sortBispectral); 145 | 146 | // -------------------------------------------------------------------- 147 | // Sanity check 148 | // -------------------------------------------------------------------- 149 | 150 | if (_spectrumType == SpectrumType::UNDEFINED) { 151 | // Probably an RGB EXR, not our job to handle it 152 | throw INCORRECT_FORMED_FILE; 153 | } 154 | 155 | if (isEmissive()) { 156 | // Check we have the same wavelength for each Stokes component 157 | // Wavelength vectors must be of the same size 158 | const float base_size_emissive = wavelengths_nm_S[0].size(); 159 | 160 | for (size_t s = 1; s < nStokesComponents(); s++) { 161 | if (wavelengths_nm_S[s].size() != base_size_emissive) { 162 | throw INCORRECT_FORMED_FILE; 163 | } 164 | 165 | // Wavelengths must correspond 166 | for (size_t wl_idx = 0; wl_idx < base_size_emissive; wl_idx++) { 167 | if ( 168 | wavelengths_nm_S[s][wl_idx].first 169 | != wavelengths_nm_S[0][wl_idx].first) { 170 | throw INCORRECT_FORMED_FILE; 171 | } 172 | } 173 | } 174 | } 175 | 176 | // If both reflective and emissive, we need to perform a last sanity check 177 | if (isEmissive() && isReflective()) { 178 | const size_t n_emissive_wavelengths = wavelengths_nm_S[0].size(); 179 | const size_t n_reflective_wavelengths 180 | = wavelengths_nm_diagonal.size(); 181 | 182 | if (n_emissive_wavelengths != n_reflective_wavelengths) 183 | throw INCORRECT_FORMED_FILE; 184 | 185 | for (size_t wl_idx = 0; wl_idx < n_emissive_wavelengths; wl_idx++) { 186 | if ( 187 | wavelengths_nm_S[0][wl_idx] 188 | != wavelengths_nm_diagonal[wl_idx]) 189 | throw INCORRECT_FORMED_FILE; 190 | } 191 | } 192 | 193 | // Now, we can populate the local wavelength vector 194 | if (isEmissive()) { 195 | _wavelengths_nm.reserve(wavelengths_nm_S[0].size()); 196 | 197 | for (const auto &wl_index : wavelengths_nm_S[0]) { 198 | _wavelengths_nm.push_back(wl_index.first); 199 | } 200 | } else { 201 | _wavelengths_nm.reserve(wavelengths_nm_diagonal.size()); 202 | 203 | for (const auto &wl_index : wavelengths_nm_diagonal) { 204 | _wavelengths_nm.push_back(wl_index.first); 205 | } 206 | } 207 | 208 | // Check every single reradiation have all upper wavelength 209 | // values 210 | // Note: this shall not be mandatory in the format but, 211 | // we only support that for now 212 | if (isBispectral()) { 213 | if (reradiation_wavelengths_nm.size() != reradiationSize()) { 214 | std::cerr << "Reradiation is incomplete" << std::endl; 215 | } 216 | 217 | float currDiagWl = wavelength_nm(0); 218 | size_t diagonalIdx = 0; 219 | size_t reradIdx = 1; 220 | 221 | for (size_t rr = 0; rr < reradiation_wavelengths_nm.size(); rr++) { 222 | const auto &rerad = reradiation_wavelengths_nm[rr]; 223 | 224 | if (rerad.first.first > currDiagWl) { 225 | if (diagonalIdx + reradIdx != nSpectralBands()) { 226 | std::cerr << "We need the full spectral reradiation " 227 | "specification" 228 | << std::endl; 229 | } 230 | diagonalIdx++; 231 | reradIdx = 1; 232 | 233 | currDiagWl = wavelength_nm(diagonalIdx); 234 | } 235 | 236 | if (rerad.first.first != wavelength_nm(diagonalIdx)) { 237 | std::cerr 238 | << "We need the full spectral reradiation specification" 239 | << std::endl; 240 | } 241 | 242 | if ( 243 | rerad.first.second != wavelength_nm(diagonalIdx + reradIdx)) { 244 | std::cerr 245 | << "We need the full spectral reradiation specification" 246 | << std::endl; 247 | } 248 | 249 | reradIdx++; 250 | } 251 | } 252 | 253 | // -------------------------------------------------------------------- 254 | // Allocate memory 255 | // -------------------------------------------------------------------- 256 | 257 | for (size_t s = 0; s < nStokesComponents(); s++) { 258 | _emissivePixelBuffers[s].resize( 259 | nSpectralBands() * width() * height()); 260 | } 261 | 262 | if (isReflective()) { 263 | _reflectivePixelBuffer.resize( 264 | nSpectralBands() * width() * height()); 265 | 266 | if (isBispectral()) { 267 | _reradiation.resize(reradiationSize() * width() * height()); 268 | } 269 | } 270 | 271 | // --------------------------------------------------------------------- 272 | // Read the pixel data 273 | // --------------------------------------------------------------------- 274 | 275 | Imf::FrameBuffer exrFrameBuffer; 276 | 277 | const Imf::PixelType compType = Imf::FLOAT; 278 | 279 | // Set the diagonal for reading 280 | const size_t xStride = sizeof(float) * nSpectralBands(); 281 | const size_t yStride = xStride * width(); 282 | 283 | for (size_t s = 0; s < nStokesComponents(); s++) { 284 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 285 | char * ptrS = (char *)(&_emissivePixelBuffers[s][wl_idx]); 286 | Imf::Slice slice = Imf::Slice::Make( 287 | compType, 288 | ptrS, 289 | exrDataWindow, 290 | xStride, 291 | yStride); 292 | 293 | exrFrameBuffer.insert( 294 | wavelengths_nm_S[s][wl_idx].second, 295 | slice); 296 | } 297 | } 298 | 299 | if (isReflective()) { 300 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 301 | char * ptrS = (char *)(&_reflectivePixelBuffer[wl_idx]); 302 | Imf::Slice slice = Imf::Slice::Make( 303 | compType, 304 | ptrS, 305 | exrDataWindow, 306 | xStride, 307 | yStride); 308 | 309 | exrFrameBuffer.insert( 310 | wavelengths_nm_diagonal[wl_idx].second, 311 | slice); 312 | } 313 | 314 | if (isBispectral()) { 315 | // Set the reradiation part fo reading 316 | const size_t xStrideReradiation 317 | = sizeof(float) * reradiationSize(); 318 | const size_t yStrideReradiation = xStrideReradiation * width(); 319 | 320 | for (size_t rr = 0; rr < reradiationSize(); rr++) { 321 | char * framebuffer = (char *)(&_reradiation[rr]); 322 | Imf::Slice slice = Imf::Slice::Make( 323 | compType, 324 | framebuffer, 325 | exrDataWindow, 326 | xStrideReradiation, 327 | yStrideReradiation); 328 | 329 | exrFrameBuffer.insert( 330 | reradiation_wavelengths_nm[reradiationSize() - rr - 1] 331 | .second, 332 | slice); 333 | } 334 | } 335 | } 336 | 337 | exrIn.setFrameBuffer(exrFrameBuffer); 338 | exrIn.readPixels(exrDataWindow.min.y, exrDataWindow.max.y); 339 | 340 | // --------------------------------------------------------------------- 341 | // Read metadata 342 | // --------------------------------------------------------------------- 343 | 344 | // Check if the version match 345 | const Imf::StringAttribute *versionAttr 346 | = exrHeader.findTypedAttribute(VERSION_ATTR); 347 | 348 | if ( 349 | versionAttr == nullptr 350 | || strcmp(versionAttr->value().c_str(), "1.0") != 0) { 351 | std::cerr 352 | << "WARN: The version is different from the one expected by " 353 | "this library or unspecified" 354 | << std::endl; 355 | } 356 | 357 | // Units (required for emissive images) 358 | const Imf::StringAttribute *emissiveUnitsAttr 359 | = exrHeader.findTypedAttribute( 360 | EMISSIVE_UNITS_ATTR); 361 | 362 | if (isEmissive() 363 | && (emissiveUnitsAttr == nullptr 364 | || strcmp(emissiveUnitsAttr->value().c_str(), "W.m^-2.sr^-1") != 0)) { 365 | std::cerr 366 | << "WARN: This unit is not supported or unspecified. We are " 367 | "going to use " 368 | "W.m^-2.sr^-1 instead" 369 | << std::endl; 370 | } 371 | 372 | // Lens transmission data 373 | const Imf::StringAttribute *lensTransmissionAttr 374 | = exrHeader.findTypedAttribute( 375 | LENS_TRANSMISSION_ATTR); 376 | 377 | if (lensTransmissionAttr != nullptr) { 378 | try { 379 | _lensTransmissionSpectra 380 | = SpectrumAttribute(*lensTransmissionAttr); 381 | } catch (SpectrumAttribute::Error &e) { 382 | throw INCORRECT_FORMED_FILE; 383 | } 384 | } 385 | 386 | // Camera spectral response 387 | const Imf::StringAttribute *cameraResponseAttr 388 | = exrHeader.findTypedAttribute( 389 | CAMERA_RESPONSE_ATTR); 390 | 391 | if (cameraResponseAttr != nullptr) { 392 | try { 393 | _cameraReponse = SpectrumAttribute(*cameraResponseAttr); 394 | } catch (SpectrumAttribute::Error &e) { 395 | throw INCORRECT_FORMED_FILE; 396 | } 397 | } 398 | 399 | // Each channel sensitivity 400 | _channelSensitivities.resize(nSpectralBands()); 401 | 402 | for (size_t i = 0; i < wavelengths_nm_S[0].size(); i++) { 403 | const Imf::StringAttribute *filterTransmissionAttr 404 | = exrHeader.findTypedAttribute( 405 | wavelengths_nm_S[0][i].second); 406 | 407 | if (filterTransmissionAttr != nullptr) { 408 | try { 409 | _channelSensitivities[i] 410 | = SpectrumAttribute(*filterTransmissionAttr); 411 | } catch (SpectrumAttribute::Error &e) { 412 | throw INCORRECT_FORMED_FILE; 413 | } 414 | } 415 | } 416 | 417 | // Exposure compensation value 418 | const Imf::FloatAttribute *exposureCompensationAttr 419 | = exrHeader.findTypedAttribute( 420 | EXPOSURE_COMPENSATION_ATTR); 421 | 422 | if (exposureCompensationAttr != nullptr) { 423 | try { 424 | _ev = exposureCompensationAttr->value(); 425 | } catch (std::invalid_argument &e) { 426 | throw INCORRECT_FORMED_FILE; 427 | } 428 | } 429 | 430 | // Polarisation handedness 431 | const Imf::StringAttribute *polarisationHandednessAttr 432 | = exrHeader.findTypedAttribute( 433 | POLARISATION_HANDEDNESS_ATTR); 434 | 435 | if (polarisationHandednessAttr != nullptr) { 436 | if (polarisationHandednessAttr->value() == "left") { 437 | _polarisationHandedness = LEFT_HANDED; 438 | } else if (polarisationHandednessAttr->value() == "right") { 439 | _polarisationHandedness = RIGHT_HANDED; 440 | } else { 441 | throw INCORRECT_FORMED_FILE; 442 | } 443 | } 444 | } 445 | 446 | 447 | void EXRBiSpectralImage::save(const std::string &filename) const 448 | { 449 | Imf::Header exrHeader(width(), height()); 450 | Imf::ChannelList &exrChannels = exrHeader.channels(); 451 | 452 | // --------------------------------------------------------------------- 453 | // Write the pixel data 454 | // --------------------------------------------------------------------- 455 | 456 | // Layout framebuffer 457 | Imf::FrameBuffer exrFrameBuffer; 458 | const Imf::PixelType compType = Imf::FLOAT; 459 | 460 | // Write RGB version 461 | std::vector rgbImage; 462 | getRGBImage(rgbImage); 463 | 464 | const std::array rgbChannels = {"R", "G", "B"}; 465 | const size_t xStrideRGB = sizeof(float) * 3; 466 | const size_t yStrideRGB = xStrideRGB * width(); 467 | 468 | for (size_t c = 0; c < 3; c++) { 469 | char *ptrRGB = (char *)(&rgbImage[c]); 470 | exrChannels.insert(rgbChannels[c], Imf::Channel(compType)); 471 | exrFrameBuffer.insert( 472 | rgbChannels[c], 473 | Imf::Slice(compType, ptrRGB, xStrideRGB, yStrideRGB)); 474 | } 475 | 476 | // Write spectral version 477 | const size_t xStride = sizeof(float) * nSpectralBands(); 478 | const size_t yStride = xStride * width(); 479 | 480 | for (size_t s = 0; s < nStokesComponents(); s++) { 481 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 482 | // Populate channel name 483 | const std::string channelName 484 | = getEmissiveChannelName(s, _wavelengths_nm[wl_idx]); 485 | exrChannels.insert(channelName, Imf::Channel(compType)); 486 | 487 | char *ptrS = (char *)(&_emissivePixelBuffers[s][wl_idx]); 488 | exrFrameBuffer.insert( 489 | channelName, 490 | Imf::Slice(compType, ptrS, xStride, yStride)); 491 | } 492 | } 493 | 494 | if (isReflective()) { 495 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 496 | // Populate channel name 497 | const std::string channelName 498 | = getReflectiveChannelName(_wavelengths_nm[wl_idx]); 499 | exrChannels.insert(channelName, Imf::Channel(compType)); 500 | 501 | char *ptrS = (char *)(&_reflectivePixelBuffer[wl_idx]); 502 | exrFrameBuffer.insert( 503 | channelName, 504 | Imf::Slice(compType, ptrS, xStride, yStride)); 505 | } 506 | 507 | if (isBispectral()) { 508 | // Write the reradiation 509 | const size_t xStrideReradiation 510 | = sizeof(float) * reradiationSize(); 511 | const size_t yStrideReradiation = xStrideReradiation * _width; 512 | 513 | for (size_t rr = 0; rr < reradiationSize(); rr++) { 514 | size_t wlFromIdx, wlToIdx; 515 | wavelengthsIdxFromIdx(rr, wlFromIdx, wlToIdx); 516 | 517 | const std::string channelName = getReradiationChannelName( 518 | _wavelengths_nm[wlFromIdx], 519 | _wavelengths_nm[wlToIdx]); 520 | 521 | exrChannels.insert(channelName, Imf::Channel(compType)); 522 | 523 | char *framebuffer = (char *)(&_reradiation[rr]); 524 | exrFrameBuffer.insert( 525 | channelName, 526 | Imf::Slice( 527 | compType, 528 | framebuffer, 529 | xStrideReradiation, 530 | yStrideReradiation)); 531 | } 532 | } 533 | } 534 | 535 | // --------------------------------------------------------------------- 536 | // Write metadata 537 | // --------------------------------------------------------------------- 538 | 539 | exrHeader.insert(VERSION_ATTR, Imf::StringAttribute("1.0")); 540 | 541 | if (lensTransmission().size() > 0) { 542 | exrHeader.insert( 543 | LENS_TRANSMISSION_ATTR, 544 | lensTransmission().getAttribute()); 545 | } 546 | 547 | if (cameraResponse().size() > 0) { 548 | exrHeader.insert( 549 | CAMERA_RESPONSE_ATTR, 550 | cameraResponse().getAttribute()); 551 | } 552 | 553 | if (channelSensitivities().size() > 0) { 554 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 555 | if (channelSensitivity(wl_idx).size() > 0) { 556 | std::string channelName 557 | = getEmissiveChannelName(0, _wavelengths_nm[wl_idx]); 558 | 559 | exrHeader.insert( 560 | channelName, 561 | channelSensitivity(wl_idx).getAttribute()); 562 | } 563 | } 564 | } 565 | 566 | exrHeader.insert(EXPOSURE_COMPENSATION_ATTR, Imf::FloatAttribute(_ev)); 567 | 568 | // Units 569 | if (isEmissive()) { 570 | exrHeader.insert( 571 | EMISSIVE_UNITS_ATTR, 572 | Imf::StringAttribute("W.m^-2.sr^-1")); 573 | } 574 | 575 | // Polarisation handedness 576 | if (isPolarised()) { 577 | Imf::StringAttribute handednessAtrrValue( 578 | _polarisationHandedness == LEFT_HANDED ? "left" : "right"); 579 | 580 | exrHeader.insert( 581 | POLARISATION_HANDEDNESS_ATTR, 582 | Imf::StringAttribute(handednessAtrrValue)); 583 | } 584 | 585 | Imf::OutputFile exrOut(filename.c_str(), exrHeader); 586 | exrOut.setFrameBuffer(exrFrameBuffer); 587 | exrOut.writePixels(height()); 588 | } 589 | 590 | SpectrumType EXRBiSpectralImage::channelType( 591 | const std::string &channelName, 592 | int & polarisationComponent, 593 | double & wavelength_nm, 594 | double & reradiation_wavelength_nm) 595 | { 596 | const std::string exprRefl = "T"; 597 | const std::string exprStokes = "S([0-3])"; 598 | const std::string exprPola = "((" + exprStokes + ")|" + exprRefl + ")"; 599 | const std::string exprValue = "(\\d*,?\\d*([Ee][+-]?\\d+)?)"; 600 | const std::string exprUnits 601 | = "(Y|Z|E|P|T|G|M|k|h|da|d|c|m|u|n|p)?(m|Hz)"; 602 | 603 | const std::regex exprDiagonal( 604 | "^" + exprPola + "\\." + exprValue + exprUnits + "$"); 605 | const std::regex exprRerad( 606 | "^" + exprRefl + "\\." + exprValue + exprUnits + "\\." + exprValue 607 | + exprUnits + "$"); 608 | 609 | std::smatch matches; 610 | 611 | const bool matchedDiagonal 612 | = std::regex_search(channelName, matches, exprDiagonal); 613 | 614 | if (matchedDiagonal) { 615 | if (matches.size() != 8) { 616 | // Something went wrong with the parsing. This shall not occur. 617 | throw INTERNAL_ERROR; 618 | } 619 | 620 | SpectrumType type; 621 | 622 | switch (matches[1].str()[0]) { 623 | case 'S': 624 | type = SpectrumType::EMISSIVE; 625 | polarisationComponent = std::stoi(matches[3].str()); 626 | if (polarisationComponent > 0) { 627 | type = type | SpectrumType::POLARISED; 628 | } 629 | break; 630 | 631 | case 'T': 632 | type = SpectrumType::REFLECTIVE; 633 | break; 634 | 635 | default: 636 | return SpectrumType::UNDEFINED; 637 | } 638 | 639 | // Get value illumination 640 | std::string centralValueStr(matches[4].str()); 641 | std::replace( 642 | centralValueStr.begin(), 643 | centralValueStr.end(), 644 | ',', 645 | '.'); 646 | const double value = std::stod(centralValueStr); 647 | 648 | wavelength_nm = Util::strToNanometers( 649 | value, // Comma separated floating point value 650 | matches[6].str(), // Unit multiplier 651 | matches[7].str() // Units 652 | ); 653 | 654 | return type; 655 | } 656 | 657 | const bool matchedRerad 658 | = std::regex_search(channelName, matches, exprRerad); 659 | 660 | if (matchedRerad) { 661 | if (matches.size() != 9) { 662 | // Something went wrong with the parsing. This shall not occur. 663 | throw INTERNAL_ERROR; 664 | } 665 | 666 | // Get value illumination 667 | std::string centralValueStrI(matches[1].str()); 668 | std::replace( 669 | centralValueStrI.begin(), 670 | centralValueStrI.end(), 671 | ',', 672 | '.'); 673 | const float value_i = std::stof(centralValueStrI); 674 | 675 | wavelength_nm = Util::strToNanometers( 676 | value_i, 677 | matches[3].str(), // Unit multiplier 678 | matches[4].str() // Units 679 | ); 680 | 681 | // Get value reradiation 682 | std::string centralValueStrO(matches[5].str()); 683 | std::replace( 684 | centralValueStrO.begin(), 685 | centralValueStrO.end(), 686 | ',', 687 | '.'); 688 | const float value_o = std::stof(centralValueStrO); 689 | 690 | reradiation_wavelength_nm = Util::strToNanometers( 691 | value_o, 692 | matches[7].str(), // Unit multiplier 693 | matches[8].str() // Units 694 | ); 695 | 696 | return SpectrumType::BISPECTRAL; 697 | } 698 | 699 | return SpectrumType::UNDEFINED; 700 | } 701 | 702 | 703 | std::string EXRBiSpectralImage::getEmissiveChannelName( 704 | int stokesComponent, double wavelength_nm) 705 | { 706 | assert(stokesComponent < 4); 707 | 708 | std::stringstream b; 709 | std::string wavelengthStr = std::to_string(wavelength_nm); 710 | std::replace(wavelengthStr.begin(), wavelengthStr.end(), '.', ','); 711 | 712 | b << "S" << stokesComponent << "." << wavelengthStr << "nm"; 713 | 714 | const std::string channelName = b.str(); 715 | 716 | #ifndef NDEBUG 717 | int stokesComponentChecked; 718 | double wavelength_nmChecked; 719 | double wavelength_nmCheckedOut; 720 | SpectrumType t = channelType( 721 | channelName, 722 | stokesComponentChecked, 723 | wavelength_nmChecked, 724 | wavelength_nmCheckedOut); 725 | 726 | assert(isEmissiveSpectrum(t)); 727 | assert(stokesComponentChecked == stokesComponent); 728 | assert(wavelength_nmChecked == wavelength_nm); 729 | #endif 730 | 731 | return channelName; 732 | } 733 | 734 | 735 | std::string 736 | EXRBiSpectralImage::getReflectiveChannelName(double wavelength_nm) 737 | { 738 | std::stringstream b; 739 | std::string wavelengthStr = std::to_string(wavelength_nm); 740 | std::replace(wavelengthStr.begin(), wavelengthStr.end(), '.', ','); 741 | 742 | b << "T" 743 | << "." << wavelengthStr << "nm"; 744 | 745 | const std::string channelName = b.str(); 746 | 747 | #ifndef NDEBUG 748 | int stokesComponent; 749 | double wavelength_nmChecked; 750 | double wavelength_nmCheckedOut; 751 | SpectrumType t = channelType( 752 | channelName, 753 | stokesComponent, 754 | wavelength_nmChecked, 755 | wavelength_nmCheckedOut); 756 | 757 | assert(isReflectiveSpectrum(t)); 758 | assert(wavelength_nmChecked == wavelength_nm); 759 | #endif 760 | 761 | return channelName; 762 | } 763 | 764 | 765 | std::string EXRBiSpectralImage::getReradiationChannelName( 766 | double wavelength_nm, double reradiation_wavelength_nm) 767 | { 768 | std::string reradWavelengthStr 769 | = std::to_string(reradiation_wavelength_nm); 770 | std::replace( 771 | reradWavelengthStr.begin(), 772 | reradWavelengthStr.end(), 773 | '.', 774 | ','); 775 | 776 | std::stringstream b; 777 | b << getReflectiveChannelName(wavelength_nm) << '.' 778 | << reradWavelengthStr << "nm"; 779 | 780 | const std::string channelName = b.str(); 781 | 782 | #ifndef NDEBUG 783 | int stokesComponent; 784 | double wavelength_nmChecked; 785 | double reradiation_wavelength_nmChecked; 786 | 787 | SpectrumType t = channelType( 788 | channelName, 789 | stokesComponent, 790 | wavelength_nmChecked, 791 | reradiation_wavelength_nmChecked); 792 | 793 | assert(isBispectralSpectrum(t)); 794 | assert(wavelength_nmChecked == wavelength_nm); 795 | assert(reradiation_wavelength_nmChecked == reradiation_wavelength_nm); 796 | #endif 797 | 798 | return channelName; 799 | } 800 | 801 | } // namespace SEXR 802 | -------------------------------------------------------------------------------- /lib/EXRSpectralImage.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | #include "Util.h" 39 | 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | 51 | namespace SEXR 52 | { 53 | EXRSpectralImage::EXRSpectralImage( 54 | size_t width, 55 | size_t height, 56 | const std::vector &wavelengths_nm, 57 | SpectrumType type, 58 | PolarisationHandedness handedness) 59 | : SpectralImage(width, height, wavelengths_nm, type, handedness) 60 | {} 61 | 62 | 63 | EXRSpectralImage::EXRSpectralImage(const std::string &filename) 64 | : SpectralImage() 65 | { 66 | Imf::InputFile exrIn(filename.c_str()); 67 | const Imf::Header & exrHeader = exrIn.header(); 68 | const Imath::Box2i &exrDataWindow = exrHeader.dataWindow(); 69 | 70 | _width = exrDataWindow.max.x - exrDataWindow.min.x + 1; 71 | _height = exrDataWindow.max.y - exrDataWindow.min.y + 1; 72 | _spectrumType = UNDEFINED; 73 | 74 | // --------------------------------------------------------------------- 75 | // Determine channels' position 76 | // --------------------------------------------------------------------- 77 | 78 | const Imf::ChannelList &exrChannels = exrHeader.channels(); 79 | 80 | std::array>, 4> 81 | wavelengths_nm_S; 82 | std::vector> wavelengths_nm_reflective; 83 | 84 | for (Imf::ChannelList::ConstIterator channel = exrChannels.begin(); 85 | channel != exrChannels.end(); 86 | channel++) { 87 | // Check if the channel is a spectral one 88 | int polarisationComponent; 89 | double wavelength_nm; 90 | SpectrumType spectralChannel = channelType( 91 | channel.name(), 92 | polarisationComponent, 93 | wavelength_nm); 94 | 95 | if (spectralChannel != SpectrumType::UNDEFINED) { 96 | _spectrumType = _spectrumType | spectralChannel; 97 | 98 | if (isEmissiveSpectrum(spectralChannel)) { 99 | wavelengths_nm_S[polarisationComponent].push_back( 100 | std::make_pair(wavelength_nm, channel.name())); 101 | } else if (isReflectiveSpectrum(spectralChannel)) { 102 | wavelengths_nm_reflective.push_back( 103 | std::make_pair(wavelength_nm, channel.name())); 104 | } 105 | } 106 | } 107 | 108 | // Sort by ascending wavelengths 109 | for (size_t s = 0; s < nStokesComponents(); s++) { 110 | std::sort(wavelengths_nm_S[s].begin(), wavelengths_nm_S[s].end()); 111 | } 112 | 113 | if (isReflective()) { 114 | std::sort( 115 | wavelengths_nm_reflective.begin(), 116 | wavelengths_nm_reflective.end()); 117 | } 118 | 119 | // --------------------------------------------------------------------- 120 | // Sanity check 121 | // --------------------------------------------------------------------- 122 | 123 | if (_spectrumType == SpectrumType::UNDEFINED) { 124 | // Probably an RGB EXR, not our job to handle it 125 | throw INCORRECT_FORMED_FILE; 126 | } 127 | 128 | if (isEmissive()) { 129 | // Check we have the same wavelength for each Stokes component 130 | // Wavelength vectors must be of the same size 131 | const float base_size_emissive = wavelengths_nm_S[0].size(); 132 | 133 | for (size_t s = 1; s < nStokesComponents(); s++) { 134 | if (wavelengths_nm_S[s].size() != base_size_emissive) { 135 | throw INCORRECT_FORMED_FILE; 136 | } 137 | 138 | // Wavelengths must correspond 139 | for (size_t wl_idx = 0; wl_idx < base_size_emissive; wl_idx++) { 140 | if ( 141 | wavelengths_nm_S[s][wl_idx].first 142 | != wavelengths_nm_S[0][wl_idx].first) { 143 | throw INCORRECT_FORMED_FILE; 144 | } 145 | } 146 | } 147 | } 148 | 149 | // If both reflective and emissive, we need to perform a last 150 | // sanity check 151 | if (isEmissive() && isReflective()) { 152 | const size_t n_emissive_wavelengths = wavelengths_nm_S[0].size(); 153 | const size_t n_reflective_wavelengths 154 | = wavelengths_nm_reflective.size(); 155 | 156 | if (n_emissive_wavelengths != n_reflective_wavelengths) 157 | throw INCORRECT_FORMED_FILE; 158 | 159 | for (size_t wl_idx = 0; wl_idx < n_emissive_wavelengths; wl_idx++) { 160 | if ( 161 | wavelengths_nm_S[0][wl_idx] 162 | != wavelengths_nm_reflective[wl_idx]) 163 | throw INCORRECT_FORMED_FILE; 164 | } 165 | } 166 | 167 | // --------------------------------------------------------------------- 168 | // Allocate memory 169 | // --------------------------------------------------------------------- 170 | 171 | // Now, we can populate the local wavelength vector 172 | if (isEmissive()) { 173 | _wavelengths_nm.reserve(wavelengths_nm_S[0].size()); 174 | 175 | for (const auto &wl_index : wavelengths_nm_S[0]) { 176 | _wavelengths_nm.push_back(wl_index.first); 177 | } 178 | } else { 179 | _wavelengths_nm.reserve(wavelengths_nm_reflective.size()); 180 | 181 | for (const auto &wl_index : wavelengths_nm_reflective) { 182 | _wavelengths_nm.push_back(wl_index.first); 183 | } 184 | } 185 | 186 | // We allocate pixel buffers memory 187 | for (size_t s = 0; s < nStokesComponents(); s++) { 188 | _emissivePixelBuffers[s].resize( 189 | nSpectralBands() * width() * height()); 190 | } 191 | 192 | if (isReflective()) { 193 | _reflectivePixelBuffer.resize( 194 | nSpectralBands() * width() * height()); 195 | } 196 | 197 | // --------------------------------------------------------------------- 198 | // Read the pixel data 199 | // --------------------------------------------------------------------- 200 | 201 | Imf::FrameBuffer exrFrameBuffer; 202 | 203 | const Imf::PixelType compType = Imf::FLOAT; 204 | const size_t xStride = sizeof(float) * nSpectralBands(); 205 | const size_t yStride = xStride * _width; 206 | 207 | for (size_t s = 0; s < nStokesComponents(); s++) { 208 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 209 | char * ptrS = (char *)(&_emissivePixelBuffers[s][wl_idx]); 210 | Imf::Slice slice = Imf::Slice::Make( 211 | compType, 212 | ptrS, 213 | exrDataWindow, 214 | xStride, 215 | yStride); 216 | 217 | exrFrameBuffer.insert( 218 | wavelengths_nm_S[s][wl_idx].second, 219 | slice); 220 | } 221 | } 222 | 223 | if (isReflective()) { 224 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 225 | char * ptrS = (char *)(&_reflectivePixelBuffer[wl_idx]); 226 | Imf::Slice slice = Imf::Slice::Make( 227 | compType, 228 | ptrS, 229 | exrDataWindow, 230 | xStride, 231 | yStride); 232 | 233 | exrFrameBuffer.insert( 234 | wavelengths_nm_reflective[wl_idx].second, 235 | slice); 236 | } 237 | } 238 | 239 | exrIn.setFrameBuffer(exrFrameBuffer); 240 | exrIn.readPixels(exrDataWindow.min.y, exrDataWindow.max.y); 241 | 242 | // --------------------------------------------------------------------- 243 | // Read metadata 244 | // --------------------------------------------------------------------- 245 | 246 | // Check if the version match 247 | const Imf::StringAttribute *versionAttr 248 | = exrHeader.findTypedAttribute(VERSION_ATTR); 249 | 250 | if ( 251 | versionAttr == nullptr 252 | || strcmp(versionAttr->value().c_str(), "1.0") != 0) { 253 | std::cerr 254 | << "WARN: The version is different from the one expected by " 255 | "this library or unspecified" 256 | << std::endl; 257 | } 258 | 259 | // Units (required for emissive images) 260 | const Imf::StringAttribute *emissiveUnitsAttr 261 | = exrHeader.findTypedAttribute( 262 | EMISSIVE_UNITS_ATTR); 263 | 264 | if (isEmissive() 265 | && (emissiveUnitsAttr == nullptr 266 | || strcmp(emissiveUnitsAttr->value().c_str(), "W.m^-2.sr^-1") != 0)) { 267 | std::cerr 268 | << "WARN: This unit is not supported or unspecified. We are " 269 | "going to use " 270 | "W.m^-2.sr^-1 instead" 271 | << std::endl; 272 | } 273 | 274 | // Lens transmission data 275 | const Imf::StringAttribute *lensTransmissionAttr 276 | = exrHeader.findTypedAttribute( 277 | LENS_TRANSMISSION_ATTR); 278 | 279 | if (lensTransmissionAttr != nullptr) { 280 | try { 281 | _lensTransmissionSpectra 282 | = SpectrumAttribute(*lensTransmissionAttr); 283 | } catch (SpectrumAttribute::Error &e) { 284 | throw INCORRECT_FORMED_FILE; 285 | } 286 | } 287 | 288 | // Camera spectral response 289 | const Imf::StringAttribute *cameraResponseAttr 290 | = exrHeader.findTypedAttribute( 291 | CAMERA_RESPONSE_ATTR); 292 | 293 | if (cameraResponseAttr != nullptr) { 294 | try { 295 | _cameraReponse = SpectrumAttribute(*cameraResponseAttr); 296 | } catch (SpectrumAttribute::Error &e) { 297 | throw INCORRECT_FORMED_FILE; 298 | } 299 | } 300 | 301 | // Each channel sensitivity 302 | _channelSensitivities.resize(nSpectralBands()); 303 | 304 | for (size_t i = 0; i < wavelengths_nm_S[0].size(); i++) { 305 | const Imf::StringAttribute *filterTransmissionAttr 306 | = exrHeader.findTypedAttribute( 307 | wavelengths_nm_S[0][i].second); 308 | 309 | if (filterTransmissionAttr != nullptr) { 310 | try { 311 | _channelSensitivities[i] 312 | = SpectrumAttribute(*filterTransmissionAttr); 313 | } catch (SpectrumAttribute::Error &e) { 314 | throw INCORRECT_FORMED_FILE; 315 | } 316 | } 317 | } 318 | 319 | // Exposure compensation value 320 | const Imf::FloatAttribute *exposureCompensationAttr 321 | = exrHeader.findTypedAttribute( 322 | EXPOSURE_COMPENSATION_ATTR); 323 | 324 | if (exposureCompensationAttr != nullptr) { 325 | try { 326 | _ev = exposureCompensationAttr->value(); 327 | } catch (std::invalid_argument &e) { 328 | throw INCORRECT_FORMED_FILE; 329 | } 330 | } 331 | 332 | // Polarisation handedness 333 | const Imf::StringAttribute *polarisationHandednessAttr 334 | = exrHeader.findTypedAttribute( 335 | POLARISATION_HANDEDNESS_ATTR); 336 | 337 | if (polarisationHandednessAttr != nullptr) { 338 | if (polarisationHandednessAttr->value() == "left") { 339 | _polarisationHandedness = LEFT_HANDED; 340 | } else if (polarisationHandednessAttr->value() == "right") { 341 | _polarisationHandedness = RIGHT_HANDED; 342 | } else { 343 | throw INCORRECT_FORMED_FILE; 344 | } 345 | } 346 | } 347 | 348 | 349 | void EXRSpectralImage::save(const std::string &filename) const 350 | { 351 | Imf::Header exrHeader(width(), height()); 352 | Imf::ChannelList &exrChannels = exrHeader.channels(); 353 | 354 | // --------------------------------------------------------------------- 355 | // Write the pixel data 356 | // --------------------------------------------------------------------- 357 | 358 | // Layout framebuffer 359 | Imf::FrameBuffer exrFrameBuffer; 360 | const Imf::PixelType compType = Imf::FLOAT; 361 | 362 | // Write RGB version 363 | std::vector rgbImage; 364 | getRGBImage(rgbImage); 365 | 366 | const std::array rgbChannels = {"R", "G", "B"}; 367 | const size_t xStrideRGB = sizeof(float) * 3; 368 | const size_t yStrideRGB = xStrideRGB * width(); 369 | 370 | for (size_t c = 0; c < 3; c++) { 371 | char *ptrRGB = (char *)(&rgbImage[c]); 372 | 373 | exrChannels.insert(rgbChannels[c], Imf::Channel(compType)); 374 | exrFrameBuffer.insert( 375 | rgbChannels[c], 376 | Imf::Slice(compType, ptrRGB, xStrideRGB, yStrideRGB)); 377 | } 378 | 379 | // Write spectral version 380 | const size_t xStride = sizeof(float) * nSpectralBands(); 381 | const size_t yStride = xStride * width(); 382 | 383 | for (size_t s = 0; s < nStokesComponents(); s++) { 384 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 385 | // Populate channel name 386 | const std::string channelName 387 | = getEmissiveChannelName(s, _wavelengths_nm[wl_idx]); 388 | exrChannels.insert(channelName, Imf::Channel(compType)); 389 | 390 | char *ptrS = (char *)(&_emissivePixelBuffers[s][wl_idx]); 391 | exrFrameBuffer.insert( 392 | channelName, 393 | Imf::Slice(compType, ptrS, xStride, yStride)); 394 | } 395 | } 396 | 397 | if (isReflective()) { 398 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 399 | // Populate channel name 400 | const std::string channelName 401 | = getReflectiveChannelName(_wavelengths_nm[wl_idx]); 402 | exrChannels.insert(channelName, Imf::Channel(compType)); 403 | 404 | char *ptrS = (char *)(&_reflectivePixelBuffer[wl_idx]); 405 | exrFrameBuffer.insert( 406 | channelName, 407 | Imf::Slice(compType, ptrS, xStride, yStride)); 408 | } 409 | } 410 | 411 | // --------------------------------------------------------------------- 412 | // Write metadata 413 | // --------------------------------------------------------------------- 414 | 415 | exrHeader.insert(VERSION_ATTR, Imf::StringAttribute("1.0")); 416 | 417 | if (lensTransmission().size() > 0) { 418 | exrHeader.insert( 419 | LENS_TRANSMISSION_ATTR, 420 | lensTransmission().getAttribute()); 421 | } 422 | 423 | if (cameraResponse().size() > 0) { 424 | exrHeader.insert( 425 | CAMERA_RESPONSE_ATTR, 426 | cameraResponse().getAttribute()); 427 | } 428 | 429 | if (channelSensitivities().size() > 0) { 430 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 431 | if (channelSensitivity(wl_idx).size() > 0) { 432 | std::string channelName 433 | = getEmissiveChannelName(0, _wavelengths_nm[wl_idx]); 434 | 435 | exrHeader.insert( 436 | channelName, 437 | channelSensitivity(wl_idx).getAttribute()); 438 | } 439 | } 440 | } 441 | 442 | exrHeader.insert(EXPOSURE_COMPENSATION_ATTR, Imf::FloatAttribute(_ev)); 443 | 444 | // Units 445 | if (isEmissive()) { 446 | exrHeader.insert( 447 | EMISSIVE_UNITS_ATTR, 448 | Imf::StringAttribute("W.m^-2.sr^-1")); 449 | } 450 | 451 | // Polarisation handedness 452 | if (isPolarised()) { 453 | Imf::StringAttribute handednessAtrrValue( 454 | _polarisationHandedness == LEFT_HANDED ? "left" : "right"); 455 | 456 | exrHeader.insert( 457 | POLARISATION_HANDEDNESS_ATTR, 458 | Imf::StringAttribute(handednessAtrrValue)); 459 | } 460 | 461 | // --------------------------------------------------------------------- 462 | // Write file 463 | // --------------------------------------------------------------------- 464 | 465 | Imf::OutputFile exrOut(filename.c_str(), exrHeader); 466 | exrOut.setFrameBuffer(exrFrameBuffer); 467 | exrOut.writePixels(height()); 468 | } 469 | 470 | 471 | SpectrumType EXRSpectralImage::channelType( 472 | const std::string &channelName, 473 | int & polarisationComponent, 474 | double & wavelength_nm) 475 | { 476 | const std::regex expr( 477 | "^((S([0-3]))|T)\\.(\\d*,?\\d*([Ee][+-]?\\d+)?)(Y|Z|E|P|T|G|M|k|h|" 478 | "da|d|c|m|u|n|p)?(m|Hz)$"); 479 | std::smatch matches; 480 | 481 | const bool matched = std::regex_search(channelName, matches, expr); 482 | 483 | SpectrumType channelType = SpectrumType::UNDEFINED; 484 | 485 | if (matched) { 486 | if (matches.size() != 8) { 487 | // Something went wrong with the parsing. This shall not occur. 488 | throw INTERNAL_ERROR; 489 | } 490 | 491 | switch (matches[1].str()[0]) { 492 | case 'S': 493 | channelType = SpectrumType::EMISSIVE; 494 | polarisationComponent = std::stoi(matches[3].str()); 495 | if (polarisationComponent > 0) { 496 | channelType = channelType | SpectrumType::POLARISED; 497 | } 498 | break; 499 | 500 | case 'T': 501 | channelType = SpectrumType::REFLECTIVE; 502 | break; 503 | 504 | default: 505 | return SpectrumType::UNDEFINED; 506 | } 507 | 508 | // Get value 509 | std::string centralValueStr(matches[4].str()); 510 | std::replace( 511 | centralValueStr.begin(), 512 | centralValueStr.end(), 513 | ',', 514 | '.'); 515 | const double value = std::stod(centralValueStr); 516 | 517 | // Convert to nanometers 518 | const std::string prefix = matches[6].str(); 519 | const std::string units = matches[7].str(); 520 | 521 | try { 522 | wavelength_nm = Util::strToNanometers(value, prefix, units); 523 | } catch (std::out_of_range &exception) { 524 | // Unknown unit or multiplier 525 | // Something went wrong with the parsing. This shall not occur. 526 | throw INTERNAL_ERROR; 527 | } 528 | } 529 | 530 | return channelType; 531 | } 532 | 533 | 534 | std::string EXRSpectralImage::getEmissiveChannelName( 535 | int stokesComponent, double wavelength_nm) 536 | { 537 | assert(stokesComponent < 4); 538 | 539 | std::stringstream b; 540 | std::string wavelengthStr = std::to_string(wavelength_nm); 541 | std::replace(wavelengthStr.begin(), wavelengthStr.end(), '.', ','); 542 | 543 | b << "S" << stokesComponent << "." << wavelengthStr << "nm"; 544 | 545 | const std::string channelName = b.str(); 546 | 547 | #ifndef NDEBUG 548 | int polarisationComponentChecked; 549 | double wavelength_nmChecked; 550 | 551 | SpectrumType t = channelType( 552 | channelName, 553 | polarisationComponentChecked, 554 | wavelength_nmChecked); 555 | 556 | assert(isEmissiveSpectrum(t)); 557 | assert(polarisationComponentChecked == stokesComponent); 558 | assert(wavelength_nmChecked == wavelength_nm); 559 | #endif 560 | 561 | return channelName; 562 | } 563 | 564 | 565 | std::string EXRSpectralImage::getReflectiveChannelName(double wavelength_nm) 566 | { 567 | std::stringstream b; 568 | std::string wavelengthStr = std::to_string(wavelength_nm); 569 | std::replace(wavelengthStr.begin(), wavelengthStr.end(), '.', ','); 570 | 571 | b << "T" 572 | << "." << wavelengthStr << "nm"; 573 | 574 | const std::string channelName = b.str(); 575 | 576 | #ifndef NDEBUG 577 | int polarisationComponentChecked; 578 | double wavelength_nmChecked; 579 | 580 | SpectrumType t = channelType( 581 | channelName, 582 | polarisationComponentChecked, 583 | wavelength_nmChecked); 584 | 585 | assert(isReflectiveSpectrum(t)); 586 | assert(wavelength_nmChecked == wavelength_nm); 587 | #endif 588 | 589 | return channelName; 590 | } 591 | 592 | } // namespace SEXR 593 | -------------------------------------------------------------------------------- /lib/EXRSpectralImage.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_PREFIX@ 3 | libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ 4 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 5 | 6 | Name: @PROJECT_NAME@ 7 | Description: @PROJECT_DESCRIPTION@ 8 | Version: @PROJECT_VERSION@ 9 | 10 | Requires: 11 | Libs: -L${libdir} -lEXRSpectralImage 12 | Cflags: -I${includedir} -------------------------------------------------------------------------------- /lib/SpectralImage.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | #include 45 | #include 46 | #include 47 | 48 | #include "SpectrumConverter.h" 49 | 50 | namespace SEXR 51 | { 52 | SpectralImage::SpectralImage( 53 | size_t width, 54 | size_t height, 55 | const std::vector &wavelengths_nm, 56 | SpectrumType type, 57 | PolarisationHandedness handedness) 58 | : _width(width) 59 | , _height(height) 60 | , _ev(0) 61 | , _wavelengths_nm(wavelengths_nm) 62 | , _spectrumType(type) 63 | , _polarisationHandedness(handedness) 64 | { 65 | const size_t buffSize = nSpectralBands() * _width * _height; 66 | 67 | for (size_t s = 0; s < nStokesComponents(); s++) { 68 | _emissivePixelBuffers[s].resize(buffSize); 69 | } 70 | 71 | if (isReflective()) { 72 | _reflectivePixelBuffer.resize(buffSize); 73 | } 74 | 75 | _channelSensitivities.resize(nSpectralBands()); 76 | } 77 | 78 | 79 | void SpectralImage::exportChannels(const std::string &path) const 80 | { 81 | // Utility function to create an EXR from a monochromatic buffer 82 | std::function writeEXR 83 | = [this](const std::string &filename, const float *buffer) { 84 | const size_t xStride = sizeof(float) * nSpectralBands(); 85 | const size_t yStride = xStride * width(); 86 | 87 | Imf::Header exrHeader(width(), height()); 88 | Imf::ChannelList &exrChannels = exrHeader.channels(); 89 | Imf::FrameBuffer exrFrameBuffer; 90 | 91 | exrChannels.insert("Y", Imf::Channel(Imf::FLOAT)); 92 | exrFrameBuffer.insert( 93 | "Y", 94 | Imf::Slice(Imf::FLOAT, (char *)(buffer), xStride, yStride)); 95 | 96 | Imf::OutputFile exrOut(filename.c_str(), exrHeader); 97 | exrOut.setFrameBuffer(exrFrameBuffer); 98 | exrOut.writePixels(height()); 99 | }; 100 | 101 | // Export the emissive part 102 | for (size_t s = 0; s < nStokesComponents(); s++) { 103 | std::stringstream filePrefix; 104 | 105 | filePrefix << "S" << s; 106 | 107 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 108 | const float & wavelength = _wavelengths_nm[wl_idx]; 109 | std::stringstream filepath; 110 | filepath << path << "/" << filePrefix.str() << " - " 111 | << wavelength << "nm.exr"; 112 | 113 | writeEXR(filepath.str(), &_emissivePixelBuffers[s][wl_idx]); 114 | } 115 | } 116 | 117 | // Export the reflective part 118 | if (isReflective()) { 119 | for (size_t wl_idx = 0; wl_idx < nSpectralBands(); wl_idx++) { 120 | const float & wavelength = _wavelengths_nm[wl_idx]; 121 | std::stringstream filepath; 122 | filepath << path << "/T - " << wavelength << "nm.exr"; 123 | 124 | writeEXR(filepath.str(), &_reflectivePixelBuffer[wl_idx]); 125 | } 126 | } 127 | } 128 | 129 | 130 | void SpectralImage::getRGBImage(std::vector &rgbImage) const 131 | { 132 | rgbImage.resize(3 * width() * height()); 133 | SpectrumConverter sc(isEmissive()); 134 | 135 | std::array rgb; 136 | 137 | if (isEmissive() && isReflective()) { 138 | for (size_t i = 0; i < width() * height(); i++) { 139 | sc.spectraToRGB( 140 | _wavelengths_nm, 141 | &_reflectivePixelBuffer[nSpectralBands() * i], 142 | &_emissivePixelBuffers[0][nSpectralBands() * i], 143 | rgb); 144 | 145 | memcpy(&rgbImage[3 * i], &rgb[0], 3 * sizeof(float)); 146 | } 147 | } else if (isEmissive()) { 148 | for (size_t i = 0; i < width() * height(); i++) { 149 | sc.spectrumToRGB( 150 | _wavelengths_nm, 151 | &_emissivePixelBuffers[0][nSpectralBands() * i], 152 | rgb); 153 | 154 | memcpy(&rgbImage[3 * i], &rgb[0], 3 * sizeof(float)); 155 | } 156 | } else if (isReflective()) { 157 | for (size_t i = 0; i < width() * height(); i++) { 158 | sc.spectrumToRGB( 159 | _wavelengths_nm, 160 | &_reflectivePixelBuffer[nSpectralBands() * i], 161 | rgb); 162 | 163 | memcpy(&rgbImage[3 * i], &rgb[0], 3 * sizeof(float)); 164 | } 165 | } 166 | 167 | // Exposure compensation 168 | for (size_t i = 0; i < width() * height(); i++) { 169 | for (size_t c = 0; c < 3; c++) { 170 | rgbImage[3 * i + c] *= std::pow(2.F, _ev); 171 | } 172 | } 173 | } 174 | 175 | 176 | void SpectralImage::setCameraResponse( 177 | const std::vector &wavelengths_nm, 178 | const std::vector &values) 179 | { 180 | assert(wavelengths_nm.size() == values.size()); 181 | 182 | _cameraReponse = SpectrumAttribute(wavelengths_nm, values); 183 | } 184 | 185 | 186 | const SpectrumAttribute &SpectralImage::cameraResponse() const 187 | { 188 | return _cameraReponse; 189 | } 190 | 191 | 192 | void SpectralImage::setLensTransmission( 193 | const std::vector &wavelengths_nm, 194 | const std::vector &values) 195 | { 196 | assert(wavelengths_nm.size() == values.size()); 197 | 198 | _lensTransmissionSpectra = SpectrumAttribute(wavelengths_nm, values); 199 | } 200 | 201 | 202 | const SpectrumAttribute &SpectralImage::lensTransmission() const 203 | { 204 | return _lensTransmissionSpectra; 205 | } 206 | 207 | 208 | void SpectralImage::setChannelSensitivity( 209 | size_t wl_idx, 210 | const std::vector &wavelengths_nm, 211 | const std::vector &values) 212 | { 213 | assert(wl_idx < _emissivePixelBuffers[0].size()); 214 | assert(wavelengths_nm.size() == values.size()); 215 | 216 | _channelSensitivities[wl_idx] 217 | = SpectrumAttribute(wavelengths_nm, values); 218 | } 219 | 220 | 221 | const std::vector & 222 | SpectralImage::channelSensitivities() const 223 | { 224 | return _channelSensitivities; 225 | } 226 | 227 | 228 | const SpectrumAttribute & 229 | SpectralImage::channelSensitivity(size_t wl_idx) const 230 | { 231 | assert(wl_idx < _channelSensitivities.size()); 232 | 233 | return _channelSensitivities[wl_idx]; 234 | } 235 | 236 | 237 | void SpectralImage::setExposureCompensationValue(float ev) { _ev = ev; } 238 | 239 | 240 | const float &SpectralImage::exposureCompensationValue() const 241 | { 242 | return _ev; 243 | } 244 | 245 | 246 | // Access the emissive part 247 | 248 | float &SpectralImage::emissive( 249 | size_t x, size_t y, size_t wavelength_idx, size_t stokesComponent) 250 | { 251 | assert(x < width()); 252 | assert(y < height()); 253 | assert(wavelength_idx < nSpectralBands()); 254 | assert(isEmissive()); 255 | assert(stokesComponent < nStokesComponents()); 256 | 257 | return _emissivePixelBuffers[stokesComponent] 258 | [nSpectralBands() * (y * width() + x) 259 | + wavelength_idx]; 260 | } 261 | 262 | 263 | const float &SpectralImage::emissive( 264 | size_t x, size_t y, size_t wavelength_idx, size_t stokesComponent) const 265 | { 266 | assert(x < width()); 267 | assert(y < height()); 268 | assert(wavelength_idx < nSpectralBands()); 269 | assert(isEmissive()); 270 | assert(stokesComponent < nStokesComponents()); 271 | 272 | return _emissivePixelBuffers[stokesComponent] 273 | [nSpectralBands() * (y * width() + x) 274 | + wavelength_idx]; 275 | } 276 | 277 | // Access the reflective part 278 | 279 | float &SpectralImage::reflective(size_t x, size_t y, size_t wavelength_idx) 280 | { 281 | assert(x < width()); 282 | assert(y < height()); 283 | assert(wavelength_idx < nSpectralBands()); 284 | assert(isReflective()); 285 | 286 | return _reflectivePixelBuffer 287 | [nSpectralBands() * (y * width() + x) + wavelength_idx]; 288 | } 289 | 290 | 291 | const float & 292 | SpectralImage::reflective(size_t x, size_t y, size_t wavelength_idx) const 293 | { 294 | assert(x < width()); 295 | assert(y < height()); 296 | assert(wavelength_idx < nSpectralBands()); 297 | assert(isReflective()); 298 | 299 | return _reflectivePixelBuffer 300 | [nSpectralBands() * (y * width() + x) + wavelength_idx]; 301 | } 302 | 303 | 304 | float SpectralImage::getEmissiveValue( 305 | size_t x, size_t y, size_t wavelength_idx, size_t stokesComponent) const 306 | { 307 | if (isEmissive()) { 308 | return emissive(x, y, wavelength_idx, stokesComponent); 309 | } 310 | 311 | return 0.F; 312 | } 313 | 314 | 315 | float SpectralImage::getReflectiveValue( 316 | size_t x, size_t y, size_t wavelength_idx) const 317 | { 318 | if (isReflective()) { 319 | return reflective(x, y, wavelength_idx); 320 | } 321 | 322 | return 0.F; 323 | } 324 | 325 | 326 | const float &SpectralImage::wavelength_nm(size_t wl_idx) const 327 | { 328 | assert(wl_idx < _wavelengths_nm.size()); 329 | 330 | return _wavelengths_nm[wl_idx]; 331 | } 332 | 333 | 334 | size_t SpectralImage::width() const { return _width; } 335 | size_t SpectralImage::height() const { return _height; } 336 | size_t SpectralImage::nSpectralBands() const 337 | { 338 | return _wavelengths_nm.size(); 339 | } 340 | 341 | 342 | size_t SpectralImage::nStokesComponents() const 343 | { 344 | if (isEmissive()) { 345 | if (isPolarised()) { 346 | return 4; 347 | } else { 348 | return 1; 349 | } 350 | } 351 | 352 | return 0; 353 | } 354 | 355 | 356 | bool SpectralImage::isPolarised() const 357 | { 358 | return isPolarisedSpectrum(_spectrumType); 359 | } 360 | 361 | 362 | bool SpectralImage::isEmissive() const 363 | { 364 | return isEmissiveSpectrum(_spectrumType); 365 | } 366 | 367 | 368 | bool SpectralImage::isReflective() const 369 | { 370 | return isReflectiveSpectrum(_spectrumType); 371 | } 372 | 373 | 374 | bool SpectralImage::isBispectral() const 375 | { 376 | return isBispectralSpectrum(_spectrumType); 377 | } 378 | 379 | 380 | SpectrumType SpectralImage::type() const { return _spectrumType; } 381 | 382 | 383 | } // namespace SEXR 384 | -------------------------------------------------------------------------------- /lib/SpectrumAttribute.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include 38 | 39 | #include "Util.h" 40 | 41 | #include 42 | #include 43 | 44 | namespace SEXR 45 | { 46 | SpectrumAttribute::SpectrumAttribute() {} 47 | 48 | 49 | SpectrumAttribute::SpectrumAttribute( 50 | std::vector wavelengths_nm, std::vector values) 51 | : _wavelengths_nm(wavelengths_nm) 52 | , _values(values) 53 | { 54 | if (_wavelengths_nm.size() != _values.size()) { 55 | throw NOT_SAME_VECTOR_SIZE; 56 | } 57 | } 58 | 59 | 60 | SpectrumAttribute::SpectrumAttribute( 61 | const Imf::StringAttribute &attributeValue) 62 | { 63 | std::string attributeValueStr = attributeValue.value(); 64 | 65 | const std::string floatRegex = "(\\d*\\.?\\d*([Ee][+-]?\\d+)?)"; 66 | const std::string units = "(Y|Z|E|P|T|G|M|k|h|da|d|c|m|u|n|p)?(m|Hz)"; 67 | 68 | const std::regex e(floatRegex + units + ":" + floatRegex + ";"); 69 | 70 | 71 | std::vector> wavelengthValues; 72 | 73 | for (std::smatch matches; 74 | std::regex_search(attributeValueStr, matches, e); 75 | attributeValueStr = matches.suffix()) { 76 | if (matches.size() != 7) { 77 | throw PARSING_ERROR; 78 | } 79 | 80 | const std::string &waveValue = matches[1]; 81 | const std::string &prefix = matches[3]; 82 | const std::string &units = matches[4]; 83 | const std::string &value = matches[5]; 84 | 85 | wavelengthValues.push_back(std::make_pair( 86 | Util::strToNanometers(std::stof(waveValue), prefix, units), 87 | std::stof(value))); 88 | } 89 | 90 | // Sort ascending values 91 | std::sort(wavelengthValues.begin(), wavelengthValues.end()); 92 | 93 | // Populate class data 94 | _wavelengths_nm.reserve(wavelengthValues.size()); 95 | _values.reserve(wavelengthValues.size()); 96 | 97 | for (const auto &wavelengthValue : wavelengthValues) { 98 | _wavelengths_nm.push_back(wavelengthValue.first); 99 | _values.push_back(wavelengthValue.second); 100 | } 101 | } 102 | 103 | 104 | Imf::StringAttribute SpectrumAttribute::getAttribute() const 105 | { 106 | std::stringstream attrValue; 107 | 108 | for (size_t i = 0; i < _wavelengths_nm.size(); i++) { 109 | attrValue << _wavelengths_nm[i] << "nm:" << _values[i] << ";"; 110 | } 111 | 112 | return Imf::StringAttribute(attrValue.str()); 113 | } 114 | 115 | } // namespace SEXR 116 | -------------------------------------------------------------------------------- /lib/SpectrumConverter.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #include "SpectrumConverter.h" 38 | #include "Util.h" 39 | #include "spectrum_data.h" 40 | 41 | #include 42 | #include 43 | #include 44 | #include 45 | 46 | namespace SEXR 47 | { 48 | SpectrumConverter::SpectrumConverter(bool emissiveSpectrum) 49 | : _emissiveSpectrum(emissiveSpectrum) 50 | , _cmfFirstWavelength_nm(CIE1931_2DEG_FIRST_WAVELENGTH_NM) 51 | { 52 | if (!_emissiveSpectrum) { 53 | _illuminantFirstWavelenght_nm = D_65_FIRST_WAVELENGTH_NM; 54 | _illuminantSPD 55 | = std::vector(std::begin(D_65_SPD), std::end(D_65_SPD)); 56 | } 57 | 58 | _xyzCmfs[0] = std::vector( 59 | std::begin(CIE1931_2DEG_X), 60 | std::end(CIE1931_2DEG_X)); 61 | _xyzCmfs[1] = std::vector( 62 | std::begin(CIE1931_2DEG_Y), 63 | std::end(CIE1931_2DEG_Y)); 64 | _xyzCmfs[2] = std::vector( 65 | std::begin(CIE1931_2DEG_Z), 66 | std::end(CIE1931_2DEG_Z)); 67 | 68 | memcpy(&_xyzToRgb[0], XYZ_TO_SRGB_D65_MATRIX, 9 * sizeof(float)); 69 | } 70 | 71 | 72 | SpectrumConverter::SpectrumConverter( 73 | const float & cmfFirstWavelength_nm, 74 | const std::array, 3> &xyzCmfs, 75 | const std::array xyzToRgb) 76 | : _cmfFirstWavelength_nm(cmfFirstWavelength_nm) 77 | , _xyzCmfs(xyzCmfs) 78 | , _xyzToRgb(xyzToRgb) 79 | {} 80 | 81 | 82 | size_t SpectrumConverter::cmfWavelengthIndex(float wavelength_nm) const 83 | { 84 | assert(wavelength_nm >= firstWavelength()); 85 | assert(wavelength_nm <= lastWavelength()); 86 | 87 | float idx_f = float(wavelength_nm - _cmfFirstWavelength_nm); 88 | 89 | assert(idx_f >= 0); 90 | assert(idx_f < _xyzCmfs[0].size()); 91 | 92 | return static_cast(std::round(idx_f)); 93 | } 94 | 95 | 96 | size_t SpectrumConverter::cmfWavelengthValue(size_t index) const 97 | { 98 | // assert(index < _xyzCmfs[0].size()); 99 | if (index >= _xyzCmfs[0].size()) return 0; 100 | return _cmfFirstWavelength_nm + index; 101 | } 102 | 103 | 104 | void SpectrumConverter::spectrumToXYZ( 105 | const std::vector &wavelengths_nm, 106 | const float * spectrum, 107 | std::array & XYZ) const 108 | { 109 | if (_emissiveSpectrum) { 110 | emissiveSpectrumToXYZ(wavelengths_nm, spectrum, XYZ); 111 | } else { 112 | reflectiveSpectrumToXYZ(wavelengths_nm, spectrum, XYZ); 113 | } 114 | } 115 | 116 | 117 | void SpectrumConverter::spectrumToRGB( 118 | const std::vector &wavelengths_nm, 119 | const float * spectrum, 120 | std::array & RGB) const 121 | { 122 | std::array XYZ; 123 | spectrumToXYZ(wavelengths_nm, spectrum, XYZ); 124 | 125 | memset(&RGB[0], 0, 3 * sizeof(float)); 126 | 127 | // Convert to RGB using the provided matrix 128 | for (size_t channel = 0; channel < 3; channel++) { 129 | for (size_t col = 0; col < 3; col++) { 130 | RGB[channel] += XYZ[col] * _xyzToRgb[3 * channel + col]; 131 | } 132 | 133 | // Ensure RGB values are > 0 134 | RGB[channel] = std::max(RGB[channel], 0.F); 135 | } 136 | } 137 | 138 | 139 | void SpectrumConverter::spectraToXYZ( 140 | const std::vector &wavelengths_nm, 141 | const float * reflectiveSpectrum, 142 | const float * emissiveSpectrum, 143 | std::array & XYZ) const 144 | { 145 | std::array XYZ_refl; 146 | std::array XYZ_emissive; 147 | 148 | reflectiveSpectrumToXYZ(wavelengths_nm, reflectiveSpectrum, XYZ_refl); 149 | emissiveSpectrumToXYZ(wavelengths_nm, emissiveSpectrum, XYZ_emissive); 150 | 151 | for (size_t c = 0; c < 3; c++) { 152 | XYZ[c] = XYZ_refl[c] + XYZ_emissive[c]; 153 | } 154 | } 155 | 156 | 157 | void SpectrumConverter::spectraToRGB( 158 | const std::vector &wavelengths_nm, 159 | const float * reflectiveSpectrum, 160 | const float * emissiveSpectrum, 161 | std::array & RGB) const 162 | { 163 | std::array XYZ; 164 | spectraToXYZ(wavelengths_nm, reflectiveSpectrum, emissiveSpectrum, XYZ); 165 | 166 | memset(&RGB[0], 0, 3 * sizeof(float)); 167 | 168 | // Convert to RGB using the provided matrix 169 | for (size_t channel = 0; channel < 3; channel++) { 170 | for (size_t col = 0; col < 3; col++) { 171 | RGB[channel] += XYZ[col] * _xyzToRgb[3 * channel + col]; 172 | } 173 | 174 | // Ensure RGB values are > 0 175 | RGB[channel] = std::max(RGB[channel], 0.F); 176 | } 177 | } 178 | 179 | 180 | void SpectrumConverter::spectrumToXYZ( 181 | const std::vector &wavelengths_nm, 182 | const float * diagonal, 183 | const float * reradiation, 184 | std::array & XYZ) const 185 | { 186 | memset(&XYZ[0], 0, 3 * sizeof(float)); 187 | 188 | if (_emissiveSpectrum) { 189 | return spectrumToXYZ(wavelengths_nm, diagonal, XYZ); 190 | } 191 | 192 | if (wavelengths_nm.size() == 0) { 193 | return; 194 | } 195 | 196 | const float illuminant_last_wavelength 197 | = _illuminantFirstWavelenght_nm + _illuminantSPD.size() - 1; 198 | const float start_wavelength = std::max( 199 | std::max(_illuminantFirstWavelenght_nm, firstWavelength()), 200 | wavelengths_nm.front()); 201 | const float end_wavelength = std::min( 202 | std::min(illuminant_last_wavelength, lastWavelength()), 203 | wavelengths_nm.back()); 204 | 205 | // Early exit, selection out of range 206 | if (end_wavelength < start_wavelength) { 207 | return; 208 | } 209 | 210 | assert(start_wavelength <= end_wavelength); 211 | 212 | float normalisation_factor(0); 213 | 214 | std::function value 215 | = [diagonal, reradiation](size_t wi, size_t wo) { 216 | if (wi == wo) { 217 | return diagonal[wi]; 218 | } 219 | 220 | return reradiation[Util::idxFromWavelengthIdx(wi, wo)]; 221 | }; 222 | 223 | for (size_t wl_idx_i = 0; wl_idx_i < wavelengths_nm.size() - 1; 224 | wl_idx_i++) { 225 | float wl_i_a = wavelengths_nm[wl_idx_i]; 226 | float wl_i_b = wavelengths_nm[wl_idx_i + 1]; 227 | 228 | // We have not reached yet the starting point 229 | if (start_wavelength > wl_i_b) { 230 | continue; 231 | } 232 | 233 | // We have finished the integration 234 | if (end_wavelength < wl_i_a) { 235 | break; 236 | } 237 | 238 | if (start_wavelength > wl_i_a) { 239 | wl_i_a = start_wavelength; 240 | } 241 | 242 | if (end_wavelength < wl_i_b) { 243 | wl_i_b = end_wavelength; 244 | } 245 | 246 | const size_t idx_illu_start 247 | = wl_i_a - _illuminantFirstWavelenght_nm; 248 | size_t idx_illu_end = wl_i_b - _illuminantFirstWavelenght_nm; 249 | 250 | // On last intervall we need to include the last 251 | // wavelength of the spectrum 252 | if (wl_idx_i == wavelengths_nm.size() - 2) { 253 | idx_illu_end = idx_illu_end + 1; 254 | } 255 | 256 | 257 | for (size_t wl_idx_o = wl_idx_i; 258 | wl_idx_o < wavelengths_nm.size() - 1; 259 | wl_idx_o++) { 260 | float wl_o_a = wavelengths_nm[wl_idx_o]; 261 | float wl_o_b = wavelengths_nm[wl_idx_o + 1]; 262 | 263 | // We have not reached yet the starting point 264 | if (start_wavelength > wl_o_b) { 265 | continue; 266 | } 267 | 268 | // We have finished the integration 269 | if (end_wavelength < wl_o_a) { 270 | break; 271 | } 272 | 273 | if (start_wavelength > wl_o_a) { 274 | wl_o_a = start_wavelength; 275 | } 276 | 277 | if (end_wavelength < wl_o_b) { 278 | wl_o_b = end_wavelength; 279 | } 280 | 281 | const size_t idx_cmf_start = cmfWavelengthIndex(wl_o_a); 282 | size_t idx_cmf_end = cmfWavelengthIndex(wl_o_b); 283 | 284 | // On last intervall we need to include the last 285 | // wavelength of the spectrum 286 | if (wl_idx_o == wavelengths_nm.size() - 2) { 287 | idx_cmf_end = idx_cmf_end + 1; 288 | } 289 | 290 | for (size_t idx_illu = idx_illu_start; idx_illu < idx_illu_end; 291 | idx_illu++) { 292 | const float curr_wl_i 293 | = idx_illu + _illuminantFirstWavelenght_nm; 294 | const float illu_value = _illuminantSPD[idx_illu]; 295 | 296 | assert(idx_illu < _illuminantSPD.size()); 297 | 298 | const float interp_illu = Util::alpha( 299 | wavelengths_nm[wl_idx_i], 300 | wavelengths_nm[wl_idx_i + 1], 301 | curr_wl_i); 302 | 303 | for (size_t idx_cmf = idx_cmf_start; idx_cmf < idx_cmf_end; 304 | idx_cmf++) { 305 | const float curr_wl_o = cmfWavelengthValue(idx_cmf); 306 | 307 | const float interp_rerad = Util::alpha( 308 | wavelengths_nm[wl_idx_o], 309 | wavelengths_nm[wl_idx_o + 1], 310 | curr_wl_o); 311 | 312 | // TODO.. not sure if this is correct 313 | if (wl_idx_i == wl_idx_o) { 314 | normalisation_factor 315 | += illu_value * _xyzCmfs[1][idx_cmf]; // Y 316 | } 317 | 318 | // interpolation 319 | const float bispect 320 | = (1 - interp_rerad) 321 | * ((1 - interp_illu) * value(wl_idx_i, wl_idx_o) + (interp_illu)*value(wl_idx_i + 1, wl_idx_o)) 322 | + (interp_rerad) 323 | * ((1 - interp_illu) * value(wl_idx_i, wl_idx_o + 1) + (interp_illu)*value(wl_idx_i + 1, wl_idx_o + 1)); 324 | 325 | const float curr_value = illu_value * bispect; 326 | 327 | for (size_t c = 0; c < 3; c++) { 328 | XYZ[c] += curr_value * _xyzCmfs[c][idx_cmf]; 329 | } 330 | } 331 | } 332 | } 333 | } 334 | 335 | for (size_t c = 0; c < 3; c++) { 336 | XYZ[c] /= normalisation_factor; 337 | } 338 | } 339 | 340 | 341 | void SpectrumConverter::spectrumToRGB( 342 | const std::vector &wavelengths_nm, 343 | const float * diagonal, 344 | const float * reradiation, 345 | std::array & RGB) const 346 | { 347 | std::array XYZ; 348 | spectrumToXYZ(wavelengths_nm, diagonal, reradiation, XYZ); 349 | 350 | memset(&RGB[0], 0, 3 * sizeof(float)); 351 | 352 | // Convert to RGB using the provided matrix 353 | for (size_t channel = 0; channel < 3; channel++) { 354 | for (size_t col = 0; col < 3; col++) { 355 | RGB[channel] += XYZ[col] * _xyzToRgb[3 * channel + col]; 356 | } 357 | 358 | // Ensure RGB values are > 0 359 | RGB[channel] = std::max(RGB[channel], 0.F); 360 | } 361 | } 362 | 363 | void SpectrumConverter::spectraToXYZ( 364 | const std::vector &wavelengths_nm, 365 | const float * diagonal, 366 | const float * reradiation, 367 | const float * emissiveSpectrum, 368 | std::array & XYZ) const 369 | { 370 | std::array XYZ_refl; 371 | std::array XYZ_emissive; 372 | 373 | spectrumToXYZ(wavelengths_nm, diagonal, reradiation, XYZ_refl); 374 | emissiveSpectrumToXYZ(wavelengths_nm, emissiveSpectrum, XYZ_emissive); 375 | 376 | for (size_t c = 0; c < 3; c++) { 377 | XYZ[c] = XYZ_refl[c] + XYZ_emissive[c]; 378 | } 379 | } 380 | 381 | 382 | void SpectrumConverter::spectraToRGB( 383 | const std::vector &wavelengths_nm, 384 | const float * diagonal, 385 | const float * reradiation, 386 | const float * emissiveSpectrum, 387 | std::array & RGB) const 388 | { 389 | std::array XYZ; 390 | spectraToXYZ( 391 | wavelengths_nm, 392 | diagonal, 393 | reradiation, 394 | emissiveSpectrum, 395 | XYZ); 396 | 397 | memset(&RGB[0], 0, 3 * sizeof(float)); 398 | 399 | // Convert to RGB using the provided matrix 400 | for (size_t channel = 0; channel < 3; channel++) { 401 | for (size_t col = 0; col < 3; col++) { 402 | RGB[channel] += XYZ[col] * _xyzToRgb[3 * channel + col]; 403 | } 404 | 405 | // Ensure RGB values are > 0 406 | RGB[channel] = std::max(RGB[channel], 0.F); 407 | } 408 | } 409 | 410 | 411 | void SpectrumConverter::emissiveSpectrumToXYZ( 412 | const std::vector &wavelengths_nm, 413 | const float * spectrum, 414 | std::array & XYZ) const 415 | { 416 | memset(&XYZ[0], 0, 3 * sizeof(float)); 417 | 418 | if (wavelengths_nm.size() == 0) { 419 | return; 420 | } 421 | 422 | const float start_wavelength 423 | = std::max(firstWavelength(), wavelengths_nm.front()); 424 | const float end_wavelength 425 | = std::min(lastWavelength(), wavelengths_nm.back()); 426 | 427 | // Early exit, selection out of range 428 | if (end_wavelength < start_wavelength) { 429 | return; 430 | } 431 | 432 | assert(start_wavelength <= end_wavelength); 433 | 434 | for (size_t idx_value = 0; idx_value < wavelengths_nm.size() - 1; 435 | idx_value++) { 436 | float wl_a = wavelengths_nm[idx_value]; 437 | float wl_b = wavelengths_nm[idx_value + 1]; 438 | 439 | // We have not reached yet the starting point 440 | if (start_wavelength > wl_b) { 441 | continue; 442 | } 443 | 444 | // We have finished the integration 445 | if (end_wavelength < wl_a) { 446 | break; 447 | } 448 | 449 | if (start_wavelength > wl_a) { 450 | wl_a = start_wavelength; 451 | } 452 | 453 | if (end_wavelength < wl_b) { 454 | wl_b = end_wavelength; 455 | } 456 | 457 | const size_t idx_cmf_start = cmfWavelengthIndex(wl_a); 458 | size_t idx_cmf_end = cmfWavelengthIndex(wl_b); 459 | 460 | // On last intervall we need to include the last 461 | // wavelength of the spectrum 462 | if (idx_value == wavelengths_nm.size() - 2) { 463 | idx_cmf_end = idx_cmf_end + 1; 464 | } 465 | 466 | for (size_t idx_cmf = idx_cmf_start; idx_cmf < idx_cmf_end; 467 | idx_cmf++) { 468 | const float curr_wl = cmfWavelengthValue(idx_cmf); 469 | const float curr_value = Util::interp( 470 | curr_wl, 471 | wavelengths_nm[idx_value], 472 | wavelengths_nm[idx_value + 1], 473 | spectrum[idx_value], 474 | spectrum[idx_value + 1]); 475 | 476 | for (size_t c = 0; c < 3; c++) { 477 | XYZ[c] += _xyzCmfs[c][idx_cmf] * curr_value; 478 | } 479 | } 480 | } 481 | } 482 | 483 | 484 | void SpectrumConverter::reflectiveSpectrumToXYZ( 485 | const std::vector &wavelengths_nm, 486 | const float * spectrum, 487 | std::array & XYZ) const 488 | { 489 | memset(&XYZ[0], 0, 3 * sizeof(float)); 490 | 491 | if (wavelengths_nm.size() == 0) { 492 | return; 493 | } 494 | 495 | const float illuminant_last_wavelength 496 | = _illuminantFirstWavelenght_nm + _illuminantSPD.size() - 1; 497 | const float start_wavelength = std::max( 498 | std::max(_illuminantFirstWavelenght_nm, firstWavelength()), 499 | wavelengths_nm.front()); 500 | const float end_wavelength = std::min( 501 | std::min(illuminant_last_wavelength, lastWavelength()), 502 | wavelengths_nm.back()); 503 | 504 | // Early exit, selection out of range 505 | if (end_wavelength < start_wavelength) { 506 | return; 507 | } 508 | 509 | assert(start_wavelength <= end_wavelength); 510 | 511 | float normalisation_factor(0); 512 | 513 | for (size_t idx_value = 0; idx_value < wavelengths_nm.size() - 1; 514 | idx_value++) { 515 | float wl_a = wavelengths_nm[idx_value]; 516 | float wl_b = wavelengths_nm[idx_value + 1]; 517 | 518 | // We have not reached yet the starting point 519 | if (start_wavelength > wl_b) { 520 | continue; 521 | } 522 | 523 | // We have finished the integration 524 | if (end_wavelength < wl_a) { 525 | break; 526 | } 527 | 528 | if (start_wavelength > wl_a) { 529 | wl_a = start_wavelength; 530 | } 531 | 532 | if (end_wavelength < wl_b) { 533 | wl_b = end_wavelength; 534 | } 535 | 536 | const size_t idx_curve_start = cmfWavelengthIndex(wl_a); 537 | size_t idx_curve_end = cmfWavelengthIndex(wl_b); 538 | 539 | // On last intervall we need to include the last 540 | // wavelength of the spectrum 541 | if (idx_value == wavelengths_nm.size() - 2) { 542 | idx_curve_end = idx_curve_end + 1; 543 | } 544 | 545 | for (size_t idx_curve = idx_curve_start; idx_curve < idx_curve_end; 546 | idx_curve++) { 547 | const float curr_wl = cmfWavelengthValue(idx_curve); 548 | 549 | const size_t idx_illu_a 550 | = curr_wl - _illuminantFirstWavelenght_nm; 551 | assert(curr_wl >= _illuminantFirstWavelenght_nm); 552 | assert(idx_illu_a < _illuminantSPD.size()); 553 | 554 | const float illu_value = _illuminantSPD[idx_illu_a]; 555 | 556 | normalisation_factor 557 | += illu_value * _xyzCmfs[1][idx_curve]; // Y 558 | 559 | const float curr_value = illu_value 560 | * Util::interp( 561 | curr_wl, 562 | wavelengths_nm[idx_value], 563 | wavelengths_nm[idx_value + 1], 564 | spectrum[idx_value], 565 | spectrum[idx_value + 1]); 566 | 567 | for (size_t c = 0; c < 3; c++) { 568 | XYZ[c] += curr_value * _xyzCmfs[c][idx_curve]; 569 | } 570 | } 571 | } 572 | 573 | for (size_t c = 0; c < 3; c++) { 574 | XYZ[c] /= normalisation_factor; 575 | } 576 | } 577 | 578 | } // namespace SEXR 579 | -------------------------------------------------------------------------------- /lib/SpectrumConverter.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | namespace SEXR 44 | { 45 | class SpectrumConverter 46 | { 47 | public: 48 | SpectrumConverter(bool emissiveSpectrum = true); 49 | 50 | SpectrumConverter( 51 | const float & cmfFirstWavelength_nm, 52 | const std::array, 3> &xyzCmfs, 53 | const std::array xyzToRgb); 54 | 55 | float firstWavelength() const { return _cmfFirstWavelength_nm; } 56 | float lastWavelength() const 57 | { 58 | return _cmfFirstWavelength_nm + _xyzCmfs[0].size() - 1; 59 | } 60 | 61 | size_t cmfWavelengthIndex(float wavelength_nm) const; 62 | size_t cmfWavelengthValue(size_t index) const; 63 | 64 | // The spectrum provided must be either emissive or reflective. 65 | // This depends on the constructor used. 66 | void spectrumToXYZ( 67 | const std::vector &wavelengths_nm, 68 | const float * spectrum, 69 | std::array & XYZ) const; 70 | 71 | void spectrumToRGB( 72 | const std::vector &wavelengths_nm, 73 | const float * spectrum, 74 | std::array & RGB) const; 75 | 76 | // Here, we provide two spectra, one for the reflective part, 77 | // the other for the emissive part. 78 | // The result will be the lighting multiplied by the reflective 79 | // added to the emissive spectrum. 80 | void spectraToXYZ( 81 | const std::vector &wavelengths_nm, 82 | const float * reflectiveSpectrum, 83 | const float * emissiveSpectrum, 84 | std::array & XYZ) const; 85 | 86 | void spectraToRGB( 87 | const std::vector &wavelengths_nm, 88 | const float * reflectiveSpectrum, 89 | const float * emissiveSpectrum, 90 | std::array & RGB) const; 91 | 92 | // Bi-spectral 93 | void spectrumToXYZ( 94 | const std::vector &wavelengths_nm, 95 | const float * diagonal, 96 | const float * reradiation, 97 | std::array & XYZ) const; 98 | 99 | void spectrumToRGB( 100 | const std::vector &wavelengths_nm, 101 | const float * diagonal, 102 | const float * reradiation, 103 | std::array & RGB) const; 104 | 105 | 106 | // Bi-spectral 107 | void spectraToXYZ( 108 | const std::vector &wavelengths_nm, 109 | const float * diagonal, 110 | const float * reradiation, 111 | const float * emissiveSpectrum, 112 | std::array & XYZ) const; 113 | 114 | void spectraToRGB( 115 | const std::vector &wavelengths_nm, 116 | const float * diagonal, 117 | const float * reradiation, 118 | const float * emissiveSpectrum, 119 | std::array & RGB) const; 120 | 121 | protected: 122 | void emissiveSpectrumToXYZ( 123 | const std::vector &wavelengths_nm, 124 | const float * spectrum, 125 | std::array & XYZ) const; 126 | 127 | void reflectiveSpectrumToXYZ( 128 | const std::vector &wavelengths_nm, 129 | const float * spectrum, 130 | std::array & XYZ) const; 131 | 132 | 133 | bool _emissiveSpectrum; 134 | 135 | float _illuminantFirstWavelenght_nm; 136 | std::vector _illuminantSPD; 137 | 138 | float _cmfFirstWavelength_nm; 139 | std::array, 3> _xyzCmfs; 140 | std::array _xyzToRgb; 141 | }; 142 | 143 | } // namespace SEXR 144 | -------------------------------------------------------------------------------- /lib/Util.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | namespace SEXR 44 | { 45 | class Util 46 | { 47 | public: 48 | static float interp(float x, float x0, float x1, float y0, float y1) 49 | { 50 | return lerp(y0, y1, alpha(x0, x1, x)); 51 | } 52 | 53 | 54 | static float alpha(float x0, float x1, float x) 55 | { 56 | return (x - x0) / (x1 - x0); 57 | } 58 | 59 | 60 | static float lerp(float a, float b, float t) { return a + t * (b - a); } 61 | 62 | 63 | static double strToNanometers( 64 | const double & value, 65 | const std::string &prefix, 66 | const std::string &units) 67 | { 68 | if (prefix == "n" && units == "m") return value; 69 | 70 | double wavelength_nm = value; 71 | 72 | const std::map unit_prefix = { 73 | {"Y", 1e24}, 74 | {"Z", 1e21}, 75 | {"E", 1e18}, 76 | {"P", 1e15}, 77 | {"T", 1e12}, 78 | {"G", 1e9}, 79 | {"M", 1e6}, 80 | {"k", 1e3}, 81 | {"h", 1e2}, 82 | {"da", 1e1}, 83 | {"d", 1e-1}, 84 | {"c", 1e-2}, 85 | {"m", 1e-3}, 86 | {"u", 1e-6}, 87 | {"n", 1e-9}, 88 | {"p", 1e-12}}; 89 | 90 | // Apply multiplier 91 | if (prefix.size() > 0) { 92 | wavelength_nm *= unit_prefix.at(prefix); 93 | } 94 | 95 | // Apply units 96 | if (units == "Hz") { 97 | wavelength_nm = 299792458. / wavelength_nm * 1e9; 98 | } else if (units == "m") { 99 | wavelength_nm = wavelength_nm * 1e9; 100 | } else { 101 | // Unknown unit 102 | // Something went wrong with the parsing. This shall not occur. 103 | throw std::out_of_range("Unknown unit"); 104 | } 105 | 106 | return wavelength_nm; 107 | } 108 | 109 | 110 | static size_t idxFromWavelengthIdx(size_t wlFrom_idx, size_t wlTo_idx) 111 | { 112 | if (wlFrom_idx < wlTo_idx) { 113 | return wlTo_idx * (wlTo_idx - 1) / 2 + wlFrom_idx; 114 | } else { 115 | return -1; 116 | } 117 | } 118 | }; 119 | 120 | } // namespace SEXR 121 | -------------------------------------------------------------------------------- /lib/cmake/FindOpenEXR.cmake: -------------------------------------------------------------------------------- 1 | # Locate OpenEXR 2 | # This module defines 3 | # OpenEXR_LIBRARY 4 | # OpenEXR_FOUND, if false, do not try to link to OpenEXR 5 | # OpenEXR_INCLUDE_DIR, where to find the headers 6 | # 7 | # $OPENEXR_DIR is an environment variable that would 8 | # correspond to the ./configure --prefix=$OPENEXR_DIR 9 | # 10 | # Created by Robert Osfield. 11 | 12 | 13 | FIND_PATH(OPENEXR_INCLUDE_DIR OpenEXR/ImfIO.h 14 | $ENV{OPENEXR_DIR}/include 15 | $ENV{OPENEXR_DIR} 16 | ~/Library/Frameworks 17 | /Library/Frameworks 18 | /usr/local/include/ 19 | /usr/include 20 | /sw/include # Fink 21 | /opt/local/include # DarwinPorts 22 | /opt/csw/include # Blastwave 23 | /opt/include 24 | /usr/freeware/include 25 | /usr/local/include/OpenEXR 26 | /usr/include/OpenEXR 27 | /sw/include/OpenEXR # Fink 28 | /opt/local/include/OpenEXR # DarwinPorts 29 | /opt/csw/include/OpenEXR # Blastwave 30 | /opt/include/OpenEXR 31 | /usr/freeware/include/OpenEXR 32 | ) 33 | 34 | # Macro to find exr libraries (deduplicating search paths) 35 | # example: OPENEXR_FIND_VAR(OPENEXR_IlmIlf_LIBRARY IlmIlf) 36 | MACRO(OPENEXR_FIND_VAR varname libname) 37 | FIND_LIBRARY( ${varname} 38 | NAMES ${libname} 39 | PATHS 40 | $ENV{OPENEXR_DIR}/lib 41 | $ENV{OPENEXR_DIR} 42 | ~/Library/Frameworks 43 | /Library/Frameworks 44 | /usr/local/lib 45 | /usr/lib 46 | /sw/lib 47 | /opt/local/lib 48 | /opt/csw/lib 49 | /opt/lib 50 | /usr/freeware/lib64 51 | ) 52 | ENDMACRO(OPENEXR_FIND_VAR) 53 | 54 | # Macro to find exr libraries (and debug versions) 55 | # example: OPENEXR_FIND(IlmIlf) 56 | SET(_openexr_FIND_COMPONENTS 57 | Half 58 | Iex 59 | IlmImf 60 | IlmThread 61 | Imath 62 | ) 63 | 64 | FIND_PATH(OpenEXR_INCLUDE_DIR 65 | NAMES 66 | OpenEXR/ImfXdr.h 67 | HINTS 68 | ${_openexr_SEARCH_DIRS} 69 | PATH_SUFFIXES 70 | include 71 | ) 72 | 73 | SET(_openexr_LIBRARIES) 74 | FOREACH(COMPONENT ${_openexr_FIND_COMPONENTS}) 75 | STRING(TOUPPER ${COMPONENT} UPPERCOMPONENT) 76 | 77 | FIND_LIBRARY(OPENEXR_${UPPERCOMPONENT}_LIBRARY 78 | NAMES 79 | ${COMPONENT} 80 | HINTS 81 | ${_openexr_SEARCH_DIRS} 82 | PATH_SUFFIXES 83 | lib64 lib 84 | ) 85 | LIST(APPEND _openexr_LIBRARIES "${OPENEXR_${UPPERCOMPONENT}_LIBRARY}") 86 | ENDFOREACH() 87 | 88 | IF(_openexr_LIBRARIES) 89 | SET(OpenEXR_LIBRARIES ${_openexr_LIBRARIES} ) 90 | SET(OPENEXR_LIBRARIES_VARS OPENEXR_IlmIlf_LIBRARY OPENEXR_IlmThread_LIBRARY OPENEXR_Half_LIBRARY OPENEXR_Iex_LIBRARY ) 91 | SET(OpenEXR_FOUND 1 CACHE BOOL "Found OpenEXR library") 92 | ELSE(_openexr_LIBRARIES) 93 | SET(OpenEXR_FOUND 0 CACHE BOOL "OpenEXR library not found!") 94 | ENDIF(_openexr_LIBRARIES) 95 | 96 | include(FindPackageHandleStandardArgs) 97 | FIND_PACKAGE_HANDLE_STANDARD_ARGS( 98 | OpenEXR 99 | REQUIRED_VARS OpenEXR_LIBRARIES OpenEXR_INCLUDE_DIR 100 | FAIL_MESSAGE "Could not find the OpenEXR library. ART will not be able to read and write such images." 101 | ) 102 | 103 | MARK_AS_ADVANCED( 104 | OpenEXR_INCLUDE_DIR 105 | OpenEXR_LIBRARIES 106 | OpenEXR_FOUND 107 | ) 108 | -------------------------------------------------------------------------------- /lib/include/BiSpectralImage.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | #include "SpectralImage.h" 40 | 41 | namespace SEXR 42 | { 43 | class BiSpectralImage: public SpectralImage 44 | { 45 | public: 46 | /** 47 | * Creates a new spectral or bispectral image. 48 | * 49 | * @param width width of the image. 50 | * @param height height of the image. 51 | * @param wavelengths_nm wavlengths in nanometers of the image. 52 | * @param type spectrum type represented in the image. 53 | * @param handedness polarisation handedness convention. 54 | */ 55 | BiSpectralImage( 56 | size_t width = 0, 57 | size_t height = 0, 58 | const std::vector &wavelengths_nm = std::vector(), 59 | SpectrumType type = REFLECTIVE, 60 | PolarisationHandedness handedness = RIGHT_HANDED); 61 | 62 | /** 63 | * Export each channel value in an individual EXR image. For 64 | * each spectral and bispectral channel, an individual image 65 | * will be created in the folder pointed by path. The folder 66 | * must exists prior to the call of this method. 67 | * 68 | * - Reflective / Transmissive channels are prefixed with "T - " 69 | * - Emissive channels are prefixed with "S - " 70 | * 71 | * Then, the wavelength in nanometer is appended with "nm" 72 | * unit specified. 73 | * 74 | * @param path folder path where to export the images. 75 | */ 76 | virtual void exportChannels(const std::string &path) const; 77 | 78 | /** 79 | * Get the RGB version of the image. The rgb image is stored 80 | * as rgbImage[3 * (row * width + col) + color]. 81 | * 82 | * @param rgbImage a reference to the pointer where to store 83 | * the RGB version of this bispectral image. 84 | */ 85 | virtual void getRGBImage(std::vector &rgbImage) const; 86 | 87 | /** 88 | * Number of elements needed to store the reradiation part of 89 | * the image. 90 | */ 91 | size_t reradiationSize() const 92 | { 93 | return nSpectralBands() * (nSpectralBands() - 1) / 2; 94 | } 95 | 96 | /** 97 | * Gives the index where the reradiation is stored from 98 | * indices of radiating wavelength and reemission wavelength. 99 | * 100 | * @param wlFrom_idx index of the radiating wavelength. 101 | * @param wlTo_idx index of the reemissive wavelength. 102 | * 103 | * @returns index where the reradiation is stored. 104 | */ 105 | static size_t idxFromWavelengthIdx(size_t wlFrom_idx, size_t wlTo_idx); 106 | 107 | /** 108 | * Gives the radiating and reemissive indices from the index 109 | * where the reradiation is stored. 110 | * 111 | * @param rerad_idx index where the reradiation is stored. 112 | * @param wlFrom_idx index of the radiating wavelength. 113 | * @param wlTo_idx index of the reemissive wavelength. 114 | */ 115 | static void wavelengthsIdxFromIdx( 116 | size_t rerad_idx, size_t &wlFrom_idx, size_t &wlTo_idx); 117 | 118 | /** 119 | * Return the reflective value for a given pixel for a given 120 | * radiating wavelength to a specific reemitting wavelength. 121 | * If wavelengthFrom_idx == wavelengthTo_idx, this returns the 122 | * diagonal (non fluorescent part). 123 | * 124 | * @param x column coordinate in the image in pixels (0 on left). 125 | * @param y row coordinate in the image in pixels (0 on top). 126 | * @param wavelengthFrom_idx index of the radiating wavelength. 127 | * @param wavelengthTo_idx index of the reemitting wavelength. 128 | */ 129 | virtual float getReflectiveValue( 130 | size_t x, 131 | size_t y, 132 | size_t wavelengthFrom_idx, 133 | size_t wavelengthTo_idx) const; 134 | 135 | /** 136 | * Gives a reference to the reflective element at location x, 137 | * y for given radiating and reemissive wavelengths indices. 138 | * 139 | * @param x column coordinate in the image in pixels (0 on left). 140 | * @param y row coordinate in the image in pixels (0 on top). 141 | * @param wavelengthFrom_idx index of the radiating wavelength. 142 | * @param wavelengthTo_idx index of the reemitting wavelength. 143 | */ 144 | virtual float &reflective( 145 | size_t x, 146 | size_t y, 147 | size_t wavelengthFrom_idx, 148 | size_t wavelengthTo_idx); 149 | 150 | virtual const float &reflective( 151 | size_t x, 152 | size_t y, 153 | size_t wavelengthFrom_idx, 154 | size_t wavelengthTo_idx) const; 155 | 156 | 157 | protected: 158 | // Upper right triangular matrices for each pixel 159 | // pixel stride is reradiationSize() 160 | std::vector _reradiation; 161 | }; 162 | 163 | } // namespace SEXR 164 | -------------------------------------------------------------------------------- /lib/include/EXRBiSpectralImage.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | #include "BiSpectralImage.h" 40 | 41 | namespace SEXR 42 | { 43 | class EXRBiSpectralImage: public BiSpectralImage 44 | { 45 | public: 46 | /** 47 | * Creates a new spectral or bispectral image. 48 | * 49 | * @param width width of the image. 50 | * @param height height of the image. 51 | * @param wavelengths_nm wavlengths in nanometers of the image. 52 | * @param type spectrum type represented in the image. 53 | */ 54 | EXRBiSpectralImage( 55 | size_t width = 0, 56 | size_t height = 0, 57 | const std::vector &wavelengths_nm = std::vector(), 58 | SpectrumType type = REFLECTIVE, 59 | PolarisationHandedness handedness = RIGHT_HANDED); 60 | 61 | /** 62 | * Loads a spectral or bispectral image from an EXR file. 63 | * 64 | * @param filename path to the image to load. 65 | */ 66 | EXRBiSpectralImage(const std::string &filename); 67 | 68 | /** 69 | * Saves the bispectral image to an EXR file. 70 | * 71 | * @param filename path where the image shall be saved. 72 | */ 73 | void save(const std::string &filename) const; 74 | 75 | static SpectrumType channelType( 76 | const std::string &channelName, 77 | int & polarisationComponent, 78 | double & wavelengths_nm, 79 | double & reradiation_wavelength_nm); 80 | 81 | /** 82 | * Gets the channel name used in the EXR file for a specific 83 | * emissive component. 84 | * 85 | * @param stokesComponent index of the Stokes component (0-3). 86 | * @param wavelength_nm wavelength in nanometers. 87 | * 88 | * @returns std::string containing the emissive channel name 89 | * for the given Stokes component at a specific wavelength. 90 | */ 91 | static std::string 92 | getEmissiveChannelName(int stokesComponent, double wavelength_nm); 93 | 94 | /** 95 | * Gets the channel name used in the EXR file for a specific 96 | * non reradiating component. 97 | * 98 | * @param wavelength_nm wavelength in nanometers. 99 | * 100 | * @returns std::string containing the reflective channel name 101 | * at a specific wavelength. 102 | */ 103 | static std::string getReflectiveChannelName(double wavelength_nm); 104 | 105 | /** 106 | * Gets the channel name used in the EXR file for a specific 107 | * reradiating component. 108 | * 109 | * @param wavelength_nm radiating wavelength in nanometers. 110 | * @param reradiation_wavelength_nm reemissive wavelength in 111 | * nanometers. 112 | * 113 | * @returns std::string containing the reflective channel name 114 | * at a specific radiating wavelength to a specific reemission 115 | * band. 116 | */ 117 | static std::string getReradiationChannelName( 118 | double wavelength_nm, double reradiation_wavelength_nm); 119 | 120 | static constexpr const char *VERSION_ATTR = "spectralLayoutVersion"; 121 | static constexpr const char *SPECTRUM_TYPE_ATTR = "spectrumType"; 122 | static constexpr const char *EMISSIVE_UNITS_ATTR = "emissiveUnits"; 123 | static constexpr const char *LENS_TRANSMISSION_ATTR 124 | = "lensTransmission"; 125 | static constexpr const char *CAMERA_RESPONSE_ATTR = "cameraResponse"; 126 | static constexpr const char *EXPOSURE_COMPENSATION_ATTR = "EV"; 127 | static constexpr const char *POLARISATION_HANDEDNESS_ATTR 128 | = "polarisationHandedness"; 129 | }; 130 | 131 | } // namespace SEXR 132 | -------------------------------------------------------------------------------- /lib/include/EXRSpectralImage.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | #include "SpectralImage.h" 44 | 45 | namespace SEXR 46 | { 47 | class EXRSpectralImage: public SpectralImage 48 | { 49 | public: 50 | /** 51 | * Creates a new spectral image. 52 | * 53 | * @param width width of the image. 54 | * @param height height of the image. 55 | * @param wavelengths_nm wavlengths in nanometers of the image. 56 | * @param type spectrum type represented in the image. 57 | */ 58 | EXRSpectralImage( 59 | size_t width = 0, 60 | size_t height = 0, 61 | const std::vector &wavelengths_nm = std::vector(), 62 | SpectrumType type = BISPECTRAL, 63 | PolarisationHandedness handedness = RIGHT_HANDED); 64 | 65 | /** 66 | * Loads a spectral image from an EXR file. 67 | * 68 | * @param filename path to the image to load. 69 | */ 70 | EXRSpectralImage(const std::string &filename); 71 | 72 | /** 73 | * Saves the spectral image to an EXR file. 74 | * 75 | * @param filename path where the image shall be saved. 76 | */ 77 | void save(const std::string &filename) const; 78 | 79 | static SpectrumType channelType( 80 | const std::string &channelName, 81 | int & polarisationComponent, 82 | double & wavelengths_nm); 83 | 84 | /** 85 | * Gets the channel name used in the EXR file for a specific 86 | * emissive component. 87 | * 88 | * @param stokesComponent index of the Stokes component (0-3). 89 | * @param wavelength_nm wavelength in nanometers. 90 | * 91 | * @returns std::string containing the emissive channel name 92 | * for the given Stokes component at a specific wavelength. 93 | */ 94 | static std::string 95 | getEmissiveChannelName(int stokesComponent, double wavelength_nm); 96 | 97 | /** 98 | * Gets the channel name used in the EXR file for a wavelength. 99 | * 100 | * @param wavelength_nm wavelength in nanometers. 101 | * 102 | * @returns std::string containing the reflective channel name 103 | * at a specific wavelength. 104 | */ 105 | static std::string getReflectiveChannelName(double wavelength_nm); 106 | 107 | static constexpr const char *VERSION_ATTR = "spectralLayoutVersion"; 108 | static constexpr const char *SPECTRUM_TYPE_ATTR = "spectrumType"; 109 | static constexpr const char *EMISSIVE_UNITS_ATTR = "emissiveUnits"; 110 | static constexpr const char *LENS_TRANSMISSION_ATTR 111 | = "lensTransmission"; 112 | static constexpr const char *CAMERA_RESPONSE_ATTR = "cameraResponse"; 113 | static constexpr const char *EXPOSURE_COMPENSATION_ATTR = "EV"; 114 | static constexpr const char *POLARISATION_HANDEDNESS_ATTR 115 | = "polarisationHandedness"; 116 | }; 117 | 118 | } // namespace SEXR 119 | -------------------------------------------------------------------------------- /lib/include/SpectralImage.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | 46 | namespace SEXR 47 | { 48 | class SpectralImage 49 | { 50 | public: 51 | enum Errors 52 | { 53 | UNSUPORTED_FILE, 54 | INTERNAL_ERROR, 55 | READ_ERROR, 56 | WRITE_ERROR, 57 | INCORRECT_FORMED_FILE 58 | }; 59 | 60 | enum PolarisationHandedness 61 | { 62 | LEFT_HANDED, 63 | RIGHT_HANDED 64 | }; 65 | 66 | /** 67 | * Creates a new spectral image. 68 | * 69 | * @param width width of the image. 70 | * @param height height of the image. 71 | * @param wavelengths_nm wavlengths in nanometers of the image. 72 | * @param type spectrum type represented in the image. 73 | * @param handedness polarisation handedness convention. 74 | */ 75 | SpectralImage( 76 | size_t width = 0, 77 | size_t height = 0, 78 | const std::vector &wavelengths_nm = std::vector(), 79 | SpectrumType type = EMISSIVE, 80 | PolarisationHandedness handedness = RIGHT_HANDED); 81 | 82 | /** 83 | * Saves the image to an EXR file. 84 | * 85 | * @param filename path where the image shall be saved. 86 | */ 87 | virtual void save(const std::string &filename) const = 0; 88 | 89 | /** 90 | * Export each channel value in an individual EXR image. For 91 | * each spectral and bispectral channel, an individual image 92 | * will be created in the folder pointed by path. The folder 93 | * must exists prior to the call of this method. 94 | * 95 | * - Reflective / Transmissive channels are prefixed with "T - " 96 | * - Emissive channels are prefixed with "S - " 97 | * 98 | * Then, the wavelength in nanometer is appended with "nm" 99 | * unit specified. 100 | * 101 | * @param path folder path where to export the images. 102 | */ 103 | virtual void exportChannels(const std::string &path) const; 104 | 105 | /** 106 | * Get the RGB version of the image. The rgb image is stored 107 | * as rgbImage[3 * (row * width + col) + color]. 108 | * 109 | * @param rgbImage a reference to the pointer where to store 110 | * the RGB version of this bispectral image. 111 | */ 112 | virtual void getRGBImage(std::vector &rgbImage) const; 113 | 114 | void setCameraResponse( 115 | const std::vector &wavelengths_nm, 116 | const std::vector &values); 117 | 118 | const SpectrumAttribute &cameraResponse() const; 119 | 120 | /** 121 | * Sets the lens transmissivity curve. 122 | * 123 | * @param wavelengths_nm wavelengths of the transmissive spectrum. 124 | * @param values transmissives values of the lens. 125 | */ 126 | void setLensTransmission( 127 | const std::vector &wavelengths_nm, 128 | const std::vector &values); 129 | 130 | /** Gets the lens transmissivitiy function. */ 131 | const SpectrumAttribute &lensTransmission() const; 132 | 133 | /** 134 | * Sets a "filter" transmissivity curve for a specific 135 | * wavelength. 136 | * 137 | * @param wl_idx wavelenght index which the filter corresponds to. 138 | * @param wavelengths_nm wavelengths of the transmissive spectrum. 139 | * @param values transmissives values of the filter. 140 | */ 141 | void setChannelSensitivity( 142 | size_t wl_idx, 143 | const std::vector &wavelengths_nm, 144 | const std::vector &values); 145 | 146 | /** Gets the transmissivity curves for each wavelength */ 147 | const std::vector &channelSensitivities() const; 148 | 149 | /** 150 | * Gets the transmissivity curve for a specific wavelength 151 | * 152 | * @param wl_idx index of the wavlength to get the sensitivity curve from. 153 | */ 154 | const SpectrumAttribute &channelSensitivity(size_t wl_idx) const; 155 | 156 | /** 157 | * Sets the exposure compensation values to use for the RGB 158 | * representation. 159 | */ 160 | void setExposureCompensationValue(float ev); 161 | 162 | /** 163 | * Gets the exposure compensation values used by the RGB 164 | * representation. 165 | */ 166 | const float &exposureCompensationValue() const; 167 | 168 | 169 | // Access the emissive part 170 | virtual float &emissive( 171 | size_t x, size_t y, size_t wavelength_idx, size_t stokesComponent); 172 | 173 | virtual const float &emissive( 174 | size_t x, 175 | size_t y, 176 | size_t wavelength_idx, 177 | size_t stokesComponent) const; 178 | 179 | // Access the reflective part 180 | 181 | /** 182 | * Gives a reference to the reflective element at location x, 183 | * y for given a wavelength index. 184 | * 185 | * @param x column coordinate in the image in pixels (0 on left). 186 | * @param y row coordinate in the image in pixels (0 on top). 187 | * @param wavelength_idx index of the radiating wavelength. 188 | */ 189 | virtual float &reflective(size_t x, size_t y, size_t wavelength_idx); 190 | 191 | virtual const float & 192 | reflective(size_t x, size_t y, size_t wavelength_idx) const; 193 | 194 | // Those are not direct memory access 195 | // They can be called whatever the image type is 196 | 197 | /** 198 | * Return the emissive value for a given pixel for a given 199 | * wavelength. 200 | * 201 | * @param x column coordinate in the image in pixels (0 on left). 202 | * @param y row coordinate in the image in pixels (0 on top). 203 | * @param wavelength_idx index of the wavelength. 204 | * @param stokesComponent index of the Stokes component. 205 | */ 206 | virtual float getEmissiveValue( 207 | size_t x, 208 | size_t y, 209 | size_t wavelength_idx, 210 | size_t stokesComponent = 0) const; 211 | 212 | /** 213 | * Return the reflective value for a given pixel for a given 214 | * wavelength. 215 | * 216 | * @param x column coordinate in the image in pixels (0 on left). 217 | * @param y row coordinate in the image in pixels (0 on top). 218 | * @param wavelength_idx index of the wavelength. 219 | */ 220 | virtual float 221 | getReflectiveValue(size_t x, size_t y, size_t wavelength_idx) const; 222 | 223 | /** 224 | * Gets the wavelength value in nanometer from a specific 225 | * wavelength index. 226 | */ 227 | const float &wavelength_nm(size_t wl_idx) const; 228 | 229 | /** Gets the width of the image in pixels. */ 230 | size_t width() const; 231 | 232 | /** Gets the height of the image in pixels. */ 233 | size_t height() const; 234 | 235 | /** Gets the number of spectral bands. */ 236 | size_t nSpectralBands() const; 237 | 238 | /** 239 | * Gets the number of Stokes components. 1 for a non polarised 240 | * image, 4 for a polarised image. 241 | */ 242 | size_t nStokesComponents() const; 243 | 244 | /** True if the image is polarised, False otherwise */ 245 | bool isPolarised() const; 246 | 247 | /** True if the image contains emissive data, False otherwise */ 248 | bool isEmissive() const; 249 | 250 | /** True if the image contains reflective data, False otherwise */ 251 | bool isReflective() const; 252 | 253 | /** True if the image contains bispectral data, False otherwise */ 254 | bool isBispectral() const; 255 | 256 | /** Spectrum type contains at each pixel location in the image */ 257 | SpectrumType type() const; 258 | 259 | protected: 260 | size_t _width, _height; 261 | float _ev; 262 | 263 | // We can have up to 6 pixel buffers: 264 | // - 1 for emissive unpolarised images (S0) 265 | // - 1 for reflective unpolarised images (RE) 266 | // - 4 for emissive polarised images (S0, S1, S2, S3) 267 | 268 | std::vector _reflectivePixelBuffer; 269 | std::array, 4> _emissivePixelBuffers; 270 | 271 | std::vector _wavelengths_nm; 272 | SpectrumType _spectrumType; 273 | 274 | PolarisationHandedness _polarisationHandedness; 275 | 276 | SpectrumAttribute _lensTransmissionSpectra; 277 | SpectrumAttribute _cameraReponse; 278 | std::vector _channelSensitivities; 279 | }; 280 | 281 | } // namespace SEXR 282 | -------------------------------------------------------------------------------- /lib/include/SpectrumAttribute.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | #include 40 | #include 41 | 42 | namespace SEXR 43 | { 44 | class SpectrumAttribute 45 | { 46 | public: 47 | enum Error 48 | { 49 | NOT_SAME_VECTOR_SIZE, 50 | PARSING_ERROR 51 | }; 52 | 53 | SpectrumAttribute(); 54 | 55 | SpectrumAttribute( 56 | std::vector wavelengths_nm, std::vector values); 57 | 58 | SpectrumAttribute(const Imf::StringAttribute &attributeValue); 59 | 60 | Imf::StringAttribute getAttribute() const; 61 | 62 | std::vector & wavelengths_nm() { return _wavelengths_nm; } 63 | const std::vector &wavelengths_nm() const 64 | { 65 | return _wavelengths_nm; 66 | } 67 | 68 | std::vector & values() { return _values; } 69 | const std::vector &values() const { return _values; } 70 | 71 | float & wavelength_nm(size_t i) { return _wavelengths_nm[i]; } 72 | const float &wavelength_nm(size_t i) const 73 | { 74 | return _wavelengths_nm[i]; 75 | } 76 | 77 | float & value(size_t i) { return _values[i]; } 78 | const float &value(size_t i) const { return _values[i]; } 79 | 80 | size_t size() const { return _wavelengths_nm.size(); } 81 | 82 | protected: 83 | std::vector _wavelengths_nm; 84 | std::vector _values; 85 | }; 86 | 87 | } // namespace SEXR 88 | -------------------------------------------------------------------------------- /lib/include/SpectrumType.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2020 - 2021 3 | * Alban Fichet, Romain Pacanowski, Alexander Wilkie 4 | * Institut d'Optique Graduate School, CNRS - Universite de Bordeaux, 5 | * Inria, Charles University 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions 10 | * are met: 11 | * 12 | * * Redistributions of source code must retain the above copyright 13 | * notice, this list of conditions and the following disclaimer. 14 | * * Redistributions in binary form must reproduce the above 15 | * copyright notice, this list of conditions and the following 16 | * disclaimer in the documentation and/or other materials provided 17 | * with the distribution. 18 | * * Neither the name of Institut d'Optique Graduate School, CNRS - 19 | * Universite de Bordeaux, Inria, Charles University nor the names of 20 | * its contributors may be used to endorse or promote products derived 21 | * from this software without specific prior written permission. 22 | * 23 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 26 | * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 27 | * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 28 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 30 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 31 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 32 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 34 | * OF THE POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma once 38 | 39 | namespace SEXR 40 | { 41 | enum SpectrumType 42 | { 43 | UNDEFINED = 0, // 0b0000 44 | REFLECTIVE = 2, // 0b0001 45 | EMISSIVE = 4, // 0b0010 46 | BISPECTRAL = 8 | REFLECTIVE, // 0b0101 47 | POLARISED = 16 // 0b1000 48 | }; 49 | 50 | 51 | inline SpectrumType operator|(SpectrumType a, SpectrumType b) 52 | { 53 | return static_cast( 54 | static_cast(a) | static_cast(b)); 55 | } 56 | 57 | inline SpectrumType operator^(SpectrumType a, SpectrumType b) 58 | { 59 | return static_cast( 60 | static_cast(a) ^ static_cast(b)); 61 | } 62 | 63 | inline bool isReflectiveSpectrum(SpectrumType s) 64 | { 65 | return (s & REFLECTIVE) == REFLECTIVE; 66 | } 67 | 68 | inline bool isEmissiveSpectrum(SpectrumType s) 69 | { 70 | return (s & EMISSIVE) == EMISSIVE; 71 | } 72 | 73 | inline bool isPolarisedSpectrum(SpectrumType s) 74 | { 75 | return (s & POLARISED) == POLARISED; 76 | } 77 | 78 | inline bool isBispectralSpectrum(SpectrumType s) 79 | { 80 | return (s & BISPECTRAL) == BISPECTRAL; 81 | } 82 | 83 | } // namespace SEXR 84 | -------------------------------------------------------------------------------- /python/data/cmf/ciexyz31.csv: -------------------------------------------------------------------------------- 1 | 360,0.000129900000,0.000003917000,0.000606100000 2 | 365,0.000232100000,0.000006965000,0.001086000000 3 | 370,0.000414900000,0.000012390000,0.001946000000 4 | 375,0.000741600000,0.000022020000,0.003486000000 5 | 380,0.001368000000,0.000039000000,0.006450001000 6 | 385,0.002236000000,0.000064000000,0.010549990000 7 | 390,0.004243000000,0.000120000000,0.020050010000 8 | 395,0.007650000000,0.000217000000,0.036210000000 9 | 400,0.014310000000,0.000396000000,0.067850010000 10 | 405,0.023190000000,0.000640000000,0.110200000000 11 | 410,0.043510000000,0.001210000000,0.207400000000 12 | 415,0.077630000000,0.002180000000,0.371300000000 13 | 420,0.134380000000,0.004000000000,0.645600000000 14 | 425,0.214770000000,0.007300000000,1.039050100000 15 | 430,0.283900000000,0.011600000000,1.385600000000 16 | 435,0.328500000000,0.016840000000,1.622960000000 17 | 440,0.348280000000,0.023000000000,1.747060000000 18 | 445,0.348060000000,0.029800000000,1.782600000000 19 | 450,0.336200000000,0.038000000000,1.772110000000 20 | 455,0.318700000000,0.048000000000,1.744100000000 21 | 460,0.290800000000,0.060000000000,1.669200000000 22 | 465,0.251100000000,0.073900000000,1.528100000000 23 | 470,0.195360000000,0.090980000000,1.287640000000 24 | 475,0.142100000000,0.112600000000,1.041900000000 25 | 480,0.095640000000,0.139020000000,0.812950100000 26 | 485,0.057950010000,0.169300000000,0.616200000000 27 | 490,0.032010000000,0.208020000000,0.465180000000 28 | 495,0.014700000000,0.258600000000,0.353300000000 29 | 500,0.004900000000,0.323000000000,0.272000000000 30 | 505,0.002400000000,0.407300000000,0.212300000000 31 | 510,0.009300000000,0.503000000000,0.158200000000 32 | 515,0.029100000000,0.608200000000,0.111700000000 33 | 520,0.063270000000,0.710000000000,0.078249990000 34 | 525,0.109600000000,0.793200000000,0.057250010000 35 | 530,0.165500000000,0.862000000000,0.042160000000 36 | 535,0.225749900000,0.914850100000,0.029840000000 37 | 540,0.290400000000,0.954000000000,0.020300000000 38 | 545,0.359700000000,0.980300000000,0.013400000000 39 | 550,0.433449900000,0.994950100000,0.008749999000 40 | 555,0.512050100000,1.000000000000,0.005749999000 41 | 560,0.594500000000,0.995000000000,0.003900000000 42 | 565,0.678400000000,0.978600000000,0.002749999000 43 | 570,0.762100000000,0.952000000000,0.002100000000 44 | 575,0.842500000000,0.915400000000,0.001800000000 45 | 580,0.916300000000,0.870000000000,0.001650001000 46 | 585,0.978600000000,0.816300000000,0.001400000000 47 | 590,1.026300000000,0.757000000000,0.001100000000 48 | 595,1.056700000000,0.694900000000,0.001000000000 49 | 600,1.062200000000,0.631000000000,0.000800000000 50 | 605,1.045600000000,0.566800000000,0.000600000000 51 | 610,1.002600000000,0.503000000000,0.000340000000 52 | 615,0.938400000000,0.441200000000,0.000240000000 53 | 620,0.854449900000,0.381000000000,0.000190000000 54 | 625,0.751400000000,0.321000000000,0.000100000000 55 | 630,0.642400000000,0.265000000000,0.000049999990 56 | 635,0.541900000000,0.217000000000,0.000030000000 57 | 640,0.447900000000,0.175000000000,0.000020000000 58 | 645,0.360800000000,0.138200000000,0.000010000000 59 | 650,0.283500000000,0.107000000000,0.000000000000 60 | 655,0.218700000000,0.081600000000,0.000000000000 61 | 660,0.164900000000,0.061000000000,0.000000000000 62 | 665,0.121200000000,0.044580000000,0.000000000000 63 | 670,0.087400000000,0.032000000000,0.000000000000 64 | 675,0.063600000000,0.023200000000,0.000000000000 65 | 680,0.046770000000,0.017000000000,0.000000000000 66 | 685,0.032900000000,0.011920000000,0.000000000000 67 | 690,0.022700000000,0.008210000000,0.000000000000 68 | 695,0.015840000000,0.005723000000,0.000000000000 69 | 700,0.011359160000,0.004102000000,0.000000000000 70 | 705,0.008110916000,0.002929000000,0.000000000000 71 | 710,0.005790346000,0.002091000000,0.000000000000 72 | 715,0.004109457000,0.001484000000,0.000000000000 73 | 720,0.002899327000,0.001047000000,0.000000000000 74 | 725,0.002049190000,0.000740000000,0.000000000000 75 | 730,0.001439971000,0.000520000000,0.000000000000 76 | 735,0.000999949300,0.000361100000,0.000000000000 77 | 740,0.000690078600,0.000249200000,0.000000000000 78 | 745,0.000476021300,0.000171900000,0.000000000000 79 | 750,0.000332301100,0.000120000000,0.000000000000 80 | 755,0.000234826100,0.000084800000,0.000000000000 81 | 760,0.000166150500,0.000060000000,0.000000000000 82 | 765,0.000117413000,0.000042400000,0.000000000000 83 | 770,0.000083075270,0.000030000000,0.000000000000 84 | 775,0.000058706520,0.000021200000,0.000000000000 85 | 780,0.000041509940,0.000014990000,0.000000000000 86 | 785,0.000029353260,0.000010600000,0.000000000000 87 | 790,0.000020673830,0.000007465700,0.000000000000 88 | 795,0.000014559770,0.000005257800,0.000000000000 89 | 800,0.000010253980,0.000003702900,0.000000000000 90 | 805,0.000007221456,0.000002607800,0.000000000000 91 | 810,0.000005085868,0.000001836600,0.000000000000 92 | 815,0.000003581652,0.000001293400,0.000000000000 93 | 820,0.000002522525,0.000000910930,0.000000000000 94 | 825,0.000001776509,0.000000641530,0.000000000000 95 | 830,0.000001251141,0.000000451810,0.000000000000 -------------------------------------------------------------------------------- /python/data/image/D65.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afichet/spectral-exr/49235c0a09171b50782662c12c361e4f1c8da7da/python/data/image/D65.exr -------------------------------------------------------------------------------- /python/data/image/Macbeth.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afichet/spectral-exr/49235c0a09171b50782662c12c361e4f1c8da7da/python/data/image/Macbeth.exr -------------------------------------------------------------------------------- /python/data/image/PolarisedRendering.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afichet/spectral-exr/49235c0a09171b50782662c12c361e4f1c8da7da/python/data/image/PolarisedRendering.exr -------------------------------------------------------------------------------- /python/radiometry/cmf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | :mod: `cmf` -- Colour Matching Functions handling 5 | ================================================= 6 | .. module:: cmf 7 | :synopsis: This modules allows conversion from spectral image to colour 8 | image given a set of colour matching functions. 9 | .. moduleauthor:: Alban Fichet 10 | """ 11 | 12 | import numpy as np 13 | import scipy.interpolate 14 | 15 | class CMF: 16 | def __init__(self, filename, sampling=2): 17 | """ 18 | Initialise an object for colour conversion. 19 | 20 | :param filename CSV file with the colour matching functions. 21 | :param sampling Used to lower the accuracy but gain speed. 22 | """ 23 | x_bar = [] 24 | y_bar = [] 25 | z_bar = [] 26 | 27 | # Read the CSV file 28 | with open(filename) as f: 29 | for l in f: 30 | wl, x, y, z = [float(el) for el in l.split(',')] 31 | x_bar.append([wl, x]) 32 | y_bar.append([wl, y]) 33 | z_bar.append([wl, z]) 34 | 35 | x_bar = np.array(x_bar) 36 | y_bar = np.array(y_bar) 37 | z_bar = np.array(z_bar) 38 | 39 | # Interpolate every 1nm to ease computations 40 | self.wl_start = np.min(x_bar[:, 0]) 41 | self.wl_end = np.max(x_bar[:, 0]) 42 | # self.wl = np.linspace(self.wl_start, self.wl_end, num=int(self.wl_end - self.wl_start + 1)) 43 | 44 | # let's be a little bit less hardcore here... 45 | self.wl = np.arange(self.wl_start, self.wl_end, sampling) 46 | 47 | x_bar_y = np.interp(self.wl, x_bar[:, 0], x_bar[:, 1], left=0, right=0) 48 | y_bar_y = np.interp(self.wl, y_bar[:, 0], y_bar[:, 1], left=0, right=0) 49 | z_bar_y = np.interp(self.wl, z_bar[:, 0], z_bar[:, 1], left=0, right=0) 50 | 51 | self.x_bar = np.stack((self.wl, x_bar_y), axis=1) 52 | self.y_bar = np.stack((self.wl, y_bar_y), axis=1) 53 | self.z_bar = np.stack((self.wl, z_bar_y), axis=1) 54 | 55 | 56 | def get_xyz_emissive_img(self, image_wl, spectral_image): 57 | """ 58 | Converts an emissive spectral image stored in a numpy array 59 | (y, x, bands) to a colour image. 60 | 61 | :param image_wl list containing the wavelength values in nanometers 62 | for the provided spectral image 63 | :param spectral_image numpy array storing the spectral image 64 | """ 65 | interp_image_f = scipy.interpolate.interp1d(image_wl, spectral_image, bounds_error=False, fill_value=(0, 0)) 66 | interp_image = np.maximum(interp_image_f(self.wl), 0) 67 | 68 | delta = self.wl[1] - self.wl[0] 69 | 70 | s_x = np.sum(interp_image * self.x_bar[:, 1], axis=-1) * delta 71 | s_y = np.sum(interp_image * self.y_bar[:, 1], axis=-1) * delta 72 | s_z = np.sum(interp_image * self.z_bar[:, 1], axis=-1) * delta 73 | 74 | return np.dstack((s_x, s_y, s_z)) 75 | 76 | 77 | def get_xyz_reflective_img(self, wavelength_illu, spectrum_illu, image_wl, spectral_image): 78 | """ 79 | Converts an reflective spectral image stored in a numpy array 80 | (y, x, bands) to a colour image. 81 | 82 | :param wavelnegth_illu list of the wavlengths of the illuminant 83 | spectrum 84 | :param spectrum_illu list of the corresponding radiance of the 85 | illuminant spectrum 86 | :param image_wl list containing the wavelength values in nanometers 87 | for the provided spectral image 88 | :param spectral_image numpy array storing the spectral image 89 | """ 90 | illu_values = np.interp(self.wl, wavelength_illu, spectrum_illu, left=0, right=0) 91 | 92 | interp_image_f = scipy.interpolate.interp1d(image_wl, spectral_image, bounds_error=False, fill_value=(0, 0)) 93 | interp_image = np.maximum(interp_image_f(self.wl), 0) 94 | 95 | delta = self.wl[1] - self.wl[0] 96 | 97 | Y_illu = np.sum(illu_values * self.y_bar[:, 1], axis=-1) * delta 98 | 99 | s_x = np.sum(interp_image * illu_values * self.x_bar[:, 1], axis=-1) / Y_illu * delta 100 | s_y = np.sum(interp_image * illu_values * self.y_bar[:, 1], axis=-1) / Y_illu * delta 101 | s_z = np.sum(interp_image * illu_values * self.z_bar[:, 1], axis=-1) / Y_illu * delta 102 | 103 | return np.dstack((s_x, s_y, s_z)) --------------------------------------------------------------------------------