Skip to content

Commit f1a0e50

Browse files
committed
[1.3.66] 2026-03-05
## Core - Added `writeEXR()` functions for writing single-channel and multi-channel float images to OpenEXR files with lossless ZIP compression, using the tinyexr header-only library. ## Radiation - Added `writeCameraImageDataEXR()` and `writeDepthImageDataEXR()` methods for exporting camera and depth data to EXR files, preserving full floating-point precision. ## Visualizer - Fixed bug in colorbar ticks where ticks could extend past the colorbar. ## Radiation - Added OptiX 8.1 ray tracing backend for NVIDIA systems with driver ≥ 560. This resolves the driver 590+ incompatibility that prevented the OptiX 6.5 backend from working on modern NVIDIA drivers. The backend is selected automatically at build time: driver ≥ 560 uses OptiX 8.1; driver < 560 continues to use OptiX 6.5.
1 parent 6192e13 commit f1a0e50

46 files changed

Lines changed: 14230 additions & 55 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ add_dependencies( png_static zlibstatic )
4545
include_directories("${CMAKE_BINARY_DIR}/lib/libjpeg-9a" "${CMAKE_CURRENT_SOURCE_DIR}/lib/libjpeg-9a")
4646
add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/lib/libjpeg-9a" "${CMAKE_BINARY_DIR}/lib/libjpeg-9a")
4747

48+
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/lib/tinyexr")
49+
4850
target_link_libraries( helios PRIVATE png_static jpeg ) #note that zlib is already linked by libpng
4951

5052
# Suppress warnings for third-party libraries

core/include/global.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,26 @@ namespace helios {
878878
*/
879879
void writeJPEG(const std::string &filename, uint width, uint height, const std::vector<unsigned char> &pixel_data);
880880

881+
//! Write a single-channel float image to an EXR file with lossless ZIP compression
882+
/**
883+
* \param[in] filename Name of the EXR image file
884+
* \param[in] width Image width in pixels
885+
* \param[in] height Image height in pixels
886+
* \param[in] pixel_data Float values at each pixel (index at pixel_data[row*width+column])
887+
* \param[in] channel_name Name of the EXR channel (default "Y" for grayscale; use "Z" for depth)
888+
*/
889+
void writeEXR(const std::string &filename, uint width, uint height, const std::vector<float> &pixel_data, const std::string &channel_name = "Y");
890+
891+
//! Write a multi-channel float image to an EXR file with lossless ZIP compression
892+
/**
893+
* \param[in] filename Name of the EXR image file
894+
* \param[in] width Image width in pixels
895+
* \param[in] height Image height in pixels
896+
* \param[in] channel_data Vector of per-channel float data, each of length width*height
897+
* \param[in] channel_names Names for each channel (e.g., {"R", "G", "B"}). Channels are sorted alphabetically per EXR convention.
898+
*/
899+
void writeEXR(const std::string &filename, uint width, uint height, const std::vector<std::vector<float>> &channel_data, const std::vector<std::string> &channel_names);
900+
881901
//! Template function to flatten a 2D vector into a 1D vector
882902
/**
883903
* \ingroup functions

core/src/global.cpp

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ extern "C" {
2727
#include "jpeglib.h"
2828
}
2929

30+
// EXR Libraries (reading and writing OpenEXR images)
31+
#define TINYEXR_USE_MINIZ 0
32+
#include "zlib.h"
33+
#define TINYEXR_IMPLEMENTATION
34+
#include "tinyexr.h"
35+
3036
using namespace helios;
3137

3238
void helios::helios_runtime_error(const std::string &error_message) {
@@ -2060,6 +2066,128 @@ void helios::writeJPEG(const std::string &a_filename, uint width, uint height, c
20602066
writeJPEG(a_filename, width, height, rgb_data);
20612067
}
20622068

2069+
void helios::writeEXR(const std::string &filename, uint width, uint height, const std::vector<float> &pixel_data, const std::string &channel_name) {
2070+
2071+
if (pixel_data.size() != width * height) {
2072+
helios_runtime_error("ERROR (writeEXR): pixel_data size (" + std::to_string(pixel_data.size()) + ") does not match width*height (" + std::to_string(width * height) + ").");
2073+
}
2074+
2075+
EXRHeader header;
2076+
InitEXRHeader(&header);
2077+
2078+
EXRImage image;
2079+
InitEXRImage(&image);
2080+
2081+
image.num_channels = 1;
2082+
image.width = scast<int>(width);
2083+
image.height = scast<int>(height);
2084+
2085+
float *image_ptr[1];
2086+
image_ptr[0] = const_cast<float *>(pixel_data.data());
2087+
2088+
image.images = reinterpret_cast<unsigned char **>(image_ptr);
2089+
2090+
header.num_channels = 1;
2091+
header.channels = scast<EXRChannelInfo *>(malloc(sizeof(EXRChannelInfo)));
2092+
strncpy(header.channels[0].name, channel_name.c_str(), 255);
2093+
header.channels[0].name[255] = '\0';
2094+
2095+
header.pixel_types = scast<int *>(malloc(sizeof(int)));
2096+
header.requested_pixel_types = scast<int *>(malloc(sizeof(int)));
2097+
header.pixel_types[0] = TINYEXR_PIXELTYPE_FLOAT;
2098+
header.requested_pixel_types[0] = TINYEXR_PIXELTYPE_FLOAT;
2099+
2100+
header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
2101+
2102+
const char *err = nullptr;
2103+
int ret = SaveEXRImageToFile(&image, &header, filename.c_str(), &err);
2104+
2105+
free(header.channels);
2106+
free(header.pixel_types);
2107+
free(header.requested_pixel_types);
2108+
2109+
if (ret != TINYEXR_SUCCESS) {
2110+
std::string error_msg = "ERROR (writeEXR): Failed to write EXR file '" + filename + "'";
2111+
if (err) {
2112+
error_msg += ": " + std::string(err);
2113+
FreeEXRErrorMessage(err);
2114+
}
2115+
helios_runtime_error(error_msg);
2116+
}
2117+
}
2118+
2119+
void helios::writeEXR(const std::string &filename, uint width, uint height, const std::vector<std::vector<float>> &channel_data, const std::vector<std::string> &channel_names) {
2120+
2121+
if (channel_data.size() != channel_names.size()) {
2122+
helios_runtime_error("ERROR (writeEXR): channel_data size (" + std::to_string(channel_data.size()) + ") does not match channel_names size (" + std::to_string(channel_names.size()) + ").");
2123+
}
2124+
if (channel_data.empty()) {
2125+
helios_runtime_error("ERROR (writeEXR): channel_data is empty.");
2126+
}
2127+
for (size_t c = 0; c < channel_data.size(); c++) {
2128+
if (channel_data[c].size() != width * height) {
2129+
helios_runtime_error("ERROR (writeEXR): channel_data[" + std::to_string(c) + "] size (" + std::to_string(channel_data[c].size()) + ") does not match width*height (" + std::to_string(width * height) + ").");
2130+
}
2131+
}
2132+
2133+
int num_channels = scast<int>(channel_data.size());
2134+
2135+
// Sort channels alphabetically (EXR convention)
2136+
std::vector<size_t> sort_indices(num_channels);
2137+
for (size_t i = 0; i < sort_indices.size(); i++) {
2138+
sort_indices[i] = i;
2139+
}
2140+
std::sort(sort_indices.begin(), sort_indices.end(), [&](size_t a, size_t b) {
2141+
return channel_names[a] < channel_names[b];
2142+
});
2143+
2144+
EXRHeader header;
2145+
InitEXRHeader(&header);
2146+
2147+
EXRImage image;
2148+
InitEXRImage(&image);
2149+
2150+
image.num_channels = num_channels;
2151+
image.width = scast<int>(width);
2152+
image.height = scast<int>(height);
2153+
2154+
std::vector<float *> image_ptrs(num_channels);
2155+
for (int c = 0; c < num_channels; c++) {
2156+
image_ptrs[c] = const_cast<float *>(channel_data[sort_indices[c]].data());
2157+
}
2158+
image.images = reinterpret_cast<unsigned char **>(image_ptrs.data());
2159+
2160+
header.num_channels = num_channels;
2161+
header.channels = scast<EXRChannelInfo *>(malloc(sizeof(EXRChannelInfo) * num_channels));
2162+
header.pixel_types = scast<int *>(malloc(sizeof(int) * num_channels));
2163+
header.requested_pixel_types = scast<int *>(malloc(sizeof(int) * num_channels));
2164+
2165+
for (int c = 0; c < num_channels; c++) {
2166+
strncpy(header.channels[c].name, channel_names[sort_indices[c]].c_str(), 255);
2167+
header.channels[c].name[255] = '\0';
2168+
header.pixel_types[c] = TINYEXR_PIXELTYPE_FLOAT;
2169+
header.requested_pixel_types[c] = TINYEXR_PIXELTYPE_FLOAT;
2170+
}
2171+
2172+
header.compression_type = TINYEXR_COMPRESSIONTYPE_ZIP;
2173+
2174+
const char *err = nullptr;
2175+
int ret = SaveEXRImageToFile(&image, &header, filename.c_str(), &err);
2176+
2177+
free(header.channels);
2178+
free(header.pixel_types);
2179+
free(header.requested_pixel_types);
2180+
2181+
if (ret != TINYEXR_SUCCESS) {
2182+
std::string error_msg = "ERROR (writeEXR): Failed to write EXR file '" + filename + "'";
2183+
if (err) {
2184+
error_msg += ": " + std::string(err);
2185+
FreeEXRErrorMessage(err);
2186+
}
2187+
helios_runtime_error(error_msg);
2188+
}
2189+
}
2190+
20632191
helios::vec3 helios::spline_interp3(float u, const vec3 &x_start, const vec3 &tan_start, const vec3 &x_end, const vec3 &tan_end) {
20642192
// Perform interpolation between two 3D points using Cubic Hermite Spline
20652193

doc/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Changelog
22

3+
# [1.3.66] 2026-03-05
4+
5+
## Core
6+
- Added `writeEXR()` functions for writing single-channel and multi-channel float images to OpenEXR files with lossless ZIP compression, using the tinyexr header-only library.
7+
8+
## Radiation
9+
- Added `writeCameraImageDataEXR()` and `writeDepthImageDataEXR()` methods for exporting camera and depth data to EXR files, preserving full floating-point precision.
10+
11+
## Visualizer
12+
- Fixed bug in colorbar ticks where ticks could extend past the colorbar.
13+
14+
## Radiation
15+
- Added OptiX 8.1 ray tracing backend for NVIDIA systems with driver ≥ 560. This resolves the driver 590+ incompatibility that prevented the OptiX 6.5 backend from working on modern NVIDIA drivers. The backend is selected automatically at build time: driver ≥ 560 uses OptiX 8.1; driver < 560 continues to use OptiX 6.5.
16+
317
# [1.3.65] 2026-02-27
418

519
## Core

doc/UserGuide.dox

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -251,13 +251,15 @@
251251
4. Double-click TdrDelay and add 600 for the Value data and make it a Decimal (instead of Hexadecimal). Click OK. If you encounter the same "Display driver stopped responding and has recovered" error in the future, increase the value.
252252
5. Close the registry editor and restart the computer for the changes to take effect.
253253

254-
\subsubsection OptiXWSL Manually installing OptiX if using Windows Subsystem for Linux (WSL)
254+
\subsubsection OptiXWSL Running OptiX under Windows Subsystem for Linux (WSL)
255255

256-
OptiX is normally packaged with Helios, such that you do not have to worry about installing it. However, if you are using Windows Subsystem for Linux (WSL), you will need to manually install OptiX since the version included with Helios does not have the required drivers for WSL. You can follow the instructions on this site to perform the installation: <a href="https://forums.developer.nvidia.com/t/problem-running-optix-7-6-in-wsl/239355/7">https://forums.developer.nvidia.com/t/problem-running-optix-7-6-in-wsl/239355/7</a>.
256+
Both the OptiX 8.1 and OptiX 6.5 backends are bundled with Helios and do not require a separate OptiX SDK installation. The OptiX runtime is loaded from the NVIDIA driver at runtime via <tt>optixInit()</tt>, so as long as the NVIDIA driver is installed in WSL, no additional steps are needed.
257257

258-
Alternatively, simply run the dependencies script found in the utilities/ folder: `source dependencies.sh`
258+
If you encounter issues finding the NVIDIA driver inside WSL, run the dependencies script in the utilities/ folder:
259259

260-
This will automatically install and configure the Linux drivers (version 470.256.02) to run OptiX.
260+
<tt>source dependencies.sh</tt>
261+
262+
This will automatically install and configure the Linux driver components needed to run CUDA and OptiX inside WSL.
261263

262264
\subsection SetupPCVulkan Setting up Vulkan for the Radiation Model
263265

@@ -2055,13 +2057,21 @@ The table below gives recommendations for Windows and Linux Systems based on you
20552057

20562058
\section chooseOptiX OptiX Version
20572059

2058-
The NVIDIA OptiX library is used for ray-tracing calculations associated with the radiation model plug-in. Helios comes packaged with two versions of OptiX: version 5.1 (which we term "legacy") and version 6.5. The newer version (6.5) is what is used by default. If you are using a GPU with compute capability <5.0, you will need to enable the legacy version (see table above).
2060+
The NVIDIA OptiX library is used for ray-tracing calculations in the radiation model. Helios bundles three versions of OptiX and selects among them automatically based on your NVIDIA driver version:
2061+
2062+
| Driver version | OptiX version used | CUDA requirement |
2063+
|---|---|---|
2064+
| ≥ 560 | OptiX 8.1 (default, recommended) | CUDA 12.0+ |
2065+
| &lt; 560 | OptiX 6.5 (legacy) | CUDA 9.0+ |
2066+
| Any (or no NVIDIA GPU) | Vulkan compute backend | None |
2067+
2068+
\note NVIDIA drivers 590.x and later dropped support for OptiX 6.5. If you have a modern GPU and driver, Helios automatically uses OptiX 8.1, which supports all current and future NVIDIA drivers. The driver 590+ incompatibility that existed in earlier Helios versions is fully resolved.
20592069

2060-
There are several ways to tell Helios to build with the legacy OptiX version, all of which involve setting a CMake variable:
2070+
<b>Legacy GPUs (compute capability 3.5):</b> If you are using a GPU with compute capability &lt; 5.0, you can force the OptiX 6.5 legacy backend by setting <tt>-DOPTIX_VERSION_LEGACY=ON</tt>. There are two ways to do this:
20612071

2062-
1. Pass option to CMake: To always build every project with the legacy OptiX version, it is easiest to set the following CMake option in CLion: "-DOPTIX_VERSION_LEGACY=ON". Go to CLion settings, then "build, execution, deployment -> CMake" Then type "-DOPTIX_VERSION_LEGACY=ON" into the CMake Options box. You can also pass this option to CMake if building from the command line.
2072+
1. Pass option to CMake: Set the following CMake option in CLion: "-DOPTIX_VERSION_LEGACY=ON". Go to CLion settings, then "build, execution, deployment -> CMake", and type "-DOPTIX_VERSION_LEGACY=ON" into the CMake Options box. You can also pass this option to CMake if building from the command line.
20632073

2064-
2. Add the variable directly in your project CMakeLists.txt file: You can add the line "set( OPTIX_VERSION_LEGACY ON )" to your project CMakeLists.txt file.
2074+
2. Add the variable directly in your project CMakeLists.txt file: Add the line <tt>set( OPTIX_VERSION_LEGACY ON )</tt> to your project CMakeLists.txt file.
20652075

20662076

20672077
*/

0 commit comments

Comments
 (0)