diff --git a/applications/CMakeLists.txt b/applications/CMakeLists.txt
index 8f878223c..4f9685da7 100644
--- a/applications/CMakeLists.txt
+++ b/applications/CMakeLists.txt
@@ -53,6 +53,8 @@ add_subdirectory(h264)
add_holohub_application(high_speed_endoscopy DEPENDS
OPERATORS emergent_source)
+add_subdirectory(holoviz)
+
add_holohub_application(hyperspectral_segmentation)
add_holohub_application(multiai_endoscopy)
diff --git a/applications/holoviz/CMakeLists.txt b/applications/holoviz/CMakeLists.txt
new file mode 100644
index 000000000..842fcbfea
--- /dev/null
+++ b/applications/holoviz/CMakeLists.txt
@@ -0,0 +1,17 @@
+# 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.
+
+add_holohub_application(holoviz_srgb)
+add_holohub_application(holoviz_vsync)
diff --git a/applications/holoviz/holoviz_srgb/CMakeLists.txt b/applications/holoviz/holoviz_srgb/CMakeLists.txt
new file mode 100644
index 000000000..f9c959067
--- /dev/null
+++ b/applications/holoviz/holoviz_srgb/CMakeLists.txt
@@ -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_srgb)
+
+find_package(holoscan 2.3 REQUIRED CONFIG
+ PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
+
+add_executable(holoviz_srgb
+ holoviz_srgb.cpp
+)
+
+target_link_libraries(holoviz_srgb
+ PRIVATE
+ holoscan::core
+ holoscan::ops::holoviz
+ )
+
+if(BUILD_TESTING)
+ # Add test
+ add_test(NAME holoviz_srgb_test
+ COMMAND holoviz_srgb
+ --count=10
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+ set_tests_properties(holoviz_srgb_test PROPERTIES
+ PASS_REGULAR_EXPRESSION "Application has finished running.")
+endif()
diff --git a/applications/holoviz/holoviz_srgb/README.md b/applications/holoviz/holoviz_srgb/README.md
new file mode 100644
index 000000000..ea1d81357
--- /dev/null
+++ b/applications/holoviz/holoviz_srgb/README.md
@@ -0,0 +1,39 @@
+# Holoviz sRGB
+
+![](holoviz_srgb.png)
+This application demonstrates the handling of the sRGB color space supported by the Holoviz operator.
+
+The Holoviz operator can convert sRGB input images to linear color space before rendering and also can convert from linear color space to sRGB before writing to the frame buffer.
+
+sRGB color space can be enabled for input images and for the frame buffer independently. By default, the sRGB color space is disabled for both.
+
+By default, the Holoviz operator is auto detecting the input image format. Auto detection always assumes linear color space for input images. To change this to sRGB color space explicitly set the `image_format_` member of the input spec for that input image to a format ending with `SRGB`:
+
+```cpp
+ // By default the image format is auto detected. Auto detection assumes linear color space,
+ // but we provide an sRGB encoded image. Create an input spec and change the image format to
+ // sRGB.
+ ops::HolovizOp::InputSpec input_spec("image", ops::HolovizOp::InputType::COLOR);
+ input_spec.image_format_ = ops::HolovizOp::ImageFormat::R8G8B8_SRGB;
+
+ auto holoviz = make_operator(
+ "holoviz",
+ Arg("tensors", std::vector{input_spec}));
+```
+
+By default, the frame buffer is using linear color space. To use the sRGB color space, set the `framebuffer_srbg` argument of the Holoviz operator to `true`:
+
+```cpp
+ auto holoviz = make_operator(
+ "holoviz",
+ // enable the sRGB frame buffer
+ Arg("framebuffer_srbg", true));
+```
+
+## Run Instructions
+
+To build and start the application:
+
+```bash
+./dev_container build_and_run holoviz_srgb
+```
diff --git a/applications/holoviz/holoviz_srgb/holoviz_srgb.cpp b/applications/holoviz/holoviz_srgb/holoviz_srgb.cpp
new file mode 100644
index 000000000..e0f918473
--- /dev/null
+++ b/applications/holoviz/holoviz_srgb/holoviz_srgb.cpp
@@ -0,0 +1,175 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2023 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
+#include
+#include
+
+#include
+
+namespace holoscan::ops {
+
+class SourceOp : public Operator {
+ public:
+ HOLOSCAN_OPERATOR_FORWARD_ARGS(SourceOp);
+
+ void initialize() override {
+ shape_ = nvidia::gxf::Shape{64, 64, 3};
+ element_type_ = nvidia::gxf::PrimitiveType::kUnsigned8;
+ 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 random values
+ for (size_t y = 0; y < shape_.dimension(0); ++y) {
+ for (size_t x = 0; x < shape_.dimension(1); ++x) {
+ for (size_t component = 0; component < shape_.dimension(2); ++component) {
+ float value;
+ switch (component) {
+ case 0:
+ value = float(x) / shape_.dimension(1);
+ break;
+ case 1:
+ value = float(y) / shape_.dimension(0);
+ break;
+ case 2:
+ value = 1.f - (float(x) / shape_.dimension(1));
+ break;
+ default:
+ value = 1.f;
+ break;
+ }
+
+ // inverse sRGB EOTF conversion from linear to non-linear
+ // https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html
+ if (value < 0.04045f) {
+ value /= 12.92f;
+ } else {
+ value = std::pow(((value + 0.055f) / 1.055f), 2.4f);
+ }
+
+ data_[y * strides_[0] + x * strides_[1] + component] = uint8_t((value * 255.f) + 0.5f);
+ }
+ }
+ }
+
+ Operator::initialize();
+ }
+
+ void setup(OperatorSpec& spec) override { spec.output("output"); }
+
+ void compute(InputContext& input, OutputContext& output, ExecutionContext& context) override {
+ auto entity = holoscan::gxf::Entity::New(&context);
+ auto tensor = static_cast(entity).add("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 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("source",
+ // stop application count
+ make_condition("count-condition", count_));
+
+ // By default the image format is auto detected. Auto detection assumes linear color space,
+ // but we provide an sRGB encoded image. Create an input spec and change the image format to
+ // sRGB.
+ ops::HolovizOp::InputSpec input_spec("image", ops::HolovizOp::InputType::COLOR);
+ input_spec.image_format_ = ops::HolovizOp::ImageFormat::R8G8B8_SRGB;
+
+ auto holoviz = make_operator(
+ "holoviz",
+ Arg("tensors", std::vector{input_spec}),
+ // enable the sRGB frame buffer
+ Arg("framebuffer_srgb", true),
+ Arg("window_title", std::string("Holoviz sRGB")),
+ Arg("cuda_stream_pool", make_resource("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 << "Holoscan ClaraViz volume renderer."
+ << "Usage: " << argv[0] << " [options]" << std::endl
+ << "Options:" << std::endl
+ << " -h, --help Display this information" << std::endl
+ << " -c , --count execute operators 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(c));
+ }
+ }
+
+ auto app = holoscan::make_application(count);
+ app->run();
+
+ holoscan::log_info("Application has finished running.");
+ return 0;
+}
diff --git a/applications/holoviz/holoviz_srgb/holoviz_srgb.png b/applications/holoviz/holoviz_srgb/holoviz_srgb.png
new file mode 120000
index 000000000..4e5c219e4
--- /dev/null
+++ b/applications/holoviz/holoviz_srgb/holoviz_srgb.png
@@ -0,0 +1 @@
+../template/cookiecutter-holoviz/holoviz_srgb.png
\ No newline at end of file
diff --git a/applications/holoviz/holoviz_srgb/metadata.json b/applications/holoviz/holoviz_srgb/metadata.json
new file mode 100644
index 000000000..25028aef3
--- /dev/null
+++ b/applications/holoviz/holoviz_srgb/metadata.json
@@ -0,0 +1,35 @@
+{
+ "application": {
+ "name": "Holoviz sRGB",
+ "authors": [
+ {
+ "name": "Holoscan Team",
+ "affiliation": "NVIDIA"
+ }
+ ],
+ "language": "C++",
+ "version": "1.0.0",
+ "changelog": {
+ "1.0": "Initial Release"
+ },
+ "holoscan_sdk": {
+ "minimum_required_version": "2.3",
+ "tested_versions": [
+ "2.3"
+ ]
+ },
+ "platforms": [
+ "amd64",
+ "arm64"
+ ],
+ "tags": [
+ "Holoviz sRGB"
+ ],
+ "ranking": 1,
+ "dependencies": {},
+ "run": {
+ "command": "/holoviz/holoviz_srgb",
+ "workdir": "holohub_bin"
+ }
+ }
+}
\ No newline at end of file
diff --git a/applications/holoviz/holoviz_vsync/CMakeLists.txt b/applications/holoviz/holoviz_vsync/CMakeLists.txt
new file mode 100644
index 000000000..561db2f3b
--- /dev/null
+++ b/applications/holoviz/holoviz_vsync/CMakeLists.txt
@@ -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_vsync)
+
+find_package(holoscan 2.3 REQUIRED CONFIG
+ PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
+
+add_executable(holoviz_vsync
+ holoviz_vsync.cpp
+)
+
+target_link_libraries(holoviz_vsync
+ PRIVATE
+ holoscan::core
+ holoscan::ops::holoviz
+ )
+
+if(BUILD_TESTING)
+ # Add test
+ add_test(NAME holoviz_vsync_test
+ COMMAND holoviz_vsync
+ --count=10
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+ set_tests_properties(holoviz_vsync_test PROPERTIES
+ PASS_REGULAR_EXPRESSION "Application has finished running.")
+endif()
diff --git a/applications/holoviz/holoviz_vsync/README.md b/applications/holoviz/holoviz_vsync/README.md
new file mode 100644
index 000000000..528452161
--- /dev/null
+++ b/applications/holoviz/holoviz_vsync/README.md
@@ -0,0 +1,23 @@
+# Holoviz vsync
+
+![](holoviz_vsync.png)
+This application demonstrates the capability of the Holoviz operator to wait for the vertical blank of the display before updating the current image. It prints the displayed frames per second to the console, if sync to vertical blank is enabled the frames per second are capped to the display refresh rate.
+
+To enable syncing to vertical blank set the `vsync` parameter of the Holoviz operator to `true`:
+
+```cpp
+ auto holoviz = make_operator(
+ "holoviz",
+ // enable synchronization to vertical blank
+ Arg("vsync", true));
+```
+
+By default, the Holoviz operator is not syncing to the vertical blank of the display.
+
+## Run Instructions
+
+To build and start the application:
+
+```bash
+./dev_container build_and_run holoviz_vsync
+```
diff --git a/applications/holoviz/holoviz_vsync/holoviz_vsync.cpp b/applications/holoviz/holoviz_vsync/holoviz_vsync.cpp
new file mode 100644
index 000000000..68b262d4f
--- /dev/null
+++ b/applications/holoviz/holoviz_vsync/holoviz_vsync.cpp
@@ -0,0 +1,176 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2023 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
+#include
+#include
+#include
+
+#include
+
+namespace holoscan::ops {
+
+class SourceOp : public Operator {
+ public:
+ HOLOSCAN_OPERATOR_FORWARD_ARGS(SourceOp);
+
+ void initialize() override {
+ shape_ = nvidia::gxf::Shape{64, 64, 3};
+ element_type_ = nvidia::gxf::PrimitiveType::kUnsigned8;
+ 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 random values
+ for (size_t y = 0; y < shape_.dimension(0); ++y) {
+ for (size_t x = 0; x < shape_.dimension(1); ++x) {
+ for (size_t component = 0; component < shape_.dimension(2); ++component) {
+ float value;
+ switch (component) {
+ case 0:
+ value = float(x) / shape_.dimension(1);
+ break;
+ case 1:
+ value = float(y) / shape_.dimension(0);
+ break;
+ case 2:
+ value = 1.f - (float(x) / shape_.dimension(1));
+ break;
+ default:
+ value = 1.f;
+ break;
+ }
+
+ data_[y * strides_[0] + x * strides_[1] + component] = uint8_t((value * 255.f) + 0.5f);
+ }
+ }
+ }
+
+ Operator::initialize();
+ }
+
+ void setup(OperatorSpec& spec) override { spec.output("output"); }
+
+ void compute(InputContext& input, OutputContext& output, ExecutionContext& context) override {
+ if (start_.time_since_epoch().count() == 0) { start_ = std::chrono::steady_clock::now(); }
+
+ auto entity = holoscan::gxf::Entity::New(&context);
+ auto tensor = static_cast(entity).add("image");
+ tensor.value()->wrapMemory(shape_,
+ element_type_,
+ element_size_,
+ strides_,
+ nvidia::gxf::MemoryStorageType::kSystem,
+ data_.data(),
+ nullptr);
+ output.emit(entity, "output");
+
+ iterations_++;
+ const std::chrono::milliseconds elapsed = std::chrono::duration_cast(
+ std::chrono::steady_clock::now() - start_);
+ if (elapsed.count() > 1000) {
+ const float fps =
+ static_cast(iterations_) / (static_cast(elapsed.count()) / 1000.f);
+ HOLOSCAN_LOG_INFO("Frames per second {}", fps);
+ start_ = std::chrono::steady_clock::now();
+ iterations_ = 0;
+ }
+ }
+
+ private:
+ nvidia::gxf::Shape shape_;
+ nvidia::gxf::PrimitiveType element_type_;
+ uint64_t element_size_;
+ nvidia::gxf::Tensor::stride_array_t strides_;
+ std::vector data_;
+ std::chrono::steady_clock::time_point start_;
+ uint32_t iterations_ = 0;
+};
+
+} // 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("source",
+ // stop application count
+ make_condition("count-condition", count_));
+
+ auto holoviz = make_operator(
+ "holoviz",
+ // enable synchronization to vertical blank
+ Arg("vsync", true),
+ Arg("window_title", std::string("Holoviz vsync")),
+ Arg("cuda_stream_pool", make_resource("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 << "Holoscan ClaraViz volume renderer."
+ << "Usage: " << argv[0] << " [options]" << std::endl
+ << "Options:" << std::endl
+ << " -h, --help Display this information" << std::endl
+ << " -c , --count execute operators 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(c));
+ }
+ }
+
+ auto app = holoscan::make_application(count);
+ app->run();
+
+ holoscan::log_info("Application has finished running.");
+ return 0;
+}
diff --git a/applications/holoviz/holoviz_vsync/holoviz_vsync.png b/applications/holoviz/holoviz_vsync/holoviz_vsync.png
new file mode 120000
index 000000000..d541a6ad4
--- /dev/null
+++ b/applications/holoviz/holoviz_vsync/holoviz_vsync.png
@@ -0,0 +1 @@
+../template/cookiecutter-holoviz/holoviz_vsync.png
\ No newline at end of file
diff --git a/applications/holoviz/holoviz_vsync/metadata.json b/applications/holoviz/holoviz_vsync/metadata.json
new file mode 100644
index 000000000..29998d1fd
--- /dev/null
+++ b/applications/holoviz/holoviz_vsync/metadata.json
@@ -0,0 +1,35 @@
+{
+ "application": {
+ "name": "Holoviz vsync",
+ "authors": [
+ {
+ "name": "Holoscan Team",
+ "affiliation": "NVIDIA"
+ }
+ ],
+ "language": "C++",
+ "version": "1.0.0",
+ "changelog": {
+ "1.0": "Initial Release"
+ },
+ "holoscan_sdk": {
+ "minimum_required_version": "2.3",
+ "tested_versions": [
+ "2.3"
+ ]
+ },
+ "platforms": [
+ "amd64",
+ "arm64"
+ ],
+ "tags": [
+ "Holoviz vsync"
+ ],
+ "ranking": 1,
+ "dependencies": {},
+ "run": {
+ "command": "/holoviz/holoviz_vsync",
+ "workdir": "holohub_bin"
+ }
+ }
+}
\ No newline at end of file
diff --git a/applications/holoviz/template/README.md b/applications/holoviz/template/README.md
new file mode 100644
index 000000000..f9b3e5ed8
--- /dev/null
+++ b/applications/holoviz/template/README.md
@@ -0,0 +1,22 @@
+# Template for Holoviz Examples
+
+Holoviz examples are generated from template files using Cookiecutter. The generated code is checked in, to re-generate the code after changing the template, execute the `generate_projects.sh` script.
+
+## Adding a new example
+
+Add a new value to the `examples` array in `cookiecutter-holoviz/cookiecutter.json`, e.g. `"new_example"`.
+Add a new line `generate "new_example_dir" "new_example" "New Example Window Title"` to `generate_projects.sh`.
+Modify the source code file `cookiecutter-holoviz/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.cpp` and add new
+code covered by the
+
+```
+{%- if cookiecutter.example == "new_example" %}
+ // some new code
+{%- endif %}
+```
+
+Add a screenshot of the app as `new_example_dir.png` to the `cookiecutter-holoviz` directory. This will be shown in the readme.
+
+Add `add_holohub_application(holoviz_new_example)` to the `CMakeLists.txt` file in `applications\holoviz`.
+
+Execute the `generate_projects.sh` script to generate the new example.
\ No newline at end of file
diff --git a/applications/holoviz/template/cookiecutter-holoviz/cookiecutter.json b/applications/holoviz/template/cookiecutter-holoviz/cookiecutter.json
new file mode 100644
index 000000000..7718dd6e1
--- /dev/null
+++ b/applications/holoviz/template/cookiecutter-holoviz/cookiecutter.json
@@ -0,0 +1,9 @@
+{
+ "full_name": "Holoscan Team",
+ "email": "aheumann@nvidia.com",
+ "project_name": "Holoviz Boilerplate",
+ "project_slug": "{{ cookiecutter.project_name.lower().replace(' ', '_') }}",
+ "project_description": "Holoviz Boilerplate contains all the boilerplate you need to create a Holoviz example application.",
+ "holoscan_version": "2.3",
+ "example": [ "sRGB", "vsync" ]
+}
\ No newline at end of file
diff --git a/applications/holoviz/template/cookiecutter-holoviz/holoviz_srgb.png b/applications/holoviz/template/cookiecutter-holoviz/holoviz_srgb.png
new file mode 100644
index 000000000..9324e32b4
Binary files /dev/null and b/applications/holoviz/template/cookiecutter-holoviz/holoviz_srgb.png differ
diff --git a/applications/holoviz/template/cookiecutter-holoviz/holoviz_vsync.png b/applications/holoviz/template/cookiecutter-holoviz/holoviz_vsync.png
new file mode 100644
index 000000000..dd51d5999
Binary files /dev/null and b/applications/holoviz/template/cookiecutter-holoviz/holoviz_vsync.png differ
diff --git a/applications/holoviz/template/cookiecutter-holoviz/hooks/post_gen_project.py b/applications/holoviz/template/cookiecutter-holoviz/hooks/post_gen_project.py
new file mode 100644
index 000000000..73135c695
--- /dev/null
+++ b/applications/holoviz/template/cookiecutter-holoviz/hooks/post_gen_project.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python3
+# 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.
+
+import os
+
+# create a symlink to the screenshot
+os.symlink(
+ "../template/cookiecutter-holoviz/{{ cookiecutter.project_slug }}.png",
+ "{{ cookiecutter.project_slug }}.png",
+)
diff --git a/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/CMakeLists.txt b/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/CMakeLists.txt
new file mode 100644
index 000000000..99343e7b2
--- /dev/null
+++ b/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/CMakeLists.txt
@@ -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({{ cookiecutter.project_slug }})
+
+find_package(holoscan {{ cookiecutter.holoscan_version }} REQUIRED CONFIG
+ PATHS "/opt/nvidia/holoscan" "/workspace/holoscan-sdk/install")
+
+add_executable({{ cookiecutter.project_slug }}
+ {{ cookiecutter.project_slug }}.cpp
+)
+
+target_link_libraries({{ cookiecutter.project_slug }}
+ PRIVATE
+ holoscan::core
+ holoscan::ops::holoviz
+ )
+
+if(BUILD_TESTING)
+ # Add test
+ add_test(NAME {{ cookiecutter.project_slug }}_test
+ COMMAND {{ cookiecutter.project_slug }}
+ --count=10
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
+ set_tests_properties({{ cookiecutter.project_slug }}_test PROPERTIES
+ PASS_REGULAR_EXPRESSION "Application has finished running.")
+endif()
diff --git a/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/README.md b/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/README.md
new file mode 100644
index 000000000..5a5d7d16b
--- /dev/null
+++ b/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/README.md
@@ -0,0 +1,57 @@
+# {{ cookiecutter.project_name }}
+
+![]({{ cookiecutter.project_slug }}.png)
+
+{%- if cookiecutter.example == "sRGB" %}
+This application demonstrates the handling of the sRGB color space supported by the Holoviz operator.
+
+The Holoviz operator can convert sRGB input images to linear color space before rendering and also can convert from linear color space to sRGB before writing to the frame buffer.
+
+sRGB color space can be enabled for input images and for the frame buffer independently. By default, the sRGB color space is disabled for both.
+
+By default, the Holoviz operator is auto detecting the input image format. Auto detection always assumes linear color space for input images. To change this to sRGB color space explicitly set the `image_format_` member of the input spec for that input image to a format ending with `SRGB`:
+
+```cpp
+ // By default the image format is auto detected. Auto detection assumes linear color space,
+ // but we provide an sRGB encoded image. Create an input spec and change the image format to
+ // sRGB.
+ ops::HolovizOp::InputSpec input_spec("image", ops::HolovizOp::InputType::COLOR);
+ input_spec.image_format_ = ops::HolovizOp::ImageFormat::R8G8B8_SRGB;
+
+ auto holoviz = make_operator(
+ "holoviz",
+ Arg("tensors", std::vector{input_spec}));
+```
+
+By default, the frame buffer is using linear color space. To use the sRGB color space, set the `framebuffer_srbg` argument of the Holoviz operator to `true`:
+
+```cpp
+ auto holoviz = make_operator(
+ "holoviz",
+ // enable the sRGB frame buffer
+ Arg("framebuffer_srbg", true));
+```
+
+{%- endif %}
+{%- if cookiecutter.example == "vsync" %}
+This application demonstrates the capability of the Holoviz operator to wait for the vertical blank of the display before updating the current image. It prints the displayed frames per second to the console, if sync to vertical blank is enabled the frames per second are capped to the display refresh rate.
+
+To enable syncing to vertical blank set the `vsync` parameter of the Holoviz operator to `true`:
+
+```cpp
+ auto holoviz = make_operator(
+ "holoviz",
+ // enable synchronization to vertical blank
+ Arg("vsync", true));
+```
+
+By default, the Holoviz operator is not syncing to the vertical blank of the display.
+{%- endif %}
+
+## Run Instructions
+
+To build and start the application:
+
+```bash
+./dev_container build_and_run {{ cookiecutter.project_slug }}
+```
diff --git a/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/metadata.json b/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/metadata.json
new file mode 100644
index 000000000..7cf82f48a
--- /dev/null
+++ b/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/metadata.json
@@ -0,0 +1,35 @@
+{
+ "application": {
+ "name": "{{ cookiecutter.project_name }}",
+ "authors": [
+ {
+ "name": "{{ cookiecutter.full_name }}",
+ "affiliation": "NVIDIA"
+ }
+ ],
+ "language": "C++",
+ "version": "1.0.0",
+ "changelog": {
+ "1.0": "Initial Release"
+ },
+ "holoscan_sdk": {
+ "minimum_required_version": "{{ cookiecutter.holoscan_version }}",
+ "tested_versions": [
+ "{{ cookiecutter.holoscan_version }}"
+ ]
+ },
+ "platforms": [
+ "amd64",
+ "arm64"
+ ],
+ "tags": [
+ "{{ cookiecutter.project_name }}"
+ ],
+ "ranking": 1,
+ "dependencies": {},
+ "run": {
+ "command": "/holoviz/{{ cookiecutter.project_slug }}",
+ "workdir": "holohub_bin"
+ }
+ }
+}
\ No newline at end of file
diff --git a/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.cpp b/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.cpp
new file mode 100644
index 000000000..a70488f55
--- /dev/null
+++ b/applications/holoviz/template/cookiecutter-holoviz/{{cookiecutter.project_slug}}/{{cookiecutter.project_slug}}.cpp
@@ -0,0 +1,224 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2023 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.
+ */
+{%- set example = namespace(print_fps = false) %}
+{%- if cookiecutter.example == "vsync" %}
+ {% set example.print_fps = true %}
+{%- endif %}
+
+
+#include
+#include
+
+{%- if cookiecutter.example == "vsync" %}
+#include
+{%- endif %}
+#include
+
+#include
+
+namespace holoscan::ops {
+
+class SourceOp : public Operator {
+ public:
+ HOLOSCAN_OPERATOR_FORWARD_ARGS(SourceOp);
+
+ void initialize() override {
+ shape_ = nvidia::gxf::Shape{64, 64, 3};
+ element_type_ = nvidia::gxf::PrimitiveType::kUnsigned8;
+ 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 random values
+ for (size_t y = 0; y < shape_.dimension(0); ++y) {
+ for (size_t x = 0; x < shape_.dimension(1); ++x) {
+ for (size_t component = 0; component < shape_.dimension(2); ++component) {
+ float value;
+ switch (component) {
+ case 0:
+ value = float(x) / shape_.dimension(1);
+ break;
+ case 1:
+ value = float(y) / shape_.dimension(0);
+ break;
+ case 2:
+ value = 1.f - (float(x) / shape_.dimension(1));
+ break;
+ default:
+ value = 1.f;
+ break;
+ }
+{%- if cookiecutter.example == "sRGB" %}
+
+ // inverse sRGB EOTF conversion from linear to non-linear
+ // https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html
+ if (value < 0.04045f) {
+ value /= 12.92f;
+ } else {
+ value = std::pow(((value + 0.055f) / 1.055f), 2.4f);
+ }
+{%- endif %}
+
+ data_[y * strides_[0] + x * strides_[1] + component] = uint8_t((value * 255.f) + 0.5f);
+ }
+ }
+ }
+
+ Operator::initialize();
+ }
+
+ void setup(OperatorSpec& spec) override { spec.output("output"); }
+
+ void compute(InputContext& input, OutputContext& output, ExecutionContext& context) override {
+{%- if example.print_fps %}
+ if (start_.time_since_epoch().count() == 0) {
+ start_ = std::chrono::steady_clock::now();
+ }
+
+{% endif -%}
+
+ auto entity = holoscan::gxf::Entity::New(&context);
+ auto tensor = static_cast(entity).add("image");
+ tensor.value()->wrapMemory(shape_,
+ element_type_,
+ element_size_,
+ strides_,
+ nvidia::gxf::MemoryStorageType::kSystem,
+ data_.data(),
+ nullptr);
+ output.emit(entity, "output");
+{%- if example.print_fps %}
+
+ iterations_++;
+ const std::chrono::milliseconds elapsed = std::chrono::duration_cast(
+ std::chrono::steady_clock::now() - start_);
+ if (elapsed.count() > 1000) {
+ const float fps =
+ static_cast(iterations_) / (static_cast(elapsed.count()) / 1000.f);
+ HOLOSCAN_LOG_INFO("Frames per second {}", fps);
+ start_ = std::chrono::steady_clock::now();
+ iterations_ = 0;
+ }
+{%- endif %}
+ }
+
+ private:
+ nvidia::gxf::Shape shape_;
+ nvidia::gxf::PrimitiveType element_type_;
+ uint64_t element_size_;
+ nvidia::gxf::Tensor::stride_array_t strides_;
+ std::vector data_;
+
+{%- if example.print_fps %}
+ std::chrono::steady_clock::time_point start_;
+ uint32_t iterations_ = 0;
+{%- endif %}
+};
+
+} // 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("source",
+ // stop application count
+ make_condition("count-condition", count_));
+{%- if cookiecutter.example == "sRGB" %}
+
+ // By default the image format is auto detected. Auto detection assumes linear color space,
+ // but we provide an sRGB encoded image. Create an input spec and change the image format to
+ // sRGB.
+ ops::HolovizOp::InputSpec input_spec("image", ops::HolovizOp::InputType::COLOR);
+ input_spec.image_format_ = ops::HolovizOp::ImageFormat::R8G8B8_SRGB;
+{%- endif %}
+
+ auto holoviz = make_operator(
+ "holoviz",
+{%- if cookiecutter.example == "sRGB" %}
+ Arg("tensors", std::vector{input_spec}),
+ // enable the sRGB frame buffer
+ Arg("framebuffer_srgb", true),
+{%- endif %}
+{%- if cookiecutter.example == "vsync" %}
+ // enable synchronization to vertical blank
+ Arg("vsync", true),
+{%- endif %}
+ Arg("window_title", std::string("{{ cookiecutter.project_name }}")),
+ Arg("cuda_stream_pool", make_resource("cuda_stream_pool", 0, 0, 0, 1, 5)));
+{%- raw %}
+
+ add_flow(source, holoviz, {{"output", "receivers"}});
+{%- endraw %}
+ }
+
+ const int count_;
+};
+
+int main(int argc, char** argv) {
+ int count = -1;
+
+{%- raw %}
+
+ struct option long_options[] = {
+ {"help", no_argument, 0, 'h'}, {"count", optional_argument, 0, 'c'}, {0, 0, 0, 0}};
+{%- endraw %}
+
+ // 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 << "Holoscan ClaraViz volume renderer."
+ << "Usage: " << argv[0] << " [options]" << std::endl
+ << "Options:" << std::endl
+ << " -h, --help Display this information" << std::endl
+ << " -c , --count execute operators 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(c));
+ }
+ }
+
+ auto app = holoscan::make_application(count);
+ app->run();
+
+ holoscan::log_info("Application has finished running.");
+ return 0;
+}
diff --git a/applications/holoviz/template/generate_projects.sh b/applications/holoviz/template/generate_projects.sh
new file mode 100755
index 000000000..cecdb7e2d
--- /dev/null
+++ b/applications/holoviz/template/generate_projects.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+# 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.
+
+set -x
+
+PATH=$PATH:$HOME/.local/bin
+
+if ! command -v cookiecutter &> /dev/null; then
+ python3 -m pip install cookiecutter
+fi
+if ! command -v clang-format &> /dev/null; then
+ python3 -m pip install clang-format
+fi
+
+generate() {
+ local project_dir="$1"
+ local example="$2"
+ local project_name="$3"
+
+ rm -rf ../${project_dir}
+ cookiecutter --no-input cookiecutter-holoviz "example=${example}" "project_name=${project_name}" -o ..
+ clang-format -i ../${project_dir}/*.cpp
+}
+
+generate "holoviz_srgb" "sRGB" "Holoviz sRGB"
+generate "holoviz_vsync" "vsync" "Holoviz vsync"
diff --git a/utilities/metadata_validator.py b/utilities/metadata_validator.py
index 3e1203e9e..65cb721e8 100644
--- a/utilities/metadata_validator.py
+++ b/utilities/metadata_validator.py
@@ -76,6 +76,14 @@ def validate_json_directory(directory, ignore_patterns=[], metadata_is_required:
# Check if the metadata is valid
for name in glob.glob(current_wdir + "/" + directory + "/**/metadata.json", recursive=True):
+ ignore = False
+ # check if we should ignore the pattern
+ for ignore_pattern in ignore_patterns:
+ if ignore_pattern in name:
+ ignore = True
+ if ignore:
+ continue
+
with open(name, "r") as file:
try:
jsonData = json.load(file)