Skip to content

Commit

Permalink
Add Holoviz HDR feature sample. (nvidia-holoscan#501)
Browse files Browse the repository at this point in the history
Signed-off-by: Andreas Heumann <[email protected]>
  • Loading branch information
AndreasHeumann authored Oct 8, 2024
1 parent 2eeab2a commit b02dfcb
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 66 deletions.
1 change: 1 addition & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,7 @@
"description": "Select Holoviz example program",
"type": "pickString",
"options": [
"holoviz_hdr",
"holoviz_srgb",
"holoviz_vsync",
"holoviz_yuv",
Expand Down
29 changes: 23 additions & 6 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,11 @@
},
{
"type": "shell",
"label": "Build holoviz_vsync",
"label": "Build holoviz_hdr",
"command": "./run",
"args": [
"build",
"holoviz_vsync",
"holoviz_hdr",
"--type",
"debug"
],
Expand All @@ -341,11 +341,11 @@
},
{
"type": "shell",
"label": "Build holoviz_yuv",
"label": "Build holoviz_srgb",
"command": "./run",
"args": [
"build",
"holoviz_yuv",
"holoviz_srgb",
"--type",
"debug"
],
Expand All @@ -358,11 +358,28 @@
},
{
"type": "shell",
"label": "Build holoviz_srgb",
"label": "Build holoviz_vsync",
"command": "./run",
"args": [
"build",
"holoviz_srgb",
"holoviz_vsync",
"--type",
"debug"
],
"options": {
"cwd": "${env:WORKSPACE_DIR}"
},
"group": "build",
"problemMatcher": [],
"detail": "CMake template build task"
},
{
"type": "shell",
"label": "Build holoviz_yuv",
"command": "./run",
"args": [
"build",
"holoviz_yuv",
"--type",
"debug"
],
Expand Down
1 change: 1 addition & 0 deletions applications/holoviz/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

add_holohub_application(holoviz_hdr)
add_holohub_application(holoviz_srgb)
add_holohub_application(holoviz_vsync)
add_holohub_application(holoviz_yuv)
41 changes: 41 additions & 0 deletions applications/holoviz/holoviz_hdr/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

cmake_minimum_required(VERSION 3.20)

project(holoviz_hdr)

find_package(holoscan 2.5 REQUIRED CONFIG
PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")

add_executable(holoviz_hdr
holoviz_hdr.cpp
)

target_link_libraries(holoviz_hdr
PRIVATE
holoscan::core
holoscan::ops::holoviz
)

if(BUILD_TESTING)
# Add test
add_test(NAME holoviz_hdr_test
COMMAND holoviz_hdr
--count=10
WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
set_tests_properties(holoviz_hdr_test PROPERTIES
PASS_REGULAR_EXPRESSION "Application has finished running.")
endif()
23 changes: 23 additions & 0 deletions applications/holoviz/holoviz_hdr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Holoviz HDR

![](holoviz_hdr.png)
This application demonstartes displaying HDR images using the Holoviz operator. The applications creates image data in HDR10 (BT2020 color space) with SMPTE ST2084 Perceptual Quantizer (PQ) EOTF and displays the image on the screen.

Note that the screenshot above does not show the real HDR image on the display since it's not possible to take screenshots of HDR images.

The Holoviz operator parameter `display_color_space` is used to set the color space. This allows HDR output on Linux distributions and displays supporting that feature. See https://docs.nvidia.com/holoscan/sdk-user-guide/visualization.html#hdr for more information.

```cpp
auto holoviz = make_operator<ops::HolovizOp>(
"holoviz",
// select the HDR10 ST2084 display color space
Arg("display_color_space", ops::HolovizOp::ColorSpace::HDR10_ST2084));
```
## Run Instructions
To build and start the application:
```bash
./dev_container build_and_run holoviz_hdr
```
196 changes: 196 additions & 0 deletions applications/holoviz/holoviz_hdr/holoviz_hdr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include <holoscan/holoscan.hpp>
#include <holoscan/operators/holoviz/holoviz.hpp>
#include <string>

#include <getopt.h>

namespace holoscan::ops {

class SourceOp : public Operator {
public:
HOLOSCAN_OPERATOR_FORWARD_ARGS(SourceOp);

void initialize() override {
const int32_t width = 64, height = 64;
shape_ = nvidia::gxf::Shape{width, height, 4};
element_type_ = nvidia::gxf::PrimitiveType::kFloat32;
element_size_ = nvidia::gxf::PrimitiveTypeSize(element_type_);
strides_ = nvidia::gxf::ComputeTrivialStrides(shape_, element_size_);

data_.resize(strides_[0] * shape_.dimension(0));

// create an RGB image with smooth color transitions
for (size_t y = 0; y < shape_.dimension(0); ++y) {
for (size_t x = 0; x < shape_.dimension(1); ++x) {
float rgb[3];
for (size_t component = 0; component < 3; ++component) {
switch (component) {
case 0:
rgb[component] = float(x) / shape_.dimension(1);
break;
case 1:
rgb[component] = float(y) / shape_.dimension(0);
break;
case 2:
rgb[component] = 1.f - (float(x) / shape_.dimension(1));
break;
}

// create two regions, the top region has 100 nits
// the bottom region starts at 100 nits and ends at 500 nits
constexpr float max_luminance = 10000.f;
if (y < height / 2) {
rgb[component] *= 100.f / max_luminance;
} else {
rgb[component] *= (100.f + (float(x) / shape_.dimension(1)) * 500.f) / max_luminance;
}
}

// use the RGB data to generate data in HDR10 (BT2020 color space) with SMPTE ST2084
// Perceptual Quantizer (PQ) EOTF

float rgb_2020[3];
// linear to BT2020 color space conversion
// https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html#PRIMARIES_BT2020
rgb_2020[0] = std::clamp(
(0.636958f * rgb[0]) + (0.144617f * rgb[1]) + (0.168881f * rgb[2]), 0.f, 1.f);
rgb_2020[1] = std::clamp(
(0.262700f * rgb[0]) + (0.677998f * rgb[1]) + (0.059302f * rgb[2]), 0.f, 1.f);
rgb_2020[2] = std::clamp(
(0.000000f * rgb[0]) + (0.028073f * rgb[1]) + (1.060985f * rgb[2]), 0.f, 1.f);

// apply inverse SMPTE ST2084 Perceptual Quantizer (PQ) EOTF
constexpr float m1 = 2610.f / 16384.f;
constexpr float m2 = 2523.f / 4096.f * 128.f;
constexpr float c2 = 2413.f / 4096.f * 32.f;
constexpr float c3 = 2392.f / 4096.f * 32.f;
constexpr float c1 = c3 - c2 + 1.f;

for (size_t component = 0; component < 3; ++component) {
float lp = std::pow(rgb_2020[component], m1);
float value = std::pow((c1 + c2 * lp) / (1.f + c3 * lp), m2);

*reinterpret_cast<float*>(reinterpret_cast<uintptr_t>(data_.data()) + y * strides_[0] +
x * strides_[1] + component * strides_[2]) = value;
}
// alpha
*reinterpret_cast<float*>(reinterpret_cast<uintptr_t>(data_.data()) + y * strides_[0] +
x * strides_[1] + 3 * strides_[2]) = 1.f;
}
}

Operator::initialize();
}

void setup(OperatorSpec& spec) override { spec.output<holoscan::gxf::Entity>("output"); }

void compute(InputContext& input, OutputContext& output, ExecutionContext& context) override {
auto entity = holoscan::gxf::Entity::New(&context);
auto tensor = static_cast<nvidia::gxf::Entity&>(entity).add<nvidia::gxf::Tensor>("image");
tensor.value()->wrapMemory(shape_,
element_type_,
element_size_,
strides_,
nvidia::gxf::MemoryStorageType::kSystem,
data_.data(),
nullptr);
output.emit(entity, "output");
}

private:
nvidia::gxf::Shape shape_;
nvidia::gxf::PrimitiveType element_type_;
uint64_t element_size_;
nvidia::gxf::Tensor::stride_array_t strides_;
std::vector<float> data_;
};

} // namespace holoscan::ops

class App : public holoscan::Application {
public:
explicit App(int count) : count_(count) {}
App() = delete;

void compose() override {
using namespace holoscan;

auto source =
make_operator<ops::SourceOp>("source",
// stop application count
make_condition<CountCondition>("count-condition", count_));

auto holoviz = make_operator<ops::HolovizOp>(
"holoviz",
// select the HDR10 ST2084 display color space
Arg("display_color_space", ops::HolovizOp::ColorSpace::HDR10_ST2084),
Arg("window_title", std::string("Holoviz HDR")),
Arg("cuda_stream_pool", make_resource<CudaStreamPool>("cuda_stream_pool", 0, 0, 0, 1, 5)));

add_flow(source, holoviz, {{"output", "receivers"}});
}

const int count_;
};

int main(int argc, char** argv) {
int count = -1;

struct option long_options[] = {
{"help", no_argument, 0, 'h'}, {"count", optional_argument, 0, 'c'}, {0, 0, 0, 0}};

// parse options
while (true) {
int option_index = 0;

const int c = getopt_long(argc, argv, "hc:", long_options, &option_index);

if (c == -1) { break; }

const std::string argument(optarg ? optarg : "");
switch (c) {
case 'h':
std::cout << "Holoviz HDR" << std::endl
<< "Usage: " << argv[0] << " [options]" << std::endl
<< "Options:" << std::endl
<< " -h, --help Display this information" << std::endl
<< " -c <COUNT>, --count <COUNT> execute operators <COUNT> times (default "
"'-1' for unlimited)"
<< std::endl;
return 0;

case 'c':
count = stoi(argument);
break;

case '?':
// unknown option, error already printed by getop_long
break;
default:
holoscan::log_error("Unhandled option '{}'", static_cast<char>(c));
}
}

auto app = holoscan::make_application<App>(count);
app->run();

holoscan::log_info("Application has finished running.");
return 0;
}
1 change: 1 addition & 0 deletions applications/holoviz/holoviz_hdr/holoviz_hdr.png
35 changes: 35 additions & 0 deletions applications/holoviz/holoviz_hdr/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"application": {
"name": "Holoviz HDR",
"authors": [
{
"name": "Holoscan Team",
"affiliation": "NVIDIA"
}
],
"language": "C++",
"version": "1.0.0",
"changelog": {
"1.0": "Initial Release"
},
"holoscan_sdk": {
"minimum_required_version": "2.5",
"tested_versions": [
"2.5"
]
},
"platforms": [
"amd64",
"arm64"
],
"tags": [
"Holoviz HDR","BT.2020","ST.2084","EOTF"
],
"ranking": 1,
"dependencies": {},
"run": {
"command": "<holohub_app_bin>/holoviz_hdr",
"workdir": "holohub_bin"
}
}
}
Loading

0 comments on commit b02dfcb

Please sign in to comment.