diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 53e494768..2a5b05b17 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -34,7 +34,7 @@ concurrency:
jobs:
cpp-build:
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@main
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
@@ -45,12 +45,12 @@ jobs:
if: github.ref_type == 'branch'
needs: [python-build]
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main
with:
arch: "amd64"
branch: ${{ inputs.branch }}
build_type: ${{ inputs.build_type || 'branch' }}
- container_image: "rapidsai/ci-conda:25.12-latest"
+ container_image: "rapidsai/ci-conda:26.02-latest"
date: ${{ inputs.date }}
node_type: "gpu-l4-latest-1"
script: "ci/build_docs.sh"
@@ -58,7 +58,7 @@ jobs:
python-build:
needs: [cpp-build]
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@main
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
@@ -68,7 +68,7 @@ jobs:
upload-conda:
needs: [cpp-build, python-build]
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@main
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
@@ -76,7 +76,7 @@ jobs:
sha: ${{ inputs.sha }}
wheel-build:
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
@@ -88,7 +88,7 @@ jobs:
wheel-publish:
needs: wheel-build
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@main
with:
build_type: ${{ inputs.build_type || 'branch' }}
branch: ${{ inputs.branch }}
diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml
index 8885145e4..ef81679a1 100644
--- a/.github/workflows/pr.yaml
+++ b/.github/workflows/pr.yaml
@@ -18,7 +18,7 @@ jobs:
- wheel-tests
- telemetry-setup
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@main
telemetry-setup:
runs-on: ubuntu-latest
continue-on-error: true
@@ -33,44 +33,44 @@ jobs:
checks:
secrets: inherit
needs: telemetry-setup
- uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@main
with:
ignored_pr_jobs: telemetry-summarize
conda-cpp-build:
needs: checks
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@main
with:
build_type: pull-request
script: ci/build_cpp.sh
conda-python-build:
needs: conda-cpp-build
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@main
with:
build_type: pull-request
script: ci/build_python.sh
conda-python-tests:
needs: conda-python-build
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@main
with:
build_type: pull-request
script: ci/test_python.sh
docs-build:
needs: conda-python-build
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main
with:
build_type: pull-request
node_type: "gpu-l4-latest-1"
arch: "amd64"
- container_image: "rapidsai/ci-conda:25.12-latest"
+ container_image: "rapidsai/ci-conda:26.02-latest"
script: "ci/build_docs.sh"
wheel-build:
needs: checks
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main
with:
build_type: pull-request
script: ci/build_wheel.sh
@@ -79,7 +79,7 @@ jobs:
wheel-tests:
needs: wheel-build
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main
with:
build_type: pull-request
script: ci/test_wheel.sh
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 23488ff1b..f8d3bedf5 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -25,7 +25,7 @@ on:
jobs:
conda-python-tests:
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@main
with:
build_type: ${{ inputs.build_type }}
branch: ${{ inputs.branch }}
@@ -34,7 +34,7 @@ jobs:
sha: ${{ inputs.sha }}
wheel-tests:
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main
with:
build_type: ${{ inputs.build_type }}
branch: ${{ inputs.branch }}
diff --git a/.github/workflows/trigger-breaking-change-alert.yaml b/.github/workflows/trigger-breaking-change-alert.yaml
index 0b885544d..c471e2a15 100644
--- a/.github/workflows/trigger-breaking-change-alert.yaml
+++ b/.github/workflows/trigger-breaking-change-alert.yaml
@@ -12,7 +12,7 @@ jobs:
trigger-notifier:
if: contains(github.event.pull_request.labels.*.name, 'breaking')
secrets: inherit
- uses: rapidsai/shared-workflows/.github/workflows/breaking-change-alert.yaml@release/25.12
+ uses: rapidsai/shared-workflows/.github/workflows/breaking-change-alert.yaml@main
with:
sender_login: ${{ github.event.sender.login }}
sender_avatar: ${{ github.event.sender.avatar_url }}
diff --git a/.gitignore b/.gitignore
index 40bbb6354..bc75731c5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -155,3 +155,7 @@ conda-bld
# Custom debug environment setup script for VS Code (used by scripts/debug_python)
/scripts/debug_env.sh
+*.tiff
+buildbackuup/
+*_cpp_documentation.md
+junit-cucim.xml
diff --git a/conda/recipes/libcucim/conda_build_config.yaml b/conda/recipes/libcucim/conda_build_config.yaml
index 1082f0d21..b3f92ba0b 100644
--- a/conda/recipes/libcucim/conda_build_config.yaml
+++ b/conda/recipes/libcucim/conda_build_config.yaml
@@ -15,3 +15,6 @@ c_stdlib_version:
cmake_version:
- ">=3.30.4"
+
+nvimgcodec_version:
+ - ">=0.6.0"
diff --git a/conda/recipes/libcucim/meta.yaml b/conda/recipes/libcucim/meta.yaml
index 5b64725bc..612d9c01c 100644
--- a/conda/recipes/libcucim/meta.yaml
+++ b/conda/recipes/libcucim/meta.yaml
@@ -58,6 +58,7 @@ requirements:
- cuda-cudart-dev
- libcufile-dev
- libnvjpeg-dev
+ - libnvimgcodec-dev {{ nvimgcodec_version }} # nvImageCodec development headers and libraries
- nvtx-c >=3.1.0
- openslide
run:
@@ -69,8 +70,10 @@ requirements:
- libcufile
- cuda-cudart
- libnvjpeg
+ - libnvimgcodec0 {{ nvimgcodec_version }} # nvImageCodec runtime library
run_constrained:
- {{ pin_compatible('openslide') }}
+ - libnvimgcodec-dev {{ nvimgcodec_version }} # Optional: for development/debugging
about:
home: https://developer.nvidia.com/multidimensional-image-processing
diff --git a/cpp/cmake/deps/nvimgcodec.cmake b/cpp/cmake/deps/nvimgcodec.cmake
new file mode 100644
index 000000000..85df92cf4
--- /dev/null
+++ b/cpp/cmake/deps/nvimgcodec.cmake
@@ -0,0 +1,232 @@
+#
+# cmake-format: off
+# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
+# SPDX-License-Identifier: Apache-2.0
+# cmake-format: on
+#
+
+if (NOT TARGET deps::nvimgcodec)
+ # Option to automatically install nvImageCodec via conda
+ option(AUTO_INSTALL_NVIMGCODEC "Automatically install nvImageCodec via conda" ON)
+ set(NVIMGCODEC_VERSION "0.7.0" CACHE STRING "nvImageCodec version to install")
+
+ # Automatic installation logic
+ if(AUTO_INSTALL_NVIMGCODEC)
+ message(STATUS "Configuring automatic nvImageCodec installation...")
+
+ # Try to find micromamba or conda in various locations
+ find_program(MICROMAMBA_EXECUTABLE
+ NAMES micromamba
+ PATHS ${CMAKE_CURRENT_SOURCE_DIR}/../../../bin
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../bin
+ ${CMAKE_CURRENT_SOURCE_DIR}/bin
+ $ENV{HOME}/micromamba/bin
+ $ENV{HOME}/.local/bin
+ /usr/local/bin
+ /opt/conda/bin
+ /opt/miniconda/bin
+ DOC "Path to micromamba executable"
+ )
+
+ find_program(CONDA_EXECUTABLE
+ NAMES conda mamba
+ PATHS $ENV{HOME}/miniconda3/bin
+ $ENV{HOME}/anaconda3/bin
+ /opt/conda/bin
+ /opt/miniconda/bin
+ /usr/local/bin
+ DOC "Path to conda/mamba executable"
+ )
+
+ # Determine which conda tool to use
+ set(CONDA_CMD "")
+ set(CONDA_TYPE "")
+ if(MICROMAMBA_EXECUTABLE)
+ set(CONDA_CMD ${MICROMAMBA_EXECUTABLE})
+ set(CONDA_TYPE "micromamba")
+ message(STATUS "Found micromamba: ${MICROMAMBA_EXECUTABLE}")
+ elseif(CONDA_EXECUTABLE)
+ set(CONDA_CMD ${CONDA_EXECUTABLE})
+ set(CONDA_TYPE "conda")
+ message(STATUS "Found conda/mamba: ${CONDA_EXECUTABLE}")
+ endif()
+
+ if(CONDA_CMD)
+ # Check if nvImageCodec is already installed
+ message(STATUS "Checking for existing nvImageCodec installation...")
+ execute_process(
+ COMMAND ${CONDA_CMD} list libnvimgcodec-dev
+ RESULT_VARIABLE NVIMGCODEC_CHECK_RESULT
+ OUTPUT_VARIABLE NVIMGCODEC_CHECK_OUTPUT
+ ERROR_QUIET
+ )
+
+ # Parse version from output if installed
+ set(NVIMGCODEC_INSTALLED_VERSION "")
+ if(NVIMGCODEC_CHECK_RESULT EQUAL 0)
+ string(REGEX MATCH "libnvimgcodec-dev[ ]+([0-9]+\\.[0-9]+\\.[0-9]+)"
+ VERSION_MATCH "${NVIMGCODEC_CHECK_OUTPUT}")
+ if(CMAKE_MATCH_1)
+ set(NVIMGCODEC_INSTALLED_VERSION ${CMAKE_MATCH_1})
+ endif()
+ endif()
+
+ # Install or upgrade if needed
+ set(NEED_INSTALL FALSE)
+ if(NOT NVIMGCODEC_CHECK_RESULT EQUAL 0)
+ message(STATUS "nvImageCodec not found - installing version ${NVIMGCODEC_VERSION}")
+ set(NEED_INSTALL TRUE)
+ elseif(NVIMGCODEC_INSTALLED_VERSION AND NVIMGCODEC_INSTALLED_VERSION VERSION_LESS NVIMGCODEC_VERSION)
+ message(STATUS "nvImageCodec ${NVIMGCODEC_INSTALLED_VERSION} found - upgrading to ${NVIMGCODEC_VERSION}")
+ set(NEED_INSTALL TRUE)
+ else()
+ message(STATUS "nvImageCodec ${NVIMGCODEC_INSTALLED_VERSION} already installed (>= ${NVIMGCODEC_VERSION})")
+ endif()
+
+ if(NEED_INSTALL)
+ # Install nvImageCodec with specific version
+ message(STATUS "Installing nvImageCodec ${NVIMGCODEC_VERSION} via ${CONDA_TYPE}...")
+ execute_process(
+ COMMAND ${CONDA_CMD} install
+ libnvimgcodec-dev=${NVIMGCODEC_VERSION}
+ libnvimgcodec0=${NVIMGCODEC_VERSION}
+ -c conda-forge -y
+ RESULT_VARIABLE CONDA_INSTALL_RESULT
+ OUTPUT_VARIABLE CONDA_INSTALL_OUTPUT
+ ERROR_VARIABLE CONDA_INSTALL_ERROR
+ TIMEOUT 300 # 5 minute timeout
+ )
+
+ if(CONDA_INSTALL_RESULT EQUAL 0)
+ message(STATUS "✓ Successfully installed nvImageCodec ${NVIMGCODEC_VERSION}")
+ else()
+ message(WARNING "✗ Failed to install nvImageCodec via ${CONDA_TYPE}")
+ message(WARNING "Error: ${CONDA_INSTALL_ERROR}")
+
+ # Try alternative installation without version constraint
+ message(STATUS "Attempting installation without version constraint...")
+ execute_process(
+ COMMAND ${CONDA_CMD} install libnvimgcodec-dev libnvimgcodec0 -c conda-forge -y
+ RESULT_VARIABLE CONDA_FALLBACK_RESULT
+ OUTPUT_QUIET
+ ERROR_QUIET
+ )
+
+ if(CONDA_FALLBACK_RESULT EQUAL 0)
+ message(STATUS "✓ Fallback installation successful")
+ else()
+ message(WARNING "✗ Fallback installation also failed")
+ endif()
+ endif()
+ endif()
+ else()
+ message(STATUS "No conda/micromamba found - skipping automatic installation")
+ endif()
+ endif()
+
+ # First try to find it as a package
+ find_package(nvimgcodec QUIET)
+
+ if(nvimgcodec_FOUND)
+ # Use the found package
+ add_library(deps::nvimgcodec INTERFACE IMPORTED GLOBAL)
+ target_link_libraries(deps::nvimgcodec INTERFACE nvimgcodec::nvimgcodec)
+ message(STATUS "✓ nvImageCodec found via find_package")
+ else()
+ # Manual detection in various environments
+ set(NVIMGCODEC_LIB_PATH "")
+ set(NVIMGCODEC_INCLUDE_PATH "")
+
+ # Try conda environment detection (both Python packages and native packages)
+ if(DEFINED ENV{CONDA_BUILD})
+ # Conda build environment
+ set(NVIMGCODEC_LIB_PATH "$ENV{PREFIX}/lib/libnvimgcodec.so.0")
+ set(NVIMGCODEC_INCLUDE_PATH "$ENV{PREFIX}/include/")
+ if(NOT EXISTS "${NVIMGCODEC_LIB_PATH}")
+ set(NVIMGCODEC_LIB_PATH "$ENV{PREFIX}/lib/libnvimgcodec.so")
+ endif()
+ elseif(DEFINED ENV{CONDA_PREFIX})
+ # Active conda environment - try native package first
+ set(CONDA_NATIVE_ROOT "$ENV{CONDA_PREFIX}")
+ if(EXISTS "${CONDA_NATIVE_ROOT}/include/nvimgcodec.h")
+ set(NVIMGCODEC_INCLUDE_PATH "${CONDA_NATIVE_ROOT}/include/")
+ if(EXISTS "${CONDA_NATIVE_ROOT}/lib/libnvimgcodec.so.0")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_NATIVE_ROOT}/lib/libnvimgcodec.so.0")
+ elseif(EXISTS "${CONDA_NATIVE_ROOT}/lib/libnvimgcodec.so")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_NATIVE_ROOT}/lib/libnvimgcodec.so")
+ endif()
+ else()
+ # Fallback: try Python site-packages in conda environment
+ foreach(PY_VER "3.13" "3.12" "3.11" "3.10" "3.9")
+ set(CONDA_PYTHON_ROOT "$ENV{CONDA_PREFIX}/lib/python${PY_VER}/site-packages/nvidia/nvimgcodec")
+ if(EXISTS "${CONDA_PYTHON_ROOT}/include/nvimgcodec.h")
+ set(NVIMGCODEC_INCLUDE_PATH "${CONDA_PYTHON_ROOT}/include/")
+ if(EXISTS "${CONDA_PYTHON_ROOT}/lib/libnvimgcodec.so.0")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_PYTHON_ROOT}/lib/libnvimgcodec.so.0")
+ elseif(EXISTS "${CONDA_PYTHON_ROOT}/lib/libnvimgcodec.so")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_PYTHON_ROOT}/lib/libnvimgcodec.so")
+ endif()
+ break()
+ endif()
+ endforeach()
+ endif()
+ else()
+ # Try Python site-packages detection
+ find_package(Python3 COMPONENTS Interpreter)
+ if(Python3_FOUND)
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} -c "import site; print(site.getsitepackages()[0])"
+ OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+
+ if(PYTHON_SITE_PACKAGES)
+ set(NVIMGCODEC_PYTHON_ROOT "${PYTHON_SITE_PACKAGES}/nvidia/nvimgcodec")
+ if(EXISTS "${NVIMGCODEC_PYTHON_ROOT}/include/nvimgcodec.h")
+ set(NVIMGCODEC_INCLUDE_PATH "${NVIMGCODEC_PYTHON_ROOT}/include/")
+ if(EXISTS "${NVIMGCODEC_PYTHON_ROOT}/lib/libnvimgcodec.so.0")
+ set(NVIMGCODEC_LIB_PATH "${NVIMGCODEC_PYTHON_ROOT}/lib/libnvimgcodec.so.0")
+ elseif(EXISTS "${NVIMGCODEC_PYTHON_ROOT}/lib/libnvimgcodec.so")
+ set(NVIMGCODEC_LIB_PATH "${NVIMGCODEC_PYTHON_ROOT}/lib/libnvimgcodec.so")
+ endif()
+ endif()
+ endif()
+ endif()
+
+ # System-wide installation fallback
+ if(NOT NVIMGCODEC_LIB_PATH)
+ if(EXISTS /usr/lib/x86_64-linux-gnu/libnvimgcodec.so.0)
+ set(NVIMGCODEC_LIB_PATH /usr/lib/x86_64-linux-gnu/libnvimgcodec.so.0)
+ set(NVIMGCODEC_INCLUDE_PATH "/usr/include/")
+ elseif(EXISTS /usr/lib/aarch64-linux-gnu/libnvimgcodec.so.0)
+ set(NVIMGCODEC_LIB_PATH /usr/lib/aarch64-linux-gnu/libnvimgcodec.so.0)
+ set(NVIMGCODEC_INCLUDE_PATH "/usr/include/")
+ elseif(EXISTS /usr/lib64/libnvimgcodec.so.0) # CentOS (x86_64)
+ set(NVIMGCODEC_LIB_PATH /usr/lib64/libnvimgcodec.so.0)
+ set(NVIMGCODEC_INCLUDE_PATH "/usr/include/")
+ endif()
+ endif()
+ endif()
+
+ # Create the target if we found the library
+ if(NVIMGCODEC_LIB_PATH AND EXISTS "${NVIMGCODEC_LIB_PATH}")
+ add_library(deps::nvimgcodec SHARED IMPORTED GLOBAL)
+ set_target_properties(deps::nvimgcodec PROPERTIES
+ IMPORTED_LOCATION "${NVIMGCODEC_LIB_PATH}"
+ INTERFACE_INCLUDE_DIRECTORIES "${NVIMGCODEC_INCLUDE_PATH}"
+ )
+ message(STATUS "✓ nvImageCodec found:")
+ message(STATUS " Library: ${NVIMGCODEC_LIB_PATH}")
+ message(STATUS " Headers: ${NVIMGCODEC_INCLUDE_PATH}")
+ else()
+ # Create a dummy target to prevent build failures
+ add_library(deps::nvimgcodec INTERFACE IMPORTED GLOBAL)
+ message(STATUS "✗ nvImageCodec not found - GPU acceleration disabled")
+ message(STATUS "To enable nvImageCodec support:")
+ message(STATUS " Option 1 (conda): micromamba install libnvimgcodec-dev -c conda-forge")
+ message(STATUS " Option 2 (pip): pip install nvidia-nvimgcodec-cu12[all]")
+ message(STATUS " Option 3 (cmake): cmake -DAUTO_INSTALL_NVIMGCODEC=ON ..")
+ endif()
+ endif()
+endif()
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/.gitignore b/cpp/plugins/cucim.kit.cumed/.idea/.gitignore
deleted file mode 100644
index 73f69e095..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/.name b/cpp/plugins/cucim.kit.cumed/.idea/.name
deleted file mode 100644
index 93c67f482..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-cumed
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/codeStyles/Project.xml b/cpp/plugins/cucim.kit.cumed/.idea/codeStyles/Project.xml
deleted file mode 100644
index c8f84c353..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/codeStyles/codeStyleConfig.xml b/cpp/plugins/cucim.kit.cumed/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 0f7bc519d..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/cucim.kit.cumed.iml b/cpp/plugins/cucim.kit.cumed/.idea/cucim.kit.cumed.iml
deleted file mode 100644
index 8afe22e01..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/cucim.kit.cumed.iml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake b/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake
deleted file mode 100644
index f11461a8a..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake
+++ /dev/null
@@ -1,6 +0,0 @@
-#
-# cmake-format: off
-# SPDX-FileCopyrightText: Copyright (c) $YEAR, NVIDIA CORPORATION. All rights reserved.
-# SPDX-License-Identifier: Apache-2.0
-# cmake-format: on
-#
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h b/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h
deleted file mode 100644
index 0255849f3..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) $YEAR, NVIDIA CORPORATION. All rights reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C Header File.h b/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C Header File.h
deleted file mode 100644
index 9cb1d09e2..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C Header File.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#parse("NVIDIA_C_HEADER.h")
-#[[#ifndef]]# ${INCLUDE_GUARD}
-#[[#define]]# ${INCLUDE_GUARD}
-
-#[[#endif]]# //${INCLUDE_GUARD}
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C Source File.c b/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C Source File.c
deleted file mode 100644
index b04dd6c62..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C Source File.c
+++ /dev/null
@@ -1,4 +0,0 @@
-#parse("NVIDIA_C_HEADER.h")
-#if (${HEADER_FILENAME})
-#[[#include]]# "${HEADER_FILENAME}"
-#end
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C++ Class Header.h b/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C++ Class Header.h
deleted file mode 100644
index f521fa555..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C++ Class Header.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#parse("NVIDIA_C_HEADER.h")
-#[[#ifndef]]# ${INCLUDE_GUARD}
-#[[#define]]# ${INCLUDE_GUARD}
-
-${NAMESPACES_OPEN}
-
-class ${NAME} {
-
-};
-
-${NAMESPACES_CLOSE}
-
-#[[#endif]]# //${INCLUDE_GUARD}
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C++ Class.cc b/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C++ Class.cc
deleted file mode 100644
index 42f43ccf4..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/C++ Class.cc
+++ /dev/null
@@ -1,2 +0,0 @@
-#parse("NVIDIA_C_HEADER.h")
-#[[#include]]# "${HEADER_FILENAME}"
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/CMakeLists.txt.cmake b/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/CMakeLists.txt.cmake
deleted file mode 100644
index 846356219..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/fileTemplates/internal/CMakeLists.txt.cmake
+++ /dev/null
@@ -1 +0,0 @@
-#parse("NVIDIA_CMAKE_HEADER.cmake")
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/misc.xml b/cpp/plugins/cucim.kit.cumed/.idea/misc.xml
deleted file mode 100644
index 2019083a1..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/cpp/plugins/cucim.kit.cumed/.idea/vcs.xml b/cpp/plugins/cucim.kit.cumed/.idea/vcs.xml
deleted file mode 100644
index fbbc5665e..000000000
--- a/cpp/plugins/cucim.kit.cumed/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/.gitignore b/cpp/plugins/cucim.kit.cuslide/.idea/.gitignore
deleted file mode 100644
index 73f69e095..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/.name b/cpp/plugins/cucim.kit.cuslide/.idea/.name
deleted file mode 100644
index cc09966de..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-cuslide
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/codeStyles/Project.xml b/cpp/plugins/cucim.kit.cuslide/.idea/codeStyles/Project.xml
deleted file mode 100644
index c8f84c353..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/codeStyles/codeStyleConfig.xml b/cpp/plugins/cucim.kit.cuslide/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 0f7bc519d..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/cucim.kit.cuslide.iml b/cpp/plugins/cucim.kit.cuslide/.idea/cucim.kit.cuslide.iml
deleted file mode 100644
index 08cda128a..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/cucim.kit.cuslide.iml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake b/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake
deleted file mode 100644
index f11461a8a..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/includes/NVIDIA_CMAKE_HEADER.cmake
+++ /dev/null
@@ -1,6 +0,0 @@
-#
-# cmake-format: off
-# SPDX-FileCopyrightText: Copyright (c) $YEAR, NVIDIA CORPORATION. All rights reserved.
-# SPDX-License-Identifier: Apache-2.0
-# cmake-format: on
-#
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h b/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h
deleted file mode 100644
index 0255849f3..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/includes/NVIDIA_C_HEADER.h
+++ /dev/null
@@ -1,4 +0,0 @@
-/*
- * SPDX-FileCopyrightText: Copyright (c) $YEAR, NVIDIA CORPORATION. All rights reserved.
- * SPDX-License-Identifier: Apache-2.0
- */
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C Header File.h b/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C Header File.h
deleted file mode 100644
index 9cb1d09e2..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C Header File.h
+++ /dev/null
@@ -1,5 +0,0 @@
-#parse("NVIDIA_C_HEADER.h")
-#[[#ifndef]]# ${INCLUDE_GUARD}
-#[[#define]]# ${INCLUDE_GUARD}
-
-#[[#endif]]# //${INCLUDE_GUARD}
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C Source File.c b/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C Source File.c
deleted file mode 100644
index b04dd6c62..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C Source File.c
+++ /dev/null
@@ -1,4 +0,0 @@
-#parse("NVIDIA_C_HEADER.h")
-#if (${HEADER_FILENAME})
-#[[#include]]# "${HEADER_FILENAME}"
-#end
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C++ Class Header.h b/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C++ Class Header.h
deleted file mode 100644
index f521fa555..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C++ Class Header.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#parse("NVIDIA_C_HEADER.h")
-#[[#ifndef]]# ${INCLUDE_GUARD}
-#[[#define]]# ${INCLUDE_GUARD}
-
-${NAMESPACES_OPEN}
-
-class ${NAME} {
-
-};
-
-${NAMESPACES_CLOSE}
-
-#[[#endif]]# //${INCLUDE_GUARD}
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C++ Class.cc b/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C++ Class.cc
deleted file mode 100644
index 42f43ccf4..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/C++ Class.cc
+++ /dev/null
@@ -1,2 +0,0 @@
-#parse("NVIDIA_C_HEADER.h")
-#[[#include]]# "${HEADER_FILENAME}"
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/CMakeLists.txt.cmake b/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/CMakeLists.txt.cmake
deleted file mode 100644
index 846356219..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/fileTemplates/internal/CMakeLists.txt.cmake
+++ /dev/null
@@ -1 +0,0 @@
-#parse("NVIDIA_CMAKE_HEADER.cmake")
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/misc.xml b/cpp/plugins/cucim.kit.cuslide/.idea/misc.xml
deleted file mode 100644
index 2019083a1..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/cpp/plugins/cucim.kit.cuslide/.idea/vcs.xml b/cpp/plugins/cucim.kit.cuslide/.idea/vcs.xml
deleted file mode 100644
index fbbc5665e..000000000
--- a/cpp/plugins/cucim.kit.cuslide/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/cpp/plugins/cucim.kit.cuslide/cmake/deps/libjpeg-turbo.cmake b/cpp/plugins/cucim.kit.cuslide/cmake/deps/libjpeg-turbo.cmake
index ba750c92d..19f578ea9 100644
--- a/cpp/plugins/cucim.kit.cuslide/cmake/deps/libjpeg-turbo.cmake
+++ b/cpp/plugins/cucim.kit.cuslide/cmake/deps/libjpeg-turbo.cmake
@@ -31,7 +31,22 @@ if (NOT TARGET deps::libjpeg-turbo)
# full path to the compiler, or to the compiler name if it is in the PATH.
# yasm is available through `sudo apt-get install yasm` on Debian Linux.
# See _deps/deps-libjpeg-turbo-src/simd/CMakeLists.txt:25.
- set(CMAKE_ASM_NASM_COMPILER yasm)
+
+ # Try to find yasm in conda environment first, then system paths
+ if(DEFINED ENV{CONDA_PREFIX})
+ find_program(YASM_EXECUTABLE NAMES yasm PATHS $ENV{CONDA_PREFIX}/bin NO_DEFAULT_PATH)
+ endif()
+ if(NOT YASM_EXECUTABLE)
+ find_program(YASM_EXECUTABLE NAMES yasm)
+ endif()
+
+ if(YASM_EXECUTABLE)
+ set(CMAKE_ASM_NASM_COMPILER ${YASM_EXECUTABLE})
+ message(STATUS "Found yasm: ${YASM_EXECUTABLE}")
+ else()
+ set(CMAKE_ASM_NASM_COMPILER yasm)
+ message(WARNING "yasm not found, using 'yasm' and hoping it's in PATH")
+ endif()
set(REQUIRE_SIMD 1) # CMP0077
message(STATUS "Fetching libjpeg-turbo sources")
diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp b/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp
index e05ef48b6..a935ef266 100644
--- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp
+++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.cpp
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: Copyright (c) 2020-2021, NVIDIA CORPORATION.
+ * SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -121,12 +121,17 @@ IFD::IFD(TIFF* tiff, uint16_t index, ifd_offset_t offset) : tiff_(tiff), ifd_ind
// TIFFPrintDirectory(tif, stdout, TIFFPRINT_STRIPS);
}
+IFD::~IFD()
+{
+}
+
bool IFD::read(const TIFF* tiff,
const cucim::io::format::ImageMetadataDesc* metadata,
const cucim::io::format::ImageReaderRegionRequestDesc* request,
cucim::io::format::ImageDataDesc* out_image_data)
{
PROF_SCOPED_RANGE(PROF_EVENT(ifd_read));
+
::TIFF* tif = tiff->tiff_client_;
uint16_t ifd_index = ifd_index_;
@@ -160,8 +165,11 @@ bool IFD::read(const TIFF* tiff,
raster = out_buf->data;
}
+ fmt::print("🔎 Checking is_read_optimizable(): {}\n", is_read_optimizable());
+
if (is_read_optimizable())
{
+ fmt::print("✅ Using optimized read path\n");
if (batch_size > 1)
{
ndim = 4;
@@ -210,8 +218,11 @@ bool IFD::read(const TIFF* tiff,
const IFD* ifd = this;
+ fmt::print("📍 location_len={}, batch_size={}, num_workers={}\n", location_len, batch_size, num_workers);
+
if (location_len > 1 || batch_size > 1 || num_workers > 0)
{
+ fmt::print("📍 Entering multi-location/batch/worker path\n");
// Reconstruct location
std::unique_ptr>* location_unique =
reinterpret_cast>*>(request->location_unique);
@@ -240,8 +251,16 @@ bool IFD::read(const TIFF* tiff,
std::unique_ptr batch_processor;
// Set raster_type to CUDA because loader will handle this with nvjpeg
- if (out_device.type() == cucim::io::DeviceType::kCUDA)
+ // BUT: NvJpegProcessor only handles JPEG (not JPEG2000), so check compression
+ fmt::print("📍 Checking device type: {} compression: {}\n",
+ static_cast(out_device.type()), compression_);
+
+ bool is_jpeg2000 = (compression_ == cuslide::jpeg2k::kAperioJpeg2kYCbCr ||
+ compression_ == cuslide::jpeg2k::kAperioJpeg2kRGB);
+
+ if (out_device.type() == cucim::io::DeviceType::kCUDA && !is_jpeg2000)
{
+ fmt::print("📍 Using CUDA device path with nvjpeg loader\n");
raster_type = cucim::io::DeviceType::kCUDA;
// The maximal number of tiles (x-axis) overapped with the given patch
@@ -270,20 +289,32 @@ bool IFD::read(const TIFF* tiff,
prefetch_factor = nvjpeg_processor->preferred_loader_prefetch_factor();
batch_processor = std::move(nvjpeg_processor);
+ fmt::print("📍 NvJpegProcessor created\n");
+ }
+ else if (is_jpeg2000)
+ {
+ fmt::print("⚠️ JPEG2000 detected - skipping NvJpegProcessor (will use nvImageCodec/OpenJPEG)\n");
}
+ fmt::print("📍 Creating ThreadBatchDataLoader (location_len={}, batch_size={}, num_workers={})\n",
+ location_len, batch_size, num_workers);
auto loader = std::make_unique(
load_func, std::move(batch_processor), out_device, std::move(request_location), std::move(request_size),
location_len, one_raster_size, batch_size, prefetch_factor, num_workers);
+ fmt::print("📍 ThreadBatchDataLoader created\n");
const uint32_t load_size = std::min(static_cast(batch_size) * (1 + prefetch_factor), location_len);
+ fmt::print("📍 Calling loader->request({})\n", load_size);
loader->request(load_size);
+ fmt::print("📍 loader->request() completed\n");
// If it reads entire image with multi threads (using loader), fetch the next item.
if (location_len == 1 && batch_size == 1)
{
+ fmt::print("📍 Calling loader->next_data()\n");
raster = loader->next_data();
+ fmt::print("📍 loader->next_data() returned\n");
}
out_image_data->loader = loader.release(); // set loader to out_image_data
@@ -665,20 +696,49 @@ bool IFD::read_region_tiles(const TIFF* tiff,
(pixel_offset_ex - tile_pixel_offset_x + 1) * samples_per_pixel :
(tw - tile_pixel_offset_x) * samples_per_pixel;
auto decode_func = [=, &image_cache]() {
- PROF_SCOPED_RANGE(PROF_EVENT_P(ifd_read_region_tiles_task, index_hash));
+ fmt::print("🔍🔍🔍 INSIDE decode_func lambda! index={}\n", index);
+ fflush(stdout);
+ // TEMPORARY: Disable profiling macro - it's causing the segfault
+ // PROF_SCOPED_RANGE(PROF_EVENT_P(ifd_read_region_tiles_task, index_hash));
+
+ fmt::print("🔍 Calculating nbytes_tile_index: tile_pixel_offset_sy={}, tw={}, tile_pixel_offset_x={}, samples_per_pixel={}\n",
+ tile_pixel_offset_sy, tw, tile_pixel_offset_x, samples_per_pixel);
+ fflush(stdout);
uint32_t nbytes_tile_index = (tile_pixel_offset_sy * tw + tile_pixel_offset_x) * samples_per_pixel;
+ fmt::print("🔍 nbytes_tile_index={}\n", nbytes_tile_index);
+ fflush(stdout);
+
uint32_t dest_pixel_index = dest_pixel_index_x;
+ fmt::print("🔍 dest_pixel_index={}\n", dest_pixel_index);
+ fflush(stdout);
+
uint8_t* tile_data = nullptr;
+ fmt::print("🔍 Checking tiledata_size: {}\n", tiledata_size);
+ fflush(stdout);
if (tiledata_size > 0)
{
+ fmt::print("🔍 Entered tiledata_size > 0 block\n");
+ fflush(stdout);
+
std::unique_ptr tile_raster =
std::unique_ptr(nullptr, cucim_free);
- if (loader && loader->batch_data_processor())
+ fmt::print("🔍 Created tile_raster unique_ptr\n");
+ fflush(stdout);
+
+ // TEMPORARY: Completely skip the loader path - it causes segfaults
+ // Go directly to the standard decode path (else block)
+ fmt::print("🔍 Skipping loader path, going to standard decode\n");
+ fflush(stdout);
+
+ if (false) // FORCE to skip loader path
{
+ // This block is never executed
switch (compression_method)
{
case COMPRESSION_JPEG:
+ case cuslide::jpeg2k::kAperioJpeg2kYCbCr: // 33003
+ case cuslide::jpeg2k::kAperioJpeg2kRGB: // 33005
break;
default:
throw std::runtime_error("Unsupported compression method");
@@ -698,41 +758,92 @@ bool IFD::read_region_tiles(const TIFF* tiff,
}
else
{
+ fmt::print("🔍 Entered else block - standard decode path\n");
+ fflush(stdout);
+
auto key = image_cache.create_key(ifd_hash_value, index);
+ fmt::print("🔍 Created cache key\n");
+ fflush(stdout);
+
image_cache.lock(index_hash);
+ fmt::print("🔍 Locked cache\n");
+ fflush(stdout);
+
auto value = image_cache.find(key);
- if (value)
+ fmt::print("🔍 Cache lookup complete\n");
+ fflush(stdout);
+
+ fmt::print("🔍 About to check if value exists (cache hit/miss)\n");
+ fflush(stdout);
+
+ bool value_exists = false;
+ try {
+ value_exists = (value != nullptr) && (value.get() != nullptr);
+ fmt::print("🔍 Value check complete: value_exists={}\n", value_exists);
+ fflush(stdout);
+ } catch (...) {
+ fmt::print("❌ Exception checking value!\n");
+ fflush(stdout);
+ throw;
+ }
+
+ if (value_exists)
{
+ fmt::print("🔍 Cache HIT - using cached tile\n");
+ fflush(stdout);
image_cache.unlock(index_hash);
tile_data = static_cast(value->data);
}
else
{
+ fmt::print("🔍 Cache MISS - need to decode tile\n");
+ fflush(stdout);
+
// Lifetime of tile_data is same with `value`
// : do not access this data when `value` is not accessible.
+ fmt::print("🔍 Checking cache_type: {}\n", static_cast(cache_type));
+ fflush(stdout);
+
if (cache_type != cucim::cache::CacheType::kNoCache)
{
+ fmt::print("🔍 Allocating from image_cache, size={}\n", tile_raster_nbytes);
+ fflush(stdout);
tile_data = static_cast(image_cache.allocate(tile_raster_nbytes));
+ fmt::print("🔍 Allocated tile_data={}\n", static_cast(tile_data));
+ fflush(stdout);
}
else
{
+ fmt::print("🔍 Allocating temporary buffer with cucim_malloc\n");
+ fflush(stdout);
// Allocate temporary buffer for tile data
tile_raster = std::unique_ptr(
reinterpret_cast(cucim_malloc(tile_raster_nbytes)), cucim_free);
tile_data = tile_raster.get();
+ fmt::print("🔍 Allocated tile_data={}\n", static_cast(tile_data));
+ fflush(stdout);
}
{
- PROF_SCOPED_RANGE(PROF_EVENT(ifd_decompression));
+ fmt::print("🔍 About to switch on compression_method={}\n", compression_method);
+ fflush(stdout);
+ // TEMPORARY: Disable profiling macro - it causes segfaults in lambdas
+ // PROF_SCOPED_RANGE(PROF_EVENT(ifd_decompression));
switch (compression_method)
{
case COMPRESSION_NONE:
+ fmt::print("🔍 Calling decode_raw\n");
+ fflush(stdout);
cuslide::raw::decode_raw(tiff_file, nullptr, tiledata_offset, tiledata_size,
&tile_data, tile_raster_nbytes, out_device);
break;
case COMPRESSION_JPEG:
+ fmt::print("🔍 Calling decode_libjpeg\n");
+ fflush(stdout);
cuslide::jpeg::decode_libjpeg(tiff_file, nullptr, tiledata_offset, tiledata_size,
jpegtable_data, jpegtable_count, &tile_data,
out_device, jpeg_color_space);
+ fmt::print("🔍 decode_libjpeg completed\n");
+ fflush(stdout);
break;
case COMPRESSION_ADOBE_DEFLATE:
case COMPRESSION_DEFLATE:
@@ -740,16 +851,27 @@ bool IFD::read_region_tiles(const TIFF* tiff,
&tile_data, tile_raster_nbytes, out_device);
break;
case cuslide::jpeg2k::kAperioJpeg2kYCbCr: // 33003
+ fmt::print("🔍 Calling decode_libopenjpeg (YCbCr)\n");
+ fflush(stdout);
cuslide::jpeg2k::decode_libopenjpeg(tiff_file, nullptr, tiledata_offset,
tiledata_size, &tile_data, tile_raster_nbytes,
out_device, cuslide::jpeg2k::ColorSpace::kSYCC);
+ fmt::print("🔍 decode_libopenjpeg (YCbCr) completed\n");
+ fflush(stdout);
break;
case cuslide::jpeg2k::kAperioJpeg2kRGB: // 33005
+ fmt::print("🔍 Calling decode_libopenjpeg (RGB), fd={}, offset={}, size={}\n",
+ tiff_file, tiledata_offset, tiledata_size);
+ fflush(stdout);
cuslide::jpeg2k::decode_libopenjpeg(tiff_file, nullptr, tiledata_offset,
tiledata_size, &tile_data, tile_raster_nbytes,
out_device, cuslide::jpeg2k::ColorSpace::kRGB);
+ fmt::print("🔍 decode_libopenjpeg completed!\n");
+ fflush(stdout);
break;
case COMPRESSION_LZW:
+ fmt::print("🔍 Calling decode_lzw\n");
+ fflush(stdout);
cuslide::lzw::decode_lzw(tiff_file, nullptr, tiledata_offset, tiledata_size,
&tile_data, tile_raster_nbytes, out_device);
// Apply unpredictor
@@ -759,27 +881,97 @@ bool IFD::read_region_tiles(const TIFF* tiff,
{
cuslide::lzw::horAcc8(tile_data, tile_raster_nbytes, nbytes_tw);
}
+ fmt::print("🔍 decode_lzw completed\n");
+ fflush(stdout);
break;
default:
+ fmt::print("❌ Unsupported compression method: {}\n", compression_method);
+ fflush(stdout);
throw std::runtime_error("Unsupported compression method");
}
+ fmt::print("🔍 Switch statement completed, decompression done\n");
+ fflush(stdout);
}
+ fmt::print("🔍 Creating cache value\n");
+ fflush(stdout);
value = image_cache.create_value(tile_data, tile_raster_nbytes);
+ fmt::print("🔍 Inserting into cache\n");
+ fflush(stdout);
image_cache.insert(key, value);
+ fmt::print("🔍 Unlocking cache\n");
+ fflush(stdout);
image_cache.unlock(index_hash);
+ fmt::print("🔍 Cache operations complete\n");
+ fflush(stdout);
}
+ fmt::print("🔍 Starting memcpy loop: tile_pixel_offset_sy={}, tile_pixel_offset_ey={}\n",
+ tile_pixel_offset_sy, tile_pixel_offset_ey);
+ fflush(stdout);
+
for (uint32_t ty = tile_pixel_offset_sy; ty <= tile_pixel_offset_ey;
++ty, dest_pixel_index += dest_pixel_step_y, nbytes_tile_index += nbytes_tw)
{
- memcpy(dest_start_ptr + dest_pixel_index, tile_data + nbytes_tile_index,
- nbytes_tile_pixel_size_x);
+ fmt::print("🔍 memcpy iteration ty={}\n", ty);
+ fmt::print("🔍 dest_start_ptr={}, dest_pixel_index={}, dest_ptr={}\n",
+ static_cast(dest_start_ptr), dest_pixel_index,
+ static_cast(dest_start_ptr + dest_pixel_index));
+ fmt::print("🔍 tile_data={}, nbytes_tile_index={}, src_ptr={}\n",
+ static_cast(tile_data), nbytes_tile_index,
+ static_cast(tile_data + nbytes_tile_index));
+ fmt::print("🔍 nbytes_tile_pixel_size_x={} (copy size)\n", nbytes_tile_pixel_size_x);
+ fflush(stdout);
+
+ // Validate pointers before memcpy
+ if (!dest_start_ptr) {
+ fmt::print("❌ ERROR: dest_start_ptr is NULL!\n");
+ fflush(stdout);
+ throw std::runtime_error("dest_start_ptr is NULL");
+ }
+ if (!tile_data) {
+ fmt::print("❌ ERROR: tile_data is NULL!\n");
+ fflush(stdout);
+ throw std::runtime_error("tile_data is NULL");
+ }
+
+ fmt::print("🔍 Calling memcpy (device type={})...\n", static_cast(out_device.type()));
+ fflush(stdout);
+
+ // Use appropriate copy method based on destination device
+ if (out_device.type() == cucim::io::DeviceType::kCUDA)
+ {
+ // Copy from CPU (tile_data) to GPU (dest_start_ptr)
+ cudaError_t cuda_status = cudaMemcpy(
+ dest_start_ptr + dest_pixel_index,
+ tile_data + nbytes_tile_index,
+ nbytes_tile_pixel_size_x,
+ cudaMemcpyHostToDevice
+ );
+ if (cuda_status != cudaSuccess)
+ {
+ fmt::print(stderr, "❌ cudaMemcpy failed: {}\n", cudaGetErrorString(cuda_status));
+ fflush(stderr);
+ throw std::runtime_error(fmt::format("cudaMemcpy failed: {}", cudaGetErrorString(cuda_status)));
+ }
+ }
+ else
+ {
+ // CPU to CPU copy
+ memcpy(dest_start_ptr + dest_pixel_index, tile_data + nbytes_tile_index,
+ nbytes_tile_pixel_size_x);
+ }
+ fmt::print("🔍 memcpy succeeded\n");
+ fflush(stdout);
}
+ fmt::print("🔍 memcpy loop completed\n");
+ fflush(stdout);
}
}
else
{
+ fmt::print("🔍 tiledata_size <= 0, filling with background\n");
+ fflush(stdout);
if (out_device.type() == cucim::io::DeviceType::kCPU)
{
for (uint32_t ty = tile_pixel_offset_sy; ty <= tile_pixel_offset_ey;
@@ -797,16 +989,27 @@ bool IFD::read_region_tiles(const TIFF* tiff,
tile_pixel_offset_ey - tile_pixel_offset_sy + 1));
}
}
+ fmt::print("🔍🔍🔍 decode_func lambda COMPLETE! Exiting...\n");
+ fflush(stdout);
};
- if (loader && *loader)
+ // TEMPORARY: Force single-threaded execution to isolate segfault
+ bool force_single_threaded = true;
+
+ if (force_single_threaded || !loader || !(*loader))
{
- loader->enqueue(std::move(decode_func),
- cucim::loader::TileInfo{ location_index, index, tiledata_offset, tiledata_size });
+ fmt::print("🔍 Executing decode_func directly (FORCED SINGLE-THREADED)\n");
+ fmt::print("🔍 index={}, tiledata_offset={}, tiledata_size={}\n", index, tiledata_offset, tiledata_size);
+ fmt::print("🔍 About to call decode_func()...\n");
+ fflush(stdout);
+ decode_func();
+ fmt::print("🔍 decode_func completed successfully\n");
+ fflush(stdout);
}
else
{
- decode_func();
+ loader->enqueue(std::move(decode_func),
+ cucim::loader::TileInfo{ location_index, index, tiledata_offset, tiledata_size });
}
dest_pixel_index_x += nbytes_tile_pixel_size_x;
@@ -975,7 +1178,8 @@ bool IFD::read_region_tiles_boundary(const TIFF* tiff,
uint32_t dest_pixel_index_orig = dest_pixel_index_x;
auto decode_func = [=, &image_cache]() {
- PROF_SCOPED_RANGE(PROF_EVENT_P(ifd_read_region_tiles_boundary_task, index_hash));
+ // TEMPORARY: Disable profiling macro - it causes segfaults in lambdas
+ // PROF_SCOPED_RANGE(PROF_EVENT_P(ifd_read_region_tiles_boundary_task, index_hash));
uint32_t nbytes_tile_index = nbytes_tile_index_orig;
uint32_t dest_pixel_index = dest_pixel_index_orig;
@@ -1022,6 +1226,8 @@ bool IFD::read_region_tiles_boundary(const TIFF* tiff,
switch (compression_method)
{
case COMPRESSION_JPEG:
+ case cuslide::jpeg2k::kAperioJpeg2kYCbCr: // 33003
+ case cuslide::jpeg2k::kAperioJpeg2kRGB: // 33005
break;
default:
throw std::runtime_error("Unsupported compression method");
@@ -1099,7 +1305,8 @@ bool IFD::read_region_tiles_boundary(const TIFF* tiff,
tile_data = tile_raster.get();
}
{
- PROF_SCOPED_RANGE(PROF_EVENT(ifd_decompression));
+ // TEMPORARY: Disable profiling macro - it causes segfaults in lambdas
+ // PROF_SCOPED_RANGE(PROF_EVENT(ifd_decompression));
switch (compression_method)
{
case COMPRESSION_NONE:
@@ -1107,9 +1314,13 @@ bool IFD::read_region_tiles_boundary(const TIFF* tiff,
&tile_data, tile_raster_nbytes, out_device);
break;
case COMPRESSION_JPEG:
+ fmt::print("🔍 Calling decode_libjpeg\n");
+ fflush(stdout);
cuslide::jpeg::decode_libjpeg(tiff_file, nullptr, tiledata_offset, tiledata_size,
jpegtable_data, jpegtable_count, &tile_data,
out_device, jpeg_color_space);
+ fmt::print("🔍 decode_libjpeg completed\n");
+ fflush(stdout);
break;
case COMPRESSION_ADOBE_DEFLATE:
case COMPRESSION_DEFLATE:
@@ -1117,14 +1328,23 @@ bool IFD::read_region_tiles_boundary(const TIFF* tiff,
&tile_data, tile_raster_nbytes, out_device);
break;
case cuslide::jpeg2k::kAperioJpeg2kYCbCr: // 33003
+ fmt::print("🔍 Calling decode_libopenjpeg (YCbCr)\n");
+ fflush(stdout);
cuslide::jpeg2k::decode_libopenjpeg(tiff_file, nullptr, tiledata_offset,
tiledata_size, &tile_data, tile_raster_nbytes,
out_device, cuslide::jpeg2k::ColorSpace::kSYCC);
+ fmt::print("🔍 decode_libopenjpeg (YCbCr) completed\n");
+ fflush(stdout);
break;
case cuslide::jpeg2k::kAperioJpeg2kRGB: // 33005
+ fmt::print("🔍 Calling decode_libopenjpeg (RGB), fd={}, offset={}, size={}\n",
+ tiff_file, tiledata_offset, tiledata_size);
+ fflush(stdout);
cuslide::jpeg2k::decode_libopenjpeg(tiff_file, nullptr, tiledata_offset,
tiledata_size, &tile_data, tile_raster_nbytes,
out_device, cuslide::jpeg2k::ColorSpace::kRGB);
+ fmt::print("🔍 decode_libopenjpeg completed!\n");
+ fflush(stdout);
break;
case COMPRESSION_LZW:
cuslide::lzw::decode_lzw(tiff_file, nullptr, tiledata_offset, tiledata_size,
@@ -1208,14 +1428,23 @@ bool IFD::read_region_tiles_boundary(const TIFF* tiff,
}
};
- if (loader && *loader)
+ // TEMPORARY: Force single-threaded execution to isolate segfault
+ bool force_single_threaded = true;
+
+ if (force_single_threaded || !loader || !(*loader))
{
- loader->enqueue(std::move(decode_func),
- cucim::loader::TileInfo{ location_index, index, tiledata_offset, tiledata_size });
+ fmt::print("🔍 Executing decode_func directly (FORCED SINGLE-THREADED)\n");
+ fmt::print("🔍 index={}, tiledata_offset={}, tiledata_size={}\n", index, tiledata_offset, tiledata_size);
+ fmt::print("🔍 About to call decode_func()...\n");
+ fflush(stdout);
+ decode_func();
+ fmt::print("🔍 decode_func completed successfully\n");
+ fflush(stdout);
}
else
{
- decode_func();
+ loader->enqueue(std::move(decode_func),
+ cucim::loader::TileInfo{ location_index, index, tiledata_offset, tiledata_size });
}
dest_pixel_index_x += nbytes_tile_pixel_size_x;
diff --git a/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.h b/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.h
index da8c3734d..2bba25751 100644
--- a/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.h
+++ b/cpp/plugins/cucim.kit.cuslide/src/cuslide/tiff/ifd.h
@@ -1,5 +1,5 @@
/*
- * SPDX-FileCopyrightText: Copyright (c) 2020, NVIDIA CORPORATION.
+ * SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
* SPDX-License-Identifier: Apache-2.0
*/
@@ -15,6 +15,7 @@
#include
#include
#include
+
//#include
namespace cuslide::tiff
@@ -27,7 +28,7 @@ class EXPORT_VISIBLE IFD : public std::enable_shared_from_this
{
public:
IFD(TIFF* tiff, uint16_t index, ifd_offset_t offset);
- ~IFD() = default;
+ ~IFD();
static bool read_region_tiles(const TIFF* tiff,
const IFD* ifd,
diff --git a/cpp/plugins/cucim.kit.cuslide2/.clang-format b/cpp/plugins/cucim.kit.cuslide2/.clang-format
new file mode 100644
index 000000000..bcadc9d0b
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/.clang-format
@@ -0,0 +1,86 @@
+AccessModifierOffset: -4
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: false
+AlignEscapedNewlinesLeft: false
+AlignTrailingComments: false
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortFunctionsOnASingleLine: false
+AllowShortIfStatementsOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine : false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: false
+AlwaysBreakBeforeMultilineStrings: true
+AlwaysBreakTemplateDeclarations: true
+BinPackArguments: true
+BinPackParameters: false
+BreakBeforeBinaryOperators: false
+BreakBeforeBraces: Custom
+BraceWrapping:
+ AfterClass: true
+ AfterControlStatement: true
+ AfterEnum: true
+ AfterFunction: true
+ AfterNamespace: true
+ AfterObjCDeclaration: true
+ AfterStruct: true
+ AfterUnion: true
+ AfterExternBlock: true
+ BeforeCatch: true
+ BeforeElse: true
+ IndentBraces: false
+ SplitEmptyFunction: true
+ SplitEmptyRecord: true
+ SplitEmptyNamespace : true
+BreakBeforeTernaryOperators: false
+BreakConstructorInitializersBeforeComma: false
+BreakStringLiterals: false
+ColumnLimit: 120
+CommentPragmas: ''
+ConstructorInitializerAllOnOneLineOrOnePerLine: true
+ConstructorInitializerIndentWidth: 4
+ContinuationIndentWidth: 4
+Cpp11BracedListStyle: false
+DerivePointerBinding: false
+FixNamespaceComments: true
+IndentCaseLabels: false
+IndentPPDirectives: AfterHash
+IndentFunctionDeclarationAfterType: false
+IndentWidth: 4
+SortIncludes: false
+IncludeCategories:
+ - Regex: '[<"](.*\/)?Defines.h[>"]'
+ Priority: 1
+# - Regex: ''
+# Priority: 3
+ - Regex: '<[[:alnum:]_.]+>'
+ Priority: 5
+ - Regex: '<[[:alnum:]_.\/]+>'
+ Priority: 4
+ - Regex: '".*"'
+ Priority: 2
+IncludeBlocks: Regroup
+Language: Cpp
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: None
+ObjCSpaceAfterProperty: true
+ObjCSpaceBeforeProtocolList: true
+PenaltyBreakBeforeFirstCallParameter: 0
+PenaltyBreakComment: 1
+PenaltyBreakFirstLessLess: 0
+PenaltyBreakString: 1
+PenaltyExcessCharacter: 10
+PenaltyReturnTypeOnItsOwnLine: 1000
+PointerAlignment: Left
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+Standard: Cpp11
+ReflowComments: true
+TabWidth: 4
+UseTab: Never
diff --git a/cpp/plugins/cucim.kit.cuslide2/.editorconfig b/cpp/plugins/cucim.kit.cuslide2/.editorconfig
new file mode 100644
index 000000000..c69a96fa2
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/.editorconfig
@@ -0,0 +1,7 @@
+[*]
+indent_style = space
+indent_size = 4
+charset = utf-8
+trim_trailing_whitespace = true
+max_line_length = 120
+insert_final_newline = true
diff --git a/cpp/plugins/cucim.kit.cuslide2/.gitignore b/cpp/plugins/cucim.kit.cuslide2/.gitignore
new file mode 100644
index 000000000..84a73e644
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/.gitignore
@@ -0,0 +1,2 @@
+cmake-build*
+install
diff --git a/cpp/plugins/cucim.kit.cuslide2/CMakeLists.txt b/cpp/plugins/cucim.kit.cuslide2/CMakeLists.txt
new file mode 100644
index 000000000..2a657cdaa
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/CMakeLists.txt
@@ -0,0 +1,328 @@
+# Apache License, Version 2.0
+# cmake-format: off
+# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION
+# SPDX-License-Identifier: Apache-2.0
+# cmake-format: on
+
+cmake_minimum_required(VERSION 3.24.0 FATAL_ERROR)
+
+################################################################################
+# Prerequisite statements
+################################################################################
+
+# Set VERSION
+unset(VERSION CACHE)
+file(STRINGS ${CMAKE_CURRENT_LIST_DIR}/../../../VERSION VERSION)
+# strip alpha version info
+string(REGEX REPLACE "a.*$" "" VERSION ${VERSION})
+
+# Append local cmake module path
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules")
+
+project(cuslide2 VERSION ${VERSION} DESCRIPTION "cuslide2" LANGUAGES C CXX)
+set(CUCIM_PLUGIN_NAME "cucim.kit.cuslide2")
+
+################################################################################
+# Include utilities
+################################################################################
+include(SuperBuildUtils)
+include(CuCIMUtils)
+
+################################################################################
+# Set cmake policy
+################################################################################
+if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19")
+ cmake_policy(SET CMP0110 NEW) # For add_test() to support arbitrary characters in test name
+endif()
+
+################################################################################
+# Basic setup
+################################################################################
+
+# Set default build type
+set(DEFAULT_BUILD_TYPE "Release")
+if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
+ message(STATUS "Setting build type to '${DEFAULT_BUILD_TYPE}' as none was specified.")
+ set(CMAKE_BUILD_TYPE "${DEFAULT_BUILD_TYPE}" CACHE STRING "Choose the type of build." FORCE)
+ # Set the possible values of build type for cmake-gui
+ set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo")
+endif ()
+
+# Set default output directories
+if (NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib")
+endif()
+if (NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY)
+ set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib")
+endif()
+if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
+endif()
+
+# Find CUDAToolkit as rmm depends on it
+find_package(CUDAToolkit REQUIRED)
+# For Threads::Threads
+find_package(Threads REQUIRED)
+
+set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED YES)
+
+# Include CUDA headers explicitly for VSCode intelli-sense
+include_directories(AFTER SYSTEM ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES})
+
+# Disable visibility to not expose unnecessary symbols
+set(CMAKE_CXX_VISIBILITY_PRESET hidden)
+set(CMAKE_VISIBILITY_INLINES_HIDDEN YES)
+
+# Set RPATH
+if (NOT APPLE)
+ set(CMAKE_INSTALL_RPATH $ORIGIN)
+endif()
+
+# Set Installation setup
+if (NOT CMAKE_INSTALL_PREFIX)
+ set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_LIST_DIR}/install) # CACHE PATH "install here" FORCE)
+endif ()
+
+include(GNUInstallDirs)
+# Force to set CMAKE_INSTALL_LIBDIR to lib as the library can be built with Cent OS ('lib64' is set) and
+# /usr/local/lib64 or /usr/local/lib is not part of ld.so.conf* (`cat /etc/ld.so.conf.d/* | grep lib64`)
+# https://gitlab.kitware.com/cmake/cmake/-/issues/20565
+set(CMAKE_INSTALL_LIBDIR lib)
+
+include(ExternalProject)
+
+################################################################################
+# Options
+################################################################################
+
+# Setup CXX11 ABI
+# : Adds CXX11 ABI definition to the compiler command line for targets in the current directory,
+# whether added before or after this command is invoked, and for the ones in sub-directories added after.
+add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) # TODO: create two library, one with CXX11 ABI and one without it.
+
+################################################################################
+# Define dependencies - PURE nvImageCodec (minimal dependencies)
+################################################################################
+superbuild_depend(fmt)
+# REMOVED: CPU decoder dependencies (nvImageCodec handles all compression)
+# superbuild_depend(libjpeg-turbo)
+# superbuild_depend(libopenjpeg)
+# superbuild_depend(libtiff)
+# superbuild_depend(libdeflate)
+superbuild_depend(openslide)
+# Testing dependencies
+superbuild_depend(catch2)
+superbuild_depend(googletest)
+superbuild_depend(googlebenchmark)
+superbuild_depend(cli11)
+superbuild_depend(pugixml)
+superbuild_depend(json)
+superbuild_depend(nvimgcodec)
+
+################################################################################
+# Find cucim package
+################################################################################
+if (NOT CUCIM_SDK_PATH)
+ get_filename_component(CUCIM_SDK_PATH "${CMAKE_SOURCE_DIR}/../../.." ABSOLUTE)
+ message("CUCIM_SDK_PATH is not set. Using '${CUCIM_SDK_PATH}'")
+else()
+ message("CUCIM_SDK_PATH is set to ${CUCIM_SDK_PATH}")
+endif()
+
+find_package(cucim CONFIG REQUIRED
+ HINTS ${CUCIM_SDK_PATH}/install/${CMAKE_INSTALL_LIBDIR}/cmake/cucim
+ $ENV{PREFIX}/include/cmake/cucim # In case conda build is used
+ )
+
+
+################################################################################
+# Define compile options
+################################################################################
+
+if(NOT BUILD_SHARED_LIBS)
+ set(BUILD_SHARED_LIBS ON)
+endif()
+
+################################################################################
+# Add library: cucim
+################################################################################
+
+# NOTE: Commented out for infrastructure-only PR. Will be enabled in follow-up PR
+# with actual implementation once source files are added.
+# TODO: Uncomment this section when src/ directory is added with implementation
+
+message(STATUS "=============================================================")
+message(STATUS "cuslide2 PURE nvImageCodec - Dependencies:")
+message(STATUS " ✓ fmt (logging)")
+message(STATUS " ✓ nvImageCodec (GPU-accelerated JPEG/JPEG2000/deflate/LZW)")
+message(STATUS " ✓ pugixml (XML metadata parsing)")
+message(STATUS " ✓ json (JSON metadata)")
+message(STATUS " ✓ openslide (for testing)")
+message(STATUS " ✓ googletest, catch2, googlebenchmark, cli11 (testing)")
+message(STATUS "")
+message(STATUS "Build configured: Pure GPU-accelerated decoding with tests!")
+message(STATUS "=============================================================")
+
+# Add library - PURE nvImageCodec implementation (no CPU fallbacks)
+add_library(${CUCIM_PLUGIN_NAME}
+ # Main plugin interface
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/cuslide.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/cuslide.h
+ # TIFF structure management (uses nvImageCodec for parsing)
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/tiff/ifd.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/tiff/ifd.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/tiff/tiff.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/tiff/tiff.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/tiff/types.h
+ # nvImageCodec decoding and TIFF parsing (GPU-accelerated)
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/nvimgcodec/nvimgcodec_decoder.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/nvimgcodec/nvimgcodec_decoder.h
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/nvimgcodec/nvimgcodec_tiff_parser.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/cuslide/nvimgcodec/nvimgcodec_tiff_parser.h)
+
+# No special source file properties needed for pure nvImageCodec implementation
+
+# Compile options
+set_target_properties(${CUCIM_PLUGIN_NAME}
+ PROPERTIES
+ CXX_STANDARD 17
+ CXX_STANDARD_REQUIRED YES
+ CXX_EXTENSIONS NO
+ SOVERSION ${PROJECT_VERSION_MAJOR}
+ VERSION ${PROJECT_VERSION}
+)
+target_compile_features(${CUCIM_PLUGIN_NAME} PRIVATE cxx_std_17)
+# Use generator expression to avoid `nvcc fatal : Value '-std=c++17' is not defined for option 'Werror'`
+target_compile_options(${CUCIM_PLUGIN_NAME} PRIVATE $<$:-Werror -Wall -Wextra>)
+
+# Link libraries - Core dependencies (always required)
+target_link_libraries(${CUCIM_PLUGIN_NAME}
+ PRIVATE
+ deps::fmt
+ cucim::cucim
+ deps::pugixml
+ deps::json
+ )
+
+# Conditionally link nvImageCodec if available
+if(TARGET deps::nvimgcodec)
+ get_target_property(NVIMGCODEC_LOCATION deps::nvimgcodec IMPORTED_LOCATION)
+ get_target_property(NVIMGCODEC_INTERFACE_LINK deps::nvimgcodec INTERFACE_LINK_LIBRARIES)
+
+ # Check if it's a real target (has location or interface links) vs dummy target
+ if(NVIMGCODEC_LOCATION OR NVIMGCODEC_INTERFACE_LINK OR TARGET nvimgcodec::nvimgcodec)
+ target_link_libraries(${CUCIM_PLUGIN_NAME} PRIVATE deps::nvimgcodec)
+ target_compile_definitions(${CUCIM_PLUGIN_NAME} PRIVATE CUCIM_HAS_NVIMGCODEC)
+ message(STATUS "✓ nvImageCodec enabled - GPU-accelerated JPEG/JPEG2000 decoding available")
+ else()
+ message(STATUS "⚠ nvImageCodec target exists but is dummy - GPU acceleration disabled")
+ endif()
+else()
+ message(STATUS "⚠ nvImageCodec target not found - GPU acceleration disabled")
+endif()
+if (TARGET CUDA::nvjpeg_static)
+ target_link_libraries(${CUCIM_PLUGIN_NAME}
+ PRIVATE
+ # Add nvjpeg before cudart so that nvjpeg.h in static library takes precedence.
+ CUDA::nvjpeg_static
+ # Add CUDA::culibos to link necessary methods for 'deps::nvjpeg_static'
+ CUDA::culibos
+ CUDA::cudart
+ )
+else()
+ target_link_libraries(${CUCIM_PLUGIN_NAME}
+ PRIVATE
+ CUDA::nvjpeg
+ CUDA::cudart
+ )
+endif()
+
+target_include_directories(${CUCIM_PLUGIN_NAME}
+ PUBLIC
+ $
+ $
+ PRIVATE
+ ${CMAKE_CURRENT_SOURCE_DIR}/../cucim.kit.cuslide/src
+ ${CMAKE_CURRENT_SOURCE_DIR}/src # Include cuslide2 src for nvimgcodec headers
+ )
+
+# Do not generate SONAME as this would be used as plugin
+# Need to use IMPORTED_NO_SONAME when using this .so file.
+set_target_properties(${CUCIM_PLUGIN_NAME} PROPERTIES NO_SONAME 1)
+# Prevent relative path problem of .so with no DT_SONAME.
+# : https://stackoverflow.com/questions/27261288/cmake-linking-shared-c-object-from-externalproject-produces-binaries-with-rel
+target_link_options(${CUCIM_PLUGIN_NAME} PRIVATE "LINKER:-soname=${CUCIM_PLUGIN_NAME}@${PROJECT_VERSION}.so")
+
+# Do not add 'lib' prefix for the library
+set_target_properties(${CUCIM_PLUGIN_NAME} PROPERTIES PREFIX "")
+# Postfix version
+set_target_properties(${CUCIM_PLUGIN_NAME} PROPERTIES OUTPUT_NAME "${CUCIM_PLUGIN_NAME}@${PROJECT_VERSION}")
+
+#set_target_properties(${CUCIM_PLUGIN_NAME} PROPERTIES LINK_FLAGS
+# "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/cuslide.map")
+
+################################################################################
+# Add tests
+################################################################################
+add_subdirectory(tests)
+add_subdirectory(benchmarks)
+
+################################################################################
+# Install
+################################################################################
+# NOTE: Disabled for infrastructure-only PR
+# TODO: Uncomment when library target exists
+set(INSTALL_TARGETS
+ ${CUCIM_PLUGIN_NAME}
+ # cuslide_tests # Disabled for infrastructure-only PR
+ # cuslide_benchmarks # Disabled for infrastructure-only PR
+ )
+
+install(TARGETS ${INSTALL_TARGETS}
+ EXPORT ${CUCIM_PLUGIN_NAME}-targets
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ COMPONENT ${CUCIM_PLUGIN_NAME}_Runtime
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ COMPONENT ${CUCIM_PLUGIN_NAME}_Runtime
+ NAMELINK_COMPONENT ${CUCIM_PLUGIN_NAME}_Development
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ COMPONENT ${CUCIM_PLUGIN_NAME}_Development
+ )
+
+# Currently cuslide plugin doesn't have include path so comment out
+# install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
+install(EXPORT ${CUCIM_PLUGIN_NAME}-targets
+ FILE
+ ${CUCIM_PLUGIN_NAME}-targets.cmake
+ NAMESPACE
+ ${PROJECT_NAME}::
+ DESTINATION
+ ${CMAKE_INSTALL_LIBDIR}/cmake/${CUCIM_PLUGIN_NAME})
+
+# Write package configs
+include(CMakePackageConfigHelpers)
+configure_package_config_file(
+ ${CMAKE_CURRENT_SOURCE_DIR}/cmake/${CUCIM_PLUGIN_NAME}-config.cmake.in
+ ${CMAKE_CURRENT_BINARY_DIR}/cmake/${CUCIM_PLUGIN_NAME}-config.cmake
+ INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CUCIM_PLUGIN_NAME}
+)
+write_basic_package_version_file(
+ ${CMAKE_CURRENT_BINARY_DIR}/cmake/${CUCIM_PLUGIN_NAME}-config-version.cmake
+ VERSION ${PROJECT_VERSION}
+ COMPATIBILITY AnyNewerVersion
+)
+install(
+ FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/cmake/${CUCIM_PLUGIN_NAME}-config.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/cmake/${CUCIM_PLUGIN_NAME}-config-version.cmake
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${CUCIM_PLUGIN_NAME}
+)
+
+
+set(CMAKE_EXPORT_PACKAGE_REGISTRY ON)
+export(PACKAGE ${CUCIM_PLUGIN_NAME})
+
+# REMOVED: endif() - no longer needed since we removed the if(TRUE) wrapper
+
+unset(BUILD_SHARED_LIBS CACHE)
diff --git a/cpp/plugins/cucim.kit.cuslide2/benchmarks/CMakeLists.txt b/cpp/plugins/cucim.kit.cuslide2/benchmarks/CMakeLists.txt
new file mode 100644
index 000000000..32c13ab73
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/benchmarks/CMakeLists.txt
@@ -0,0 +1,37 @@
+#
+# cmake-format: off
+# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
+# SPDX-License-Identifier: Apache-2.0
+# cmake-format: on
+#
+
+################################################################################
+# Add executable: cuslide_benchmarks
+################################################################################
+add_executable(cuslide_benchmarks main.cpp config.h)
+#set_source_files_properties(main.cpp PROPERTIES LANGUAGE CUDA) # failed with CLI11 library
+
+set_target_properties(cuslide_benchmarks
+ PROPERTIES
+ CXX_STANDARD 17
+ CXX_STANDARD_REQUIRED YES
+ CXX_EXTENSIONS NO
+)
+target_compile_features(cuslide_benchmarks PRIVATE ${CUCIM_REQUIRED_FEATURES})
+# Use generator expression to avoid `nvcc fatal : Value '-std=c++17' is not defined for option 'Werror'`
+target_compile_options(cuslide_benchmarks PRIVATE $<$:-Werror -Wall -Wextra>)
+target_compile_definitions(cuslide_benchmarks
+ PUBLIC
+ CUSLIDE_VERSION=${PROJECT_VERSION}
+ CUSLIDE_VERSION_MAJOR=${PROJECT_VERSION_MAJOR}
+ CUSLIDE_VERSION_MINOR=${PROJECT_VERSION_MINOR}
+ CUSLIDE_VERSION_PATCH=${PROJECT_VERSION_PATCH}
+ CUSLIDE_VERSION_BUILD=${PROJECT_VERSION_BUILD}
+)
+target_link_libraries(cuslide_benchmarks
+ PRIVATE
+ cucim::cucim
+ deps::googlebenchmark
+ deps::openslide
+ deps::cli11
+ )
diff --git a/cpp/plugins/cucim.kit.cuslide2/benchmarks/config.h b/cpp/plugins/cucim.kit.cuslide2/benchmarks/config.h
new file mode 100644
index 000000000..11071ce78
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/benchmarks/config.h
@@ -0,0 +1,69 @@
+/*
+ * Apache License, Version 2.0
+ * SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#ifndef CUSLIDE_CONFIG_H
+#define CUSLIDE_CONFIG_H
+
+#include
+
+struct AppConfig
+{
+ std::string test_folder;
+ std::string test_file;
+ bool discard_cache = false;
+ int random_seed = 0;
+ bool random_start_location = false;
+
+ int64_t image_width = 0;
+ int64_t image_height = 0;
+
+ // Pseudo configurations for google benchmark
+ bool benchmark_list_tests = false;
+ std::string benchmark_filter; //
+ int benchmark_min_time = 0; //
+ int benchmark_repetitions = 0; //
+ bool benchmark_report_aggregates_only = false;
+ bool benchmark_display_aggregates_only = false;
+ std::string benchmark_format; //
+ std::string benchmark_out; //
+ std::string benchmark_out_format; //
+ std::string benchmark_color; // {auto|true|false}
+ std::string benchmark_counters_tabular;
+ std::string v; //
+
+ std::string get_input_path(const std::string default_value = "generated/tiff_stripe_4096x4096_256.tif") const
+ {
+ // If `test_file` is absolute path
+ if (!test_folder.empty() && test_file.substr(0, 1) == "/")
+ {
+ return test_file;
+ }
+ else
+ {
+ std::string test_data_folder = test_folder;
+ if (test_data_folder.empty())
+ {
+ if (const char* env_p = std::getenv("CUCIM_TESTDATA_FOLDER"))
+ {
+ test_data_folder = env_p;
+ }
+ else
+ {
+ test_data_folder = "test_data";
+ }
+ }
+ if (test_file.empty())
+ {
+ return test_data_folder + "/" + default_value;
+ }
+ else
+ {
+ return test_data_folder + "/" + test_file;
+ }
+ }
+ }
+};
+
+#endif // CUSLIDE_CONFIG_H
diff --git a/cpp/plugins/cucim.kit.cuslide2/benchmarks/main.cpp b/cpp/plugins/cucim.kit.cuslide2/benchmarks/main.cpp
new file mode 100644
index 000000000..01605dca0
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/benchmarks/main.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (c) 2020-2022, NVIDIA CORPORATION.
+ *
+ * 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 "config.h"
+
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "cucim/core/framework.h"
+#include "cucim/io/format/image_format.h"
+#include "cucim/memory/memory_manager.h"
+
+#define XSTR(x) STR(x)
+#define STR(x) #x
+
+//#include
+
+CUCIM_FRAMEWORK_GLOBALS("cuslide.app")
+
+static AppConfig g_config;
+
+
+static void test_basic(benchmark::State& state)
+{
+ std::string input_path = g_config.get_input_path();
+
+ int arg = -1;
+ for (auto state_item : state)
+ {
+ state.PauseTiming();
+ {
+ // Use a different start random seed for the different argument
+ if (arg != state.range())
+ {
+ arg = state.range();
+ srand(g_config.random_seed + arg);
+ }
+
+ if (g_config.discard_cache)
+ {
+ int fd = open(input_path.c_str(), O_RDONLY);
+ fdatasync(fd);
+ posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+ close(fd);
+ }
+ }
+ state.ResumeTiming();
+
+ // auto start = std::chrono::high_resolution_clock::now();
+ cucim::Framework* framework = cucim::acquire_framework("cuslide.app");
+ if (!framework)
+ {
+ fmt::print("framework is not available!\n");
+ return;
+ }
+
+ cucim::io::format::IImageFormat* image_format =
+ framework->acquire_interface_from_library(
+ "cucim.kit.cuslide@" XSTR(CUSLIDE_VERSION) ".so");
+ // std::cout << image_format->formats[0].get_format_name() << std::endl;
+ if (image_format == nullptr)
+ {
+ fmt::print("plugin library is not available!\n");
+ return;
+ }
+
+ std::string input_path = g_config.get_input_path();
+ std::shared_ptr* file_handle_shared = reinterpret_cast*>(
+ image_format->formats[0].image_parser.open(input_path.c_str()));
+
+ std::shared_ptr file_handle = *file_handle_shared;
+ delete file_handle_shared;
+
+ // Set deleter to close the file handle
+ file_handle->set_deleter(image_format->formats[0].image_parser.close);
+
+ cucim::io::format::ImageMetadata metadata{};
+ image_format->formats[0].image_parser.parse(file_handle.get(), &metadata.desc());
+
+ cucim::io::format::ImageReaderRegionRequestDesc request{};
+ int64_t request_location[2] = { 0, 0 };
+ if (g_config.random_start_location)
+ {
+ request_location[0] = rand() % (g_config.image_width - state.range(0));
+ request_location[1] = rand() % (g_config.image_height - state.range(0));
+ }
+
+ request.location = request_location;
+ request.level = 0;
+ int64_t request_size[2] = { state.range(0), state.range(0) };
+ request.size = request_size;
+ request.device = const_cast("cpu");
+
+ cucim::io::format::ImageDataDesc image_data;
+
+ image_format->formats[0].image_reader.read(
+ file_handle.get(), &metadata.desc(), &request, &image_data, nullptr /*out_metadata*/);
+ cucim_free(image_data.container.data);
+
+ // auto end = std::chrono::high_resolution_clock::now();
+ // auto elapsed_seconds = std::chrono::duration_cast>(end - start);
+ // state.SetIterationTime(elapsed_seconds.count());
+ }
+}
+
+static void test_openslide(benchmark::State& state)
+{
+ std::string input_path = g_config.get_input_path();
+
+ int arg = -1;
+ for (auto _ : state)
+ {
+ state.PauseTiming();
+ {
+ // Use a different start random seed for the different argument
+ if (arg != state.range())
+ {
+ arg = state.range();
+ srand(g_config.random_seed + arg);
+ }
+
+ if (g_config.discard_cache)
+ {
+ int fd = open(input_path.c_str(), O_RDONLY);
+ fdatasync(fd);
+ posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
+ close(fd);
+ }
+ }
+ state.ResumeTiming();
+
+ openslide_t* slide = openslide_open(input_path.c_str());
+ uint32_t* buf = static_cast(cucim_malloc(state.range(0) * state.range(0) * 4));
+ int64_t request_location[2] = { 0, 0 };
+ if (g_config.random_start_location)
+ {
+ request_location[0] = rand() % (g_config.image_width - state.range(0));
+ request_location[1] = rand() % (g_config.image_height - state.range(0));
+ }
+ openslide_read_region(slide, buf, request_location[0], request_location[1], 0, state.range(0), state.range(0));
+ cucim_free(buf);
+ openslide_close(slide);
+ }
+}
+
+BENCHMARK(test_basic)->Unit(benchmark::kMicrosecond)->RangeMultiplier(2)->Range(1, 4096); //->UseManualTime();
+BENCHMARK(test_openslide)->Unit(benchmark::kMicrosecond)->RangeMultiplier(2)->Range(1, 4096);
+
+static bool remove_help_option(int* argc, char** argv)
+{
+ for (int i = 1; argc && i < *argc; ++i)
+ {
+ if (strncmp(argv[i], "-h", 3) == 0 || strncmp(argv[i], "--help", 7) == 0)
+ {
+ for (int j = i + 1; argc && j < *argc; ++j)
+ {
+ argv[j - 1] = argv[j];
+ }
+ --(*argc);
+ argv[*argc] = nullptr;
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool setup_configuration()
+{
+ std::string input_path = g_config.get_input_path();
+ openslide_t* slide = openslide_open(input_path.c_str());
+ if (slide == nullptr)
+ {
+ fmt::print("[Error] Cannot load {}!\n", input_path);
+ return false;
+ }
+
+ int64_t w, h;
+ openslide_get_level0_dimensions(slide, &w, &h);
+
+ g_config.image_width = w;
+ g_config.image_height = h;
+
+ openslide_close(slide);
+
+ return true;
+}
+
+// BENCHMARK_MAIN();
+int main(int argc, char** argv)
+{
+ // Skip processing help option
+ bool has_help_option = remove_help_option(&argc, argv);
+
+ ::benchmark::Initialize(&argc, argv);
+ // if (::benchmark::ReportUnrecognizedArguments(argc, argv))
+ // return 1;
+ CLI::App app{ "benchmark: cuSlide" };
+ app.add_option("--test_folder", g_config.test_folder, "An input test folder path");
+ app.add_option("--test_file", g_config.test_file, "An input test image file path");
+ app.add_option("--discard_cache", g_config.discard_cache, "Discard page cache for the input file for each iteration");
+ app.add_option("--random_seed", g_config.random_seed, "A random seed number");
+ app.add_option(
+ "--random_start_location", g_config.random_start_location, "Randomize start location of read_region()");
+
+ // Pseudo benchmark options
+ app.add_option("--benchmark_list_tests", g_config.benchmark_list_tests, "{true|false}");
+ app.add_option("--benchmark_filter", g_config.benchmark_filter, "");
+ app.add_option("--benchmark_min_time", g_config.benchmark_min_time, "");
+ app.add_option("--benchmark_repetitions", g_config.benchmark_repetitions, "");
+ app.add_option("--benchmark_report_aggregates_only", g_config.benchmark_report_aggregates_only, "{true|false}");
+ app.add_option("--benchmark_display_aggregates_only", g_config.benchmark_display_aggregates_only, "{true|false}");
+ app.add_option("--benchmark_format", g_config.benchmark_format, "");
+ app.add_option("--benchmark_out", g_config.benchmark_out, "");
+ app.add_option("--benchmark_out_format", g_config.benchmark_out_format, "");
+ app.add_option("--benchmark_color", g_config.benchmark_color, "{auto|true|false}");
+ app.add_option("--benchmark_counters_tabular", g_config.benchmark_counters_tabular, "{true|false}");
+ app.add_option("--v", g_config.v, "");
+
+ // Append help option if exists
+ if (has_help_option)
+ {
+ argv[argc] = const_cast("--help");
+ ++argc;
+ // https://github.com/matepek/vscode-catch2-test-adapter detects google benchmark binaries by the following
+ // text:
+ printf("benchmark [--benchmark_list_tests={true|false}]\n");
+ }
+ CLI11_PARSE(app, argc, argv);
+
+ if (!setup_configuration())
+ {
+ return 1;
+ }
+ ::benchmark::RunSpecifiedBenchmarks();
+}
diff --git a/cpp/plugins/cucim.kit.cuslide2/cmake/cucim.kit.cuslide-config.cmake.in b/cpp/plugins/cucim.kit.cuslide2/cmake/cucim.kit.cuslide-config.cmake.in
new file mode 100644
index 000000000..2b9bae2fc
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/cmake/cucim.kit.cuslide-config.cmake.in
@@ -0,0 +1,15 @@
+#
+# SPDX-FileCopyrightText: Copyright (c) 2020, NVIDIA CORPORATION.
+# SPDX-License-Identifier: Apache-2.0
+#
+
+@PACKAGE_INIT@
+
+# Find dependent libraries
+# ...
+include(CMakeFindDependencyMacro)
+#find_dependency(Boost x.x.x REQUIRED)
+
+if(NOT TARGET cuslide::cuslide)
+ include(${CMAKE_CURRENT_LIST_DIR}/cucim.kit.cuslide-targets.cmake)
+endif()
diff --git a/cpp/plugins/cucim.kit.cuslide2/cmake/cucim.kit.cuslide2-config.cmake.in b/cpp/plugins/cucim.kit.cuslide2/cmake/cucim.kit.cuslide2-config.cmake.in
new file mode 100644
index 000000000..4fbcf1e10
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/cmake/cucim.kit.cuslide2-config.cmake.in
@@ -0,0 +1,13 @@
+#
+# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
+# SPDX-License-Identifier: Apache-2.0
+#
+
+@PACKAGE_INIT@
+
+# Find dependent libraries
+# ...
+include(CMakeFindDependencyMacro)
+#find_dependency(Boost x.x.x REQUIRED)
+
+include(${CMAKE_CURRENT_LIST_DIR}/cucim.kit.cuslide2-targets.cmake)
diff --git a/cpp/plugins/cucim.kit.cuslide2/cmake/deps/nvimgcodec.cmake b/cpp/plugins/cucim.kit.cuslide2/cmake/deps/nvimgcodec.cmake
new file mode 100644
index 000000000..f20da219b
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/cmake/deps/nvimgcodec.cmake
@@ -0,0 +1,412 @@
+#
+# cmake-format: off
+# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
+# SPDX-License-Identifier: Apache-2.0
+# cmake-format: on
+#
+
+# nvImageCodec v0.7.0 internal release configuration
+# ===================================================
+#
+# This cmake module configures nvImageCodec for cuslide2. It supports:
+# 1. Pre-installed packages via NVIMGCODEC_ROOT
+# 2. Download from URL via NVIMGCODEC_URL
+# 3. Auto-detection in conda/pip/system paths
+#
+# For internal release v0.7.0, use one of these options:
+#
+# Option A - Specify local installation path (CUDA 12):
+# cmake -DNVIMGCODEC_ROOT=/home/cdinea/Downloads/cucim_pr3/nvimgcodec/12 ..
+#
+# Option B - Specify local installation path (CUDA 13):
+# cmake -DNVIMGCODEC_ROOT=/home/cdinea/Downloads/cucim_pr3/nvimgcodec/13 ..
+#
+# Option C - Use auto-detection with NVIMGCODEC_CUDA_VERSION:
+# cmake -DNVIMGCODEC_DIR=/home/cdinea/Downloads/cucim_pr3/nvimgcodec -DNVIMGCODEC_CUDA_VERSION=12 ..
+#
+# Available packages for v0.7.0 Build 11:
+# C Packages (CUDA 12.9 and 13.0):
+# - linux-x86_64, linux-sbsa, linux-aarch64 (12.9 only), windows-x86_64
+# Python Packages:
+# - CUDA 12 (12.9): linux-aarch64, linux-sbsa, linux-x86_64, windows-x86_64
+# - CUDA 13 (13.0): linux-sbsa, linux-x86_64, windows-x86_64
+
+set(NVIMGCODEC_VERSION "0.7.0" CACHE STRING "nvImageCodec version to use")
+set(NVIMGCODEC_ROOT "" CACHE PATH "Path to nvImageCodec installation directory (e.g., /path/to/nvimgcodec/12)")
+set(NVIMGCODEC_DIR "" CACHE PATH "Path to nvImageCodec parent directory containing CUDA version subdirs")
+set(NVIMGCODEC_CUDA_VERSION "" CACHE STRING "CUDA version to use (12 or 13) when NVIMGCODEC_DIR is set")
+set(NVIMGCODEC_URL "" CACHE STRING "URL to download nvImageCodec tarball from internal release")
+
+# Default nvimgcodec location for this machine
+set(NVIMGCODEC_DEFAULT_DIR "/home/cdinea/Downloads/cucim_pr3/nvimgcodec")
+
+if (NOT TARGET deps::nvimgcodec)
+ set(NVIMGCODEC_LIB_PATH "")
+ set(NVIMGCODEC_INCLUDE_PATH "")
+ set(NVIMGCODEC_EXTENSIONS_PATH "")
+ set(NVIMGCODEC_FOUND FALSE)
+
+ message(STATUS "")
+ message(STATUS "=== nvImageCodec v${NVIMGCODEC_VERSION} Configuration ===")
+
+ # =========================================================================
+ # Determine the actual root path to use
+ # =========================================================================
+ set(NVIMGCODEC_ACTUAL_ROOT "")
+
+ # Priority 1: Direct NVIMGCODEC_ROOT specification
+ if(NVIMGCODEC_ROOT AND EXISTS "${NVIMGCODEC_ROOT}")
+ set(NVIMGCODEC_ACTUAL_ROOT "${NVIMGCODEC_ROOT}")
+ message(STATUS "Using NVIMGCODEC_ROOT: ${NVIMGCODEC_ACTUAL_ROOT}")
+
+ # Priority 2: NVIMGCODEC_DIR + CUDA version
+ elseif(NVIMGCODEC_DIR AND EXISTS "${NVIMGCODEC_DIR}")
+ # Auto-detect CUDA version if not specified
+ if(NOT NVIMGCODEC_CUDA_VERSION)
+ # Try to detect from CUDAToolkit
+ if(CUDAToolkit_VERSION_MAJOR)
+ set(NVIMGCODEC_CUDA_VERSION "${CUDAToolkit_VERSION_MAJOR}")
+ message(STATUS "Auto-detected CUDA version: ${NVIMGCODEC_CUDA_VERSION}")
+ else()
+ set(NVIMGCODEC_CUDA_VERSION "12")
+ message(STATUS "Defaulting to CUDA version: ${NVIMGCODEC_CUDA_VERSION}")
+ endif()
+ endif()
+
+ if(EXISTS "${NVIMGCODEC_DIR}/${NVIMGCODEC_CUDA_VERSION}")
+ set(NVIMGCODEC_ACTUAL_ROOT "${NVIMGCODEC_DIR}/${NVIMGCODEC_CUDA_VERSION}")
+ message(STATUS "Using NVIMGCODEC_DIR with CUDA ${NVIMGCODEC_CUDA_VERSION}: ${NVIMGCODEC_ACTUAL_ROOT}")
+ endif()
+
+ # Priority 3: Check default location
+ elseif(EXISTS "${NVIMGCODEC_DEFAULT_DIR}")
+ # Auto-detect CUDA version
+ if(NOT NVIMGCODEC_CUDA_VERSION)
+ if(CUDAToolkit_VERSION_MAJOR)
+ set(NVIMGCODEC_CUDA_VERSION "${CUDAToolkit_VERSION_MAJOR}")
+ else()
+ set(NVIMGCODEC_CUDA_VERSION "12")
+ endif()
+ endif()
+
+ if(EXISTS "${NVIMGCODEC_DEFAULT_DIR}/${NVIMGCODEC_CUDA_VERSION}")
+ set(NVIMGCODEC_ACTUAL_ROOT "${NVIMGCODEC_DEFAULT_DIR}/${NVIMGCODEC_CUDA_VERSION}")
+ message(STATUS "Using default location with CUDA ${NVIMGCODEC_CUDA_VERSION}: ${NVIMGCODEC_ACTUAL_ROOT}")
+ endif()
+ endif()
+
+ # =========================================================================
+ # Method 1: Use CMake config files from the package (preferred)
+ # =========================================================================
+ if(NVIMGCODEC_ACTUAL_ROOT AND EXISTS "${NVIMGCODEC_ACTUAL_ROOT}/cmake/nvimgcodec/nvimgcodecConfig.cmake")
+ message(STATUS "Found nvImageCodec CMake config at: ${NVIMGCODEC_ACTUAL_ROOT}/cmake/nvimgcodec")
+
+ # Add to CMAKE_PREFIX_PATH for find_package
+ list(APPEND CMAKE_PREFIX_PATH "${NVIMGCODEC_ACTUAL_ROOT}/cmake")
+
+ find_package(nvimgcodec CONFIG QUIET
+ PATHS "${NVIMGCODEC_ACTUAL_ROOT}/cmake"
+ NO_DEFAULT_PATH
+ )
+
+ if(nvimgcodec_FOUND)
+ # The nvimgcodec CMake config sets these variables:
+ # nvimgcodec_INCLUDE_DIR, nvimgcodec_LIB_DIR, nvimgcodec_EXTENSIONS_DIR
+ # But it doesn't set INTERFACE_INCLUDE_DIRECTORIES on the target, so we must do it
+
+ # Get library path from target
+ get_target_property(_nvimgcodec_loc nvimgcodec::nvimgcodec IMPORTED_LOCATION_RELEASE)
+ if(NOT _nvimgcodec_loc)
+ get_target_property(_nvimgcodec_loc nvimgcodec::nvimgcodec IMPORTED_LOCATION)
+ endif()
+
+ # Use nvimgcodec_INCLUDE_DIR from the config (not from target property)
+ set(NVIMGCODEC_LIB_PATH "${_nvimgcodec_loc}")
+ set(NVIMGCODEC_INCLUDE_PATH "${nvimgcodec_INCLUDE_DIR}")
+ set(NVIMGCODEC_EXTENSIONS_PATH "${nvimgcodec_EXTENSIONS_DIR}")
+
+ # Add include directory to the nvimgcodec target (it's missing from the CMake config)
+ set_target_properties(nvimgcodec::nvimgcodec PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${nvimgcodec_INCLUDE_DIR}"
+ )
+
+ # Create our wrapper target
+ add_library(deps::nvimgcodec INTERFACE IMPORTED GLOBAL)
+ target_link_libraries(deps::nvimgcodec INTERFACE nvimgcodec::nvimgcodec)
+
+ set(NVIMGCODEC_FOUND TRUE)
+
+ message(STATUS "✓ nvImageCodec v${NVIMGCODEC_VERSION} found via CMake config")
+ message(STATUS " Include dir: ${nvimgcodec_INCLUDE_DIR}")
+ endif()
+ endif()
+
+ # =========================================================================
+ # Method 2: Manual detection in specified root
+ # =========================================================================
+ if(NOT NVIMGCODEC_FOUND AND NVIMGCODEC_ACTUAL_ROOT)
+ message(STATUS "Searching for nvImageCodec in: ${NVIMGCODEC_ACTUAL_ROOT}")
+
+ # Check for header file
+ if(EXISTS "${NVIMGCODEC_ACTUAL_ROOT}/include/nvimgcodec.h")
+ set(NVIMGCODEC_INCLUDE_PATH "${NVIMGCODEC_ACTUAL_ROOT}/include")
+ endif()
+
+ # Check for library file (lib64 first, then lib)
+ foreach(LIB_NAME "libnvimgcodec.so.0" "libnvimgcodec.so" "libnvimgcodec.so.${NVIMGCODEC_VERSION}")
+ foreach(LIB_DIR "lib64" "lib" "")
+ if(LIB_DIR)
+ set(LIB_CHECK_PATH "${NVIMGCODEC_ACTUAL_ROOT}/${LIB_DIR}/${LIB_NAME}")
+ else()
+ set(LIB_CHECK_PATH "${NVIMGCODEC_ACTUAL_ROOT}/${LIB_NAME}")
+ endif()
+ if(EXISTS "${LIB_CHECK_PATH}")
+ set(NVIMGCODEC_LIB_PATH "${LIB_CHECK_PATH}")
+ break()
+ endif()
+ endforeach()
+ if(NVIMGCODEC_LIB_PATH)
+ break()
+ endif()
+ endforeach()
+
+ # Check for extensions directory
+ if(EXISTS "${NVIMGCODEC_ACTUAL_ROOT}/extensions")
+ set(NVIMGCODEC_EXTENSIONS_PATH "${NVIMGCODEC_ACTUAL_ROOT}/extensions")
+ endif()
+
+ if(NVIMGCODEC_INCLUDE_PATH AND NVIMGCODEC_LIB_PATH)
+ set(NVIMGCODEC_FOUND TRUE)
+ message(STATUS "✓ nvImageCodec v${NVIMGCODEC_VERSION} found via manual detection")
+ endif()
+ endif()
+
+ # =========================================================================
+ # Method 3: Download from URL (internal release)
+ # =========================================================================
+ if(NOT NVIMGCODEC_FOUND AND NVIMGCODEC_URL)
+ message(STATUS "Downloading nvImageCodec from: ${NVIMGCODEC_URL}")
+
+ include(FetchContent)
+
+ FetchContent_Declare(
+ deps-nvimgcodec
+ URL ${NVIMGCODEC_URL}
+ DOWNLOAD_EXTRACT_TIMESTAMP TRUE
+ )
+
+ FetchContent_GetProperties(deps-nvimgcodec)
+ if(NOT deps-nvimgcodec_POPULATED)
+ message(STATUS "Fetching nvImageCodec v${NVIMGCODEC_VERSION}...")
+ FetchContent_Populate(deps-nvimgcodec)
+ message(STATUS "Fetching nvImageCodec v${NVIMGCODEC_VERSION} - done")
+ endif()
+
+ set(NVIMGCODEC_DOWNLOAD_DIR "${deps-nvimgcodec_SOURCE_DIR}")
+
+ # Search for headers and library in downloaded content
+ if(EXISTS "${NVIMGCODEC_DOWNLOAD_DIR}/include/nvimgcodec.h")
+ set(NVIMGCODEC_INCLUDE_PATH "${NVIMGCODEC_DOWNLOAD_DIR}/include")
+ endif()
+
+ foreach(LIB_NAME "libnvimgcodec.so.0" "libnvimgcodec.so")
+ foreach(LIB_DIR "lib64" "lib" "")
+ if(LIB_DIR)
+ set(LIB_CHECK_PATH "${NVIMGCODEC_DOWNLOAD_DIR}/${LIB_DIR}/${LIB_NAME}")
+ else()
+ set(LIB_CHECK_PATH "${NVIMGCODEC_DOWNLOAD_DIR}/${LIB_NAME}")
+ endif()
+ if(EXISTS "${LIB_CHECK_PATH}")
+ set(NVIMGCODEC_LIB_PATH "${LIB_CHECK_PATH}")
+ break()
+ endif()
+ endforeach()
+ if(NVIMGCODEC_LIB_PATH)
+ break()
+ endif()
+ endforeach()
+
+ if(EXISTS "${NVIMGCODEC_DOWNLOAD_DIR}/extensions")
+ set(NVIMGCODEC_EXTENSIONS_PATH "${NVIMGCODEC_DOWNLOAD_DIR}/extensions")
+ endif()
+
+ if(NVIMGCODEC_INCLUDE_PATH AND NVIMGCODEC_LIB_PATH)
+ set(NVIMGCODEC_FOUND TRUE)
+ message(STATUS "✓ nvImageCodec v${NVIMGCODEC_VERSION} downloaded and extracted")
+ else()
+ message(WARNING "Downloaded nvImageCodec but couldn't find library or headers")
+ message(WARNING " Download dir: ${NVIMGCODEC_DOWNLOAD_DIR}")
+ endif()
+ endif()
+
+ # =========================================================================
+ # Method 4: Try find_package (works in conda and system installations)
+ # =========================================================================
+ if(NOT NVIMGCODEC_FOUND)
+ find_package(nvimgcodec ${NVIMGCODEC_VERSION} QUIET CONFIG)
+
+ if(nvimgcodec_FOUND)
+ add_library(deps::nvimgcodec INTERFACE IMPORTED GLOBAL)
+ target_link_libraries(deps::nvimgcodec INTERFACE nvimgcodec::nvimgcodec)
+ message(STATUS "✓ nvImageCodec found via find_package (version: ${nvimgcodec_VERSION})")
+ set(NVIMGCODEC_FOUND TRUE)
+ endif()
+ endif()
+
+ # =========================================================================
+ # Method 5: Manual detection in conda/pip/system paths
+ # =========================================================================
+ if(NOT NVIMGCODEC_FOUND)
+ # Try conda environment
+ if(DEFINED ENV{CONDA_PREFIX})
+ # Try native conda package (libnvimgcodec-dev)
+ set(CONDA_NATIVE_ROOT "$ENV{CONDA_PREFIX}")
+ if(EXISTS "${CONDA_NATIVE_ROOT}/include/nvimgcodec.h")
+ set(NVIMGCODEC_INCLUDE_PATH "${CONDA_NATIVE_ROOT}/include")
+ if(EXISTS "${CONDA_NATIVE_ROOT}/lib/libnvimgcodec.so.0")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_NATIVE_ROOT}/lib/libnvimgcodec.so.0")
+ elseif(EXISTS "${CONDA_NATIVE_ROOT}/lib/libnvimgcodec.so")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_NATIVE_ROOT}/lib/libnvimgcodec.so")
+ endif()
+ endif()
+
+ # Fallback: try Python site-packages in conda environment
+ if(NOT NVIMGCODEC_LIB_PATH)
+ foreach(PY_VER "3.13" "3.12" "3.11" "3.10" "3.9")
+ set(CONDA_PYTHON_ROOT "$ENV{CONDA_PREFIX}/lib/python${PY_VER}/site-packages/nvidia/nvimgcodec")
+ if(EXISTS "${CONDA_PYTHON_ROOT}/include/nvimgcodec.h")
+ set(NVIMGCODEC_INCLUDE_PATH "${CONDA_PYTHON_ROOT}/include")
+ if(EXISTS "${CONDA_PYTHON_ROOT}/lib/libnvimgcodec.so.0")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_PYTHON_ROOT}/lib/libnvimgcodec.so.0")
+ elseif(EXISTS "${CONDA_PYTHON_ROOT}/lib/libnvimgcodec.so")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_PYTHON_ROOT}/lib/libnvimgcodec.so")
+ elseif(EXISTS "${CONDA_PYTHON_ROOT}/libnvimgcodec.so.0")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_PYTHON_ROOT}/libnvimgcodec.so.0")
+ elseif(EXISTS "${CONDA_PYTHON_ROOT}/libnvimgcodec.so")
+ set(NVIMGCODEC_LIB_PATH "${CONDA_PYTHON_ROOT}/libnvimgcodec.so")
+ endif()
+ if(NVIMGCODEC_LIB_PATH)
+ break()
+ endif()
+ endif()
+ endforeach()
+ endif()
+ endif()
+
+ # Try Python site-packages (outside conda or as additional fallback)
+ if(NOT NVIMGCODEC_LIB_PATH)
+ find_package(Python3 COMPONENTS Interpreter QUIET)
+ if(Python3_FOUND)
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} -c "import site; print(site.getusersitepackages())"
+ OUTPUT_VARIABLE PYTHON_USER_SITE_PACKAGES
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+ execute_process(
+ COMMAND ${Python3_EXECUTABLE} -c "import site; print(site.getsitepackages()[0])"
+ OUTPUT_VARIABLE PYTHON_SITE_PACKAGES
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+
+ foreach(SITE_PKG_DIR ${PYTHON_USER_SITE_PACKAGES} ${PYTHON_SITE_PACKAGES})
+ if(SITE_PKG_DIR)
+ set(NVIMGCODEC_PYTHON_ROOT "${SITE_PKG_DIR}/nvidia/nvimgcodec")
+ if(EXISTS "${NVIMGCODEC_PYTHON_ROOT}/include/nvimgcodec.h")
+ set(NVIMGCODEC_INCLUDE_PATH "${NVIMGCODEC_PYTHON_ROOT}/include")
+ foreach(LIB_SUBDIR "lib" "")
+ if(LIB_SUBDIR)
+ set(LIB_BASE "${NVIMGCODEC_PYTHON_ROOT}/${LIB_SUBDIR}")
+ else()
+ set(LIB_BASE "${NVIMGCODEC_PYTHON_ROOT}")
+ endif()
+ if(EXISTS "${LIB_BASE}/libnvimgcodec.so.0")
+ set(NVIMGCODEC_LIB_PATH "${LIB_BASE}/libnvimgcodec.so.0")
+ break()
+ elseif(EXISTS "${LIB_BASE}/libnvimgcodec.so")
+ set(NVIMGCODEC_LIB_PATH "${LIB_BASE}/libnvimgcodec.so")
+ break()
+ endif()
+ endforeach()
+ if(NVIMGCODEC_LIB_PATH)
+ break()
+ endif()
+ endif()
+ endif()
+ endforeach()
+ endif()
+ endif()
+
+ # System-wide installation fallback
+ if(NOT NVIMGCODEC_LIB_PATH)
+ foreach(SYS_LIB_DIR "/usr/lib/x86_64-linux-gnu" "/usr/lib/aarch64-linux-gnu" "/usr/lib64")
+ if(EXISTS "${SYS_LIB_DIR}/libnvimgcodec.so.0")
+ set(NVIMGCODEC_LIB_PATH "${SYS_LIB_DIR}/libnvimgcodec.so.0")
+ set(NVIMGCODEC_INCLUDE_PATH "/usr/include")
+ break()
+ endif()
+ endforeach()
+ endif()
+
+ if(NVIMGCODEC_LIB_PATH AND EXISTS "${NVIMGCODEC_LIB_PATH}")
+ set(NVIMGCODEC_FOUND TRUE)
+ endif()
+ endif()
+
+ # =========================================================================
+ # Create the target if nvImageCodec was found
+ # =========================================================================
+ if(NVIMGCODEC_FOUND AND NOT TARGET deps::nvimgcodec)
+ if(NVIMGCODEC_LIB_PATH AND EXISTS "${NVIMGCODEC_LIB_PATH}")
+ add_library(deps::nvimgcodec SHARED IMPORTED GLOBAL)
+ set_target_properties(deps::nvimgcodec PROPERTIES
+ IMPORTED_LOCATION "${NVIMGCODEC_LIB_PATH}"
+ INTERFACE_INCLUDE_DIRECTORIES "${NVIMGCODEC_INCLUDE_PATH}"
+ )
+ endif()
+ endif()
+
+ if(NVIMGCODEC_FOUND)
+ message(STATUS "✓ nvImageCodec v${NVIMGCODEC_VERSION} configured successfully:")
+ message(STATUS " Library: ${NVIMGCODEC_LIB_PATH}")
+ message(STATUS " Headers: ${NVIMGCODEC_INCLUDE_PATH}")
+ if(NVIMGCODEC_EXTENSIONS_PATH)
+ message(STATUS " Extensions: ${NVIMGCODEC_EXTENSIONS_PATH}")
+ endif()
+
+ # Cache the paths
+ set(NVIMGCODEC_INCLUDE_PATH ${NVIMGCODEC_INCLUDE_PATH} CACHE INTERNAL "" FORCE)
+ set(NVIMGCODEC_LIB_PATH ${NVIMGCODEC_LIB_PATH} CACHE INTERNAL "" FORCE)
+ set(NVIMGCODEC_EXTENSIONS_PATH ${NVIMGCODEC_EXTENSIONS_PATH} CACHE INTERNAL "" FORCE)
+ mark_as_advanced(NVIMGCODEC_INCLUDE_PATH NVIMGCODEC_LIB_PATH NVIMGCODEC_EXTENSIONS_PATH)
+
+ # Export extensions path as compile definition (useful at runtime)
+ if(NVIMGCODEC_EXTENSIONS_PATH)
+ add_compile_definitions(NVIMGCODEC_EXTENSIONS_DIR="${NVIMGCODEC_EXTENSIONS_PATH}")
+ endif()
+ else()
+ message(STATUS "")
+ message(STATUS "✗ nvImageCodec v${NVIMGCODEC_VERSION} not found - GPU acceleration disabled")
+ message(STATUS "")
+ message(STATUS "To install nvImageCodec v${NVIMGCODEC_VERSION} (internal release Build 11):")
+ message(STATUS "")
+ message(STATUS " Option 1 - Use downloaded packages (CUDA 12):")
+ message(STATUS " cmake -DNVIMGCODEC_ROOT=/home/cdinea/Downloads/cucim_pr3/nvimgcodec/12 ..")
+ message(STATUS "")
+ message(STATUS " Option 2 - Use downloaded packages (CUDA 13):")
+ message(STATUS " cmake -DNVIMGCODEC_ROOT=/home/cdinea/Downloads/cucim_pr3/nvimgcodec/13 ..")
+ message(STATUS "")
+ message(STATUS " Option 3 - Auto-detect CUDA version:")
+ message(STATUS " cmake -DNVIMGCODEC_DIR=/home/cdinea/Downloads/cucim_pr3/nvimgcodec ..")
+ message(STATUS "")
+ message(STATUS " Available platforms for v${NVIMGCODEC_VERSION}:")
+ message(STATUS " C Packages: linux-x86_64, linux-sbsa, linux-aarch64 (12.9), windows-x86_64")
+ message(STATUS " Python (CUDA 12): linux-x86_64, linux-sbsa, linux-aarch64, windows-x86_64")
+ message(STATUS " Python (CUDA 13): linux-x86_64, linux-sbsa, windows-x86_64")
+ message(STATUS "")
+ endif()
+
+ message(STATUS "=== End nvImageCodec Configuration ===")
+ message(STATUS "")
+endif()
diff --git a/cpp/plugins/cucim.kit.cuslide2/cmake/modules/CuCIMUtils.cmake b/cpp/plugins/cucim.kit.cuslide2/cmake/modules/CuCIMUtils.cmake
new file mode 100644
index 000000000..7762e8338
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/cmake/modules/CuCIMUtils.cmake
@@ -0,0 +1,52 @@
+#
+# cmake-format: off
+# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
+# SPDX-License-Identifier: Apache-2.0
+# cmake-format: on
+#
+
+# Store current BUILD_SHARED_LIBS setting in CUCIM_OLD_BUILD_SHARED_LIBS
+if(NOT COMMAND cucim_set_build_shared_libs)
+ macro(cucim_set_build_shared_libs new_value)
+ set(CUCIM_OLD_BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS}})
+ if (DEFINED CACHE{BUILD_SHARED_LIBS})
+ set(CUCIM_OLD_BUILD_SHARED_LIBS_CACHED TRUE)
+ else()
+ set(CUCIM_OLD_BUILD_SHARED_LIBS_CACHED FALSE)
+ endif()
+ set(BUILD_SHARED_LIBS ${new_value} CACHE BOOL "" FORCE)
+ endmacro()
+endif()
+
+# Restore BUILD_SHARED_LIBS setting from CUCIM_OLD_BUILD_SHARED_LIBS
+if(NOT COMMAND cucim_restore_build_shared_libs)
+ macro(cucim_restore_build_shared_libs)
+ if (CUCIM_OLD_BUILD_SHARED_LIBS_CACHED)
+ set(BUILD_SHARED_LIBS ${CUCIM_OLD_BUILD_SHARED_LIBS} CACHE BOOL "" FORCE)
+ else()
+ unset(BUILD_SHARED_LIBS CACHE)
+ set(BUILD_SHARED_LIBS ${CUCIM_OLD_BUILD_SHARED_LIBS})
+ endif()
+ endmacro()
+endif()
+
+# Define CMAKE_CUDA_ARCHITECTURES for the given architecture values
+#
+# Params:
+# arch_list - architecture value list (e.g., '60;70;75;80;86')
+if(NOT COMMAND cucim_define_cuda_architectures)
+ function(cucim_define_cuda_architectures arch_list)
+ set(arch_string "")
+ # Create SASS for all architectures in the list
+ foreach(arch IN LISTS arch_list)
+ set(arch_string "${arch_string}" "${arch}-real")
+ endforeach(arch)
+
+ # Create PTX for the latest architecture for forward-compatibility.
+ list(GET arch_list -1 latest_arch)
+ foreach(arch IN LISTS arch_list)
+ set(arch_string "${arch_string}" "${latest_arch}-virtual")
+ endforeach(arch)
+ set(CMAKE_CUDA_ARCHITECTURES ${arch_string} PARENT_SCOPE)
+ endfunction()
+endif()
diff --git a/cpp/plugins/cucim.kit.cuslide2/cmake/modules/SuperBuildUtils.cmake b/cpp/plugins/cucim.kit.cuslide2/cmake/modules/SuperBuildUtils.cmake
new file mode 100644
index 000000000..2edb82b6f
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/cmake/modules/SuperBuildUtils.cmake
@@ -0,0 +1,24 @@
+#
+# cmake-format: off
+# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
+# SPDX-License-Identifier: Apache-2.0
+# cmake-format: on
+#
+
+include(FetchContent)
+
+# Local deps directory (for cuslide2-specific dependencies like nvimgcodec)
+set(CMAKE_LOCAL_DEPS_DIR "${CMAKE_CURRENT_LIST_DIR}/../deps")
+# Shared deps directory from cuslide plugin (for common dependencies)
+set(CMAKE_SHARED_DEPS_DIR "${CMAKE_CURRENT_LIST_DIR}/../../../cucim.kit.cuslide/cmake/deps")
+
+function(superbuild_depend module_name)
+ # Check local deps first (cuslide2-specific), then shared deps
+ if(EXISTS "${CMAKE_LOCAL_DEPS_DIR}/${module_name}.cmake")
+ include("${CMAKE_LOCAL_DEPS_DIR}/${module_name}.cmake")
+ elseif(EXISTS "${CMAKE_SHARED_DEPS_DIR}/${module_name}.cmake")
+ include("${CMAKE_SHARED_DEPS_DIR}/${module_name}.cmake")
+ else()
+ message(FATAL_ERROR "Dependency ${module_name}.cmake not found in local or shared deps")
+ endif()
+endfunction()
diff --git a/cpp/plugins/cucim.kit.cuslide2/cuslide.map b/cpp/plugins/cucim.kit.cuslide2/cuslide.map
new file mode 100644
index 000000000..6ebbbdfbb
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/cuslide.map
@@ -0,0 +1,4 @@
+CUSLIDE_0.1 {
+ local:
+ *;
+};
diff --git a/cpp/plugins/cucim.kit.cuslide2/src/cuslide/cuslide.cpp b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/cuslide.cpp
new file mode 100644
index 000000000..60b0e3b4f
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/cuslide.cpp
@@ -0,0 +1,382 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#define CUCIM_EXPORTS
+
+#include "cuslide.h"
+
+#include "cucim/core/framework.h"
+#include "cucim/core/plugin_util.h"
+#include "cucim/io/format/image_format.h"
+#include "tiff/tiff.h"
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+using json = nlohmann::json;
+
+const struct cucim::PluginImplDesc kPluginImpl = {
+ "cucim.kit.cuslide2", // name
+ { 0, 1, 0 }, // version
+ "dev", // build
+ "clara team", // author
+ "cuslide2", // description
+ "cuslide2 plugin with nvImageCodec support", // long_description
+ "Apache-2.0", // license
+ "https://github.com/rapidsai/cucim", // url
+ "linux", // platforms,
+ cucim::PluginHotReload::kDisabled, // hot_reload
+};
+
+// Using CARB_PLUGIN_IMPL_MINIMAL instead of CARB_PLUGIN_IMPL
+// This minimal macro doesn't define global variables for logging, profiler, crash reporting,
+// and also doesn't call for the client registration for those systems
+CUCIM_PLUGIN_IMPL_MINIMAL(kPluginImpl, cucim::io::format::IImageFormat)
+CUCIM_PLUGIN_IMPL_NO_DEPS()
+
+
+static void set_enabled(bool val)
+{
+ (void)val;
+}
+
+static bool is_enabled()
+{
+ return true;
+}
+
+static const char* get_format_name()
+{
+ return "nvImageCodec TIFF";
+}
+
+static bool CUCIM_ABI checker_is_valid(const char* file_name, const char* buf, size_t size)
+{
+ (void)buf;
+ (void)size;
+ auto file = std::filesystem::path(file_name);
+ auto extension = file.extension().string();
+ if (extension.compare(".tif") == 0 || extension.compare(".tiff") == 0 || extension.compare(".svs") == 0)
+ {
+ return true;
+ }
+ return false;
+}
+
+static CuCIMFileHandle_share CUCIM_ABI parser_open(const char* file_path)
+{
+ auto tif = new cuslide::tiff::TIFF(file_path, O_RDONLY);
+ tif->construct_ifds();
+ // Move the ownership of the file handle object to the caller (CuImage).
+ // CRITICAL: Use std::move to transfer ownership and avoid double-free
+ auto handle_t = std::move(tif->file_handle());
+ tif->file_handle() = nullptr; // Now safe - original is already moved
+ CuCIMFileHandle_share handle = new std::shared_ptr(std::move(handle_t));
+ return handle;
+}
+
+static bool CUCIM_ABI parser_parse(CuCIMFileHandle_ptr handle_ptr, cucim::io::format::ImageMetadataDesc* out_metadata_desc)
+{
+ CuCIMFileHandle* handle = reinterpret_cast(handle_ptr);
+ if (!out_metadata_desc || !out_metadata_desc->handle)
+ {
+ throw std::runtime_error("out_metadata_desc shouldn't be nullptr!");
+ }
+ cucim::io::format::ImageMetadata& out_metadata =
+ *reinterpret_cast(out_metadata_desc->handle);
+
+ auto tif = static_cast(handle->client_data);
+
+ size_t ifd_count = tif->ifd_count();
+ size_t level_count = tif->level_count();
+
+ // Detect if this is an Aperio SVS file
+ // Try ImageDescription first (works with nvImageCodec 0.7.0+)
+ bool is_aperio_svs = (tif->ifd(0)->image_description().rfind("Aperio", 0) == 0);
+
+ // Detect if this is a Philips TIFF file
+ // Philips TIFF also has multiple SubfileType=0 (by design)
+ bool is_philips_tiff = (tif->tiff_type() == cuslide::tiff::TiffType::Philips);
+
+ // Fallback detection for nvImageCodec 0.6.0: check for multiple resolution levels
+ // Aperio SVS files typically have 3-6 IFDs with multiple resolution levels
+ // If we have multiple IFDs and they look like a pyramid, treat as Aperio/SVS
+ if (!is_aperio_svs && ifd_count >= 3 && level_count >= 3)
+ {
+ // Check if IFDs form a pyramid structure (decreasing sizes)
+ bool is_pyramid = true;
+ for (size_t i = 1; i < std::min(size_t(3), level_count); ++i)
+ {
+ auto ifd_curr = tif->level_ifd(i);
+ auto ifd_prev = tif->level_ifd(i-1);
+ if (ifd_curr->width() >= ifd_prev->width())
+ {
+ is_pyramid = false;
+ break;
+ }
+ }
+
+ if (is_pyramid)
+ {
+ #ifdef DEBUG
+ fmt::print("ℹ️ Detected pyramid structure → treating as Aperio SVS/multi-resolution TIFF\n");
+ #endif // DEBUG
+ is_aperio_svs = true;
+ }
+ }
+
+ // If not Aperio SVS, Philips TIFF, or multi-resolution pyramid, apply strict validation
+ if (!is_aperio_svs && !is_philips_tiff)
+ {
+ std::vector main_ifd_list;
+ for (size_t i = 0; i < ifd_count; i++)
+ {
+ const std::shared_ptr& ifd = tif->ifd(i);
+ uint64_t subfile_type = ifd->subfile_type();
+ if (subfile_type == 0)
+ {
+ main_ifd_list.push_back(i);
+ }
+ }
+
+ // Assume that the image has only one main (high resolution) image.
+ if (main_ifd_list.size() != 1)
+ {
+ throw std::runtime_error(
+ fmt::format("This format has more than one image with Subfile Type 0 so cannot be loaded!"));
+ }
+ }
+
+ //
+ // Metadata Setup
+ //
+
+ // Note: int-> uint16_t due to type differences between ImageMetadataDesc.ndim and DLTensor.ndim
+ const uint16_t ndim = 3;
+ auto& resource = out_metadata.get_resource();
+
+ std::string_view dims{ "YXC" };
+
+ const auto& level0_ifd = tif->level_ifd(0);
+ std::pmr::vector shape(
+ { level0_ifd->height(), level0_ifd->width(), level0_ifd->samples_per_pixel() }, &resource);
+
+ DLDataType dtype{ kDLUInt, 8, 1 };
+
+ // TODO: Fill correct values for cucim::io::format::ImageMetadataDesc
+ uint8_t n_ch = level0_ifd->samples_per_pixel();
+ if (n_ch != 3)
+ {
+ // Image loaded by a slow-path(libtiff) always will have 4 channel
+ // (by TIFFRGBAImageGet() method in libtiff)
+ n_ch = 4;
+ shape[2] = 4;
+ }
+ std::pmr::vector channel_names(&resource);
+ channel_names.reserve(n_ch);
+ if (n_ch == 3)
+ {
+ channel_names.emplace_back(std::string_view{ "R" });
+ channel_names.emplace_back(std::string_view{ "G" });
+ channel_names.emplace_back(std::string_view{ "B" });
+ }
+ else
+ {
+ channel_names.emplace_back(std::string_view{ "R" });
+ channel_names.emplace_back(std::string_view{ "G" });
+ channel_names.emplace_back(std::string_view{ "B" });
+ channel_names.emplace_back(std::string_view{ "A" });
+ }
+
+ // Spacing units
+ std::pmr::vector spacing_units(&resource);
+ spacing_units.reserve(ndim);
+
+ std::pmr::vector spacing(&resource);
+ spacing.reserve(ndim);
+ const auto resolution_unit = level0_ifd->resolution_unit();
+ const auto x_resolution = level0_ifd->x_resolution();
+ const auto y_resolution = level0_ifd->y_resolution();
+
+ switch (resolution_unit)
+ {
+ case 1: // no absolute unit of measurement
+ spacing.emplace_back(y_resolution);
+ spacing.emplace_back(x_resolution);
+ spacing.emplace_back(1.0f);
+
+ spacing_units.emplace_back(std::string_view{ "" });
+ spacing_units.emplace_back(std::string_view{ "" });
+ break;
+ case 2: // inch
+ spacing.emplace_back(y_resolution != 0 ? 25400 / y_resolution : 1.0f);
+ spacing.emplace_back(x_resolution != 0 ? 25400 / x_resolution : 1.0f);
+ spacing.emplace_back(1.0f);
+
+ spacing_units.emplace_back(std::string_view{ "micrometer" });
+ spacing_units.emplace_back(std::string_view{ "micrometer" });
+ break;
+ case 3: // centimeter
+ spacing.emplace_back(y_resolution != 0 ? 10000 / y_resolution : 1.0f);
+ spacing.emplace_back(x_resolution != 0 ? 10000 / x_resolution : 1.0f);
+ spacing.emplace_back(1.0f);
+
+ spacing_units.emplace_back(std::string_view{ "micrometer" });
+ spacing_units.emplace_back(std::string_view{ "micrometer" });
+ break;
+ default:
+ spacing.insert(spacing.end(), ndim, 1.0f);
+ }
+
+ spacing_units.emplace_back(std::string_view{ "color" });
+
+ std::pmr::vector origin({ 0.0, 0.0, 0.0 }, &resource);
+ // Direction cosines (size is always 3x3)
+ // clang-format off
+ std::pmr::vector direction({ 1.0, 0.0, 0.0,
+ 0.0, 1.0, 0.0,
+ 0.0, 0.0, 1.0}, &resource);
+ // clang-format on
+
+ // The coordinate frame in which the direction cosines are measured (either 'LPS'(ITK/DICOM) or 'RAS'(NIfTI/3D
+ // Slicer))
+ std::string_view coord_sys{ "LPS" };
+
+ const uint16_t level_ndim = 2;
+ std::pmr::vector level_dimensions(&resource);
+ level_dimensions.reserve(level_count * 2);
+ for (size_t i = 0; i < level_count; ++i)
+ {
+ const auto& level_ifd = tif->level_ifd(i);
+ level_dimensions.emplace_back(level_ifd->width());
+ level_dimensions.emplace_back(level_ifd->height());
+ }
+
+ std::pmr::vector level_downsamples(&resource);
+ float orig_width = static_cast(shape[1]);
+ float orig_height = static_cast(shape[0]);
+ for (size_t i = 0; i < level_count; ++i)
+ {
+ const auto& level_ifd = tif->level_ifd(i);
+ level_downsamples.emplace_back(((orig_width / level_ifd->width()) + (orig_height / level_ifd->height())) / 2);
+ }
+
+ std::pmr::vector level_tile_sizes(&resource);
+ level_tile_sizes.reserve(level_count * 2);
+ for (size_t i = 0; i < level_count; ++i)
+ {
+ const auto& level_ifd = tif->level_ifd(i);
+ level_tile_sizes.emplace_back(level_ifd->tile_width());
+ level_tile_sizes.emplace_back(level_ifd->tile_height());
+ }
+
+ const size_t associated_image_count = tif->associated_image_count();
+ std::pmr::vector associated_image_names(&resource);
+ for (const auto& associated_image : tif->associated_images())
+ {
+ associated_image_names.emplace_back(std::string_view{ associated_image.first.c_str() });
+ }
+
+ auto& image_description = level0_ifd->image_description();
+ std::string_view raw_data{ image_description.empty() ? "" : image_description.c_str() };
+
+ // Dynamically allocate memory for json_data (need to be freed manually);
+ const std::string& json_str = tif->metadata();
+ char* json_data_ptr = static_cast(cucim_malloc(json_str.size() + 1));
+ memcpy(json_data_ptr, json_str.data(), json_str.size() + 1);
+ std::string_view json_data{ json_data_ptr, json_str.size() };
+
+ out_metadata.ndim(ndim);
+ out_metadata.dims(std::move(dims));
+ out_metadata.shape(std::move(shape));
+ out_metadata.dtype(dtype);
+ out_metadata.channel_names(std::move(channel_names));
+ out_metadata.spacing(std::move(spacing));
+ out_metadata.spacing_units(std::move(spacing_units));
+ out_metadata.origin(std::move(origin));
+ out_metadata.direction(std::move(direction));
+ out_metadata.coord_sys(std::move(coord_sys));
+ out_metadata.level_count(level_count);
+ out_metadata.level_ndim(level_ndim);
+ out_metadata.level_dimensions(std::move(level_dimensions));
+ out_metadata.level_downsamples(std::move(level_downsamples));
+ out_metadata.level_tile_sizes(std::move(level_tile_sizes));
+ out_metadata.image_count(associated_image_count);
+ out_metadata.image_names(std::move(associated_image_names));
+ out_metadata.raw_data(raw_data);
+ out_metadata.json_data(json_data);
+
+ return true;
+}
+
+static bool CUCIM_ABI parser_close(CuCIMFileHandle_ptr handle_ptr)
+{
+ CuCIMFileHandle* handle = reinterpret_cast(handle_ptr);
+
+ auto tif = static_cast(handle->client_data);
+ delete tif;
+ handle->client_data = nullptr;
+ return true;
+}
+
+static bool CUCIM_ABI reader_read(const CuCIMFileHandle_ptr handle_ptr,
+ const cucim::io::format::ImageMetadataDesc* metadata,
+ const cucim::io::format::ImageReaderRegionRequestDesc* request,
+ cucim::io::format::ImageDataDesc* out_image_data,
+ cucim::io::format::ImageMetadataDesc* out_metadata = nullptr)
+{
+ CuCIMFileHandle* handle = reinterpret_cast(handle_ptr);
+ auto tif = static_cast(handle->client_data);
+ bool result = tif->read(metadata, request, out_image_data, out_metadata);
+
+ return result;
+}
+
+static bool CUCIM_ABI writer_write(const CuCIMFileHandle_ptr handle_ptr,
+ const cucim::io::format::ImageMetadataDesc* metadata,
+ const cucim::io::format::ImageDataDesc* image_data)
+{
+ CuCIMFileHandle* handle = reinterpret_cast(handle_ptr);
+ (void)handle;
+ (void)metadata;
+ (void)image_data;
+
+ return true;
+}
+
+void fill_interface(cucim::io::format::IImageFormat& iface)
+{
+ static cucim::io::format::ImageCheckerDesc image_checker = { 0, 0, checker_is_valid };
+ static cucim::io::format::ImageParserDesc image_parser = { parser_open, parser_parse, parser_close };
+
+ static cucim::io::format::ImageReaderDesc image_reader = { reader_read };
+ static cucim::io::format::ImageWriterDesc image_writer = { writer_write };
+
+ // clang-format off
+ static cucim::io::format::ImageFormatDesc image_format_desc = {
+ set_enabled,
+ is_enabled,
+ get_format_name,
+ image_checker,
+ image_parser,
+ image_reader,
+ image_writer
+ };
+ // clang-format on
+
+ // clang-format off
+ iface =
+ {
+ &image_format_desc,
+ 1
+ };
+ // clang-format on
+}
diff --git a/cpp/plugins/cucim.kit.cuslide2/src/cuslide/cuslide.h b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/cuslide.h
new file mode 100644
index 000000000..e8b936e8b
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/cuslide.h
@@ -0,0 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2020, NVIDIA CORPORATION.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef CUSLIDE_CUSLIDE_H
+#define CUSLIDE_CUSLIDE_H
+#endif // CUSLIDE_CUSLIDE_H
diff --git a/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_decoder.cpp b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_decoder.cpp
new file mode 100644
index 000000000..7c4de7d81
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_decoder.cpp
@@ -0,0 +1,445 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "nvimgcodec_decoder.h"
+#include "nvimgcodec_tiff_parser.h"
+
+#ifdef CUCIM_HAS_NVIMGCODEC
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef CUCIM_HAS_NVIMGCODEC
+#include
+#endif
+
+namespace cuslide2::nvimgcodec
+{
+
+#ifdef CUCIM_HAS_NVIMGCODEC
+
+// ============================================================================
+// RAII Helpers for nvImageCodec Resources
+// ============================================================================
+
+// RAII wrapper for nvimgcodecCodeStream_t (including sub-code streams)
+// Per nvImageCodec team: each code stream (parent or sub) has its own state
+// and MUST be explicitly destroyed. Sub-streams are NOT automatically cleaned
+// up when the parent is destroyed.
+struct CodeStreamDeleter
+{
+ void operator()(nvimgcodecCodeStream_t stream) const
+ {
+ if (stream)
+ {
+ nvimgcodecCodeStreamDestroy(stream);
+ }
+ }
+};
+using UniqueCodeStream = std::unique_ptr, CodeStreamDeleter>;
+
+// RAII wrapper for nvimgcodecImage_t
+struct ImageDeleter
+{
+ void operator()(nvimgcodecImage_t image) const
+ {
+ if (image) nvimgcodecImageDestroy(image);
+ }
+};
+using UniqueImage = std::unique_ptr, ImageDeleter>;
+
+// RAII wrapper for nvimgcodecFuture_t
+struct FutureDeleter
+{
+ void operator()(nvimgcodecFuture_t future) const
+ {
+ if (future) nvimgcodecFutureDestroy(future);
+ }
+};
+using UniqueFuture = std::unique_ptr, FutureDeleter>;
+
+// RAII wrapper for decode buffer (handles both CPU and GPU memory)
+class DecodeBuffer
+{
+public:
+ DecodeBuffer() = default;
+ ~DecodeBuffer() { reset(); }
+
+ // Non-copyable
+ DecodeBuffer(const DecodeBuffer&) = delete;
+ DecodeBuffer& operator=(const DecodeBuffer&) = delete;
+
+ // Movable
+ DecodeBuffer(DecodeBuffer&& other) noexcept
+ : buffer_(other.buffer_), is_device_(other.is_device_)
+ {
+ other.buffer_ = nullptr;
+ }
+
+ DecodeBuffer& operator=(DecodeBuffer&& other) noexcept
+ {
+ if (this != &other)
+ {
+ reset();
+ buffer_ = other.buffer_;
+ is_device_ = other.is_device_;
+ other.buffer_ = nullptr;
+ }
+ return *this;
+ }
+
+ bool allocate(size_t size, bool device_memory)
+ {
+ reset();
+ is_device_ = device_memory;
+ if (device_memory)
+ {
+ cudaError_t status = cudaMalloc(&buffer_, size);
+ return status == cudaSuccess;
+ }
+ else
+ {
+ // Use standard malloc for CPU memory
+ // NOTE: Must use malloc() (not cudaMallocHost()) because cuCIM's cleanup
+ // code uses free(). Using cudaMallocHost() would require cudaFreeHost(),
+ // causing "free(): invalid pointer" crash when cuCIM calls free().
+ buffer_ = malloc(size);
+ return buffer_ != nullptr;
+ }
+ }
+
+ void reset()
+ {
+ if (buffer_)
+ {
+ if (is_device_)
+ cudaFree(buffer_);
+ else
+ free(buffer_); // Standard free (matches malloc)
+ buffer_ = nullptr;
+ }
+ }
+
+ void* get() const { return buffer_; }
+ bool is_device() const { return is_device_; }
+
+ // Release ownership (for passing to caller)
+ void* release()
+ {
+ void* tmp = buffer_;
+ buffer_ = nullptr;
+ return tmp;
+ }
+
+private:
+ void* buffer_ = nullptr;
+ bool is_device_ = false;
+};
+
+// ============================================================================
+// IFD-Level Region Decoding (Primary Decode Function)
+// ============================================================================
+
+bool decode_ifd_region_nvimgcodec(const IfdInfo& ifd_info,
+ nvimgcodecCodeStream_t main_code_stream,
+ uint32_t x, uint32_t y,
+ uint32_t width, uint32_t height,
+ uint8_t** output_buffer,
+ const cucim::io::Device& out_device)
+{
+ if (!main_code_stream)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Invalid main_code_stream\n");
+ #endif
+ return false;
+ }
+
+ #ifdef DEBUG
+ fmt::print("🚀 Decoding IFD[{}] region: [{},{}] {}x{}, codec: {}\n",
+ ifd_info.index, x, y, width, height, ifd_info.codec);
+ #endif
+
+ try
+ {
+ // CRITICAL: Must use the same manager that created main_code_stream!
+ // Using a decoder from a different nvImageCodec instance causes segfaults.
+ auto& manager = NvImageCodecTiffParserManager::instance();
+ if (!manager.is_available())
+ {
+ #ifdef DEBUG
+ fmt::print("❌ nvImageCodec TIFF parser manager not initialized\n");
+ #endif
+ return false;
+ }
+
+ // Select decoder based on target device
+ // CPU-only backend can handle in-bounds ROI decoding for TIFF files
+ std::string device_str = std::string(out_device);
+ bool target_is_cpu = (device_str.find("cpu") != std::string::npos);
+
+ // Check if ROI is out of bounds (extends beyond image boundaries)
+ // CPU decoder doesn't support out-of-bounds ROI decoding, must use hybrid decoder
+ bool roi_out_of_bounds = (x + width > ifd_info.width) || (y + height > ifd_info.height);
+ if (target_is_cpu && roi_out_of_bounds)
+ {
+ target_is_cpu = false; // Force hybrid decoder for out-of-bounds ROI
+ #ifdef DEBUG
+ fmt::print(" ⚠️ ROI out of bounds (region ends at [{},{}] but image is {}x{}), using hybrid decoder\n",
+ x + width, y + height, ifd_info.width, ifd_info.height);
+ #endif
+ }
+
+ nvimgcodecDecoder_t decoder;
+ if (target_is_cpu && manager.has_cpu_decoder())
+ {
+ decoder = manager.get_cpu_decoder();
+ #ifdef DEBUG
+ fmt::print(" 💡 Using CPU-only decoder for ROI\n");
+ #endif
+ }
+ else
+ {
+ decoder = manager.get_decoder();
+ #ifdef DEBUG
+ fmt::print(" 💡 Using hybrid decoder for ROI\n");
+ #endif
+ }
+
+ // Step 1: Create view with ROI for this IFD
+ nvimgcodecRegion_t region{};
+ region.struct_type = NVIMGCODEC_STRUCTURE_TYPE_REGION;
+ region.struct_size = sizeof(nvimgcodecRegion_t);
+ region.struct_next = nullptr;
+ region.ndim = 2;
+ region.start[0] = y; // row
+ region.start[1] = x; // col
+ region.end[0] = y + height;
+ region.end[1] = x + width;
+
+ nvimgcodecCodeStreamView_t view{};
+ view.struct_type = NVIMGCODEC_STRUCTURE_TYPE_CODE_STREAM_VIEW;
+ view.struct_size = sizeof(nvimgcodecCodeStreamView_t);
+ view.struct_next = nullptr;
+ view.image_idx = ifd_info.index;
+ view.region = region;
+
+ // Get sub-code stream for this ROI (RAII managed)
+ nvimgcodecCodeStream_t roi_stream_raw = nullptr;
+ nvimgcodecStatus_t status = nvimgcodecCodeStreamGetSubCodeStream(
+ main_code_stream,
+ &roi_stream_raw,
+ &view
+ );
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Failed to create ROI sub-stream (status: {})\n",
+ static_cast(status));
+ #endif
+ return false;
+ }
+ // RAII wrapper - sub-stream will be properly destroyed when scope exits
+ UniqueCodeStream roi_stream(roi_stream_raw);
+
+ // Step 2: Determine buffer kind based on target device and decoder
+ int device_count = 0;
+ cudaError_t cuda_err = cudaGetDeviceCount(&device_count);
+ bool gpu_available = (cuda_err == cudaSuccess && device_count > 0);
+
+ nvimgcodecImageBufferKind_t buffer_kind;
+ if (target_is_cpu)
+ {
+ // CPU target: use host buffer directly
+ buffer_kind = NVIMGCODEC_IMAGE_BUFFER_KIND_STRIDED_HOST;
+ #ifdef DEBUG
+ fmt::print(" ℹ️ Using CPU buffer for ROI decoding\n");
+ #endif
+ }
+ else if (gpu_available)
+ {
+ // GPU target with GPU available: use device buffer
+ buffer_kind = NVIMGCODEC_IMAGE_BUFFER_KIND_STRIDED_DEVICE;
+ }
+ else
+ {
+ // GPU target but no GPU available: fall back to host buffer
+ buffer_kind = NVIMGCODEC_IMAGE_BUFFER_KIND_STRIDED_HOST;
+ #ifdef DEBUG
+ fmt::print(" ⚠️ No GPU available, using CPU buffer\n");
+ #endif
+ }
+
+ // Step 3: Prepare output image info for the region
+ nvimgcodecImageInfo_t output_image_info{};
+ output_image_info.struct_type = NVIMGCODEC_STRUCTURE_TYPE_IMAGE_INFO;
+ output_image_info.struct_size = sizeof(nvimgcodecImageInfo_t);
+ output_image_info.struct_next = nullptr;
+
+ // Use interleaved RGB format
+ output_image_info.sample_format = NVIMGCODEC_SAMPLEFORMAT_I_RGB;
+ output_image_info.color_spec = NVIMGCODEC_COLORSPEC_SRGB;
+ output_image_info.chroma_subsampling = NVIMGCODEC_SAMPLING_NONE;
+ output_image_info.num_planes = 1;
+ output_image_info.buffer_kind = buffer_kind;
+
+ // Calculate buffer requirements for the region
+ uint32_t num_channels = 3; // RGB
+ size_t row_stride = width * num_channels;
+ size_t buffer_size = row_stride * height;
+
+ output_image_info.plane_info[0].height = height;
+ output_image_info.plane_info[0].width = width;
+ output_image_info.plane_info[0].num_channels = num_channels;
+ output_image_info.plane_info[0].row_stride = row_stride;
+ output_image_info.plane_info[0].sample_type = NVIMGCODEC_SAMPLE_DATA_TYPE_UINT8;
+ // Note: buffer_size removed in nvImageCodec v0.7.0 - size is inferred from plane_info
+ output_image_info.cuda_stream = 0;
+
+ #ifdef DEBUG
+ fmt::print(" Buffer: {}x{} RGB, stride={}, size={} bytes\n",
+ width, height, row_stride, buffer_size);
+ #endif
+
+ // Step 4: Allocate output buffer (RAII managed)
+ bool use_device_memory = (buffer_kind == NVIMGCODEC_IMAGE_BUFFER_KIND_STRIDED_DEVICE);
+ DecodeBuffer decode_buffer;
+ if (!decode_buffer.allocate(buffer_size, use_device_memory))
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Failed to allocate {} memory\n", use_device_memory ? "GPU" : "host");
+ #endif
+ return false;
+ }
+ #ifdef DEBUG
+ fmt::print(" Allocated {} buffer\n", use_device_memory ? "GPU" : "CPU");
+ #endif
+
+ output_image_info.buffer = decode_buffer.get();
+
+ // Step 5: Create image object (RAII managed)
+ nvimgcodecImage_t image_raw = nullptr;
+ status = nvimgcodecImageCreate(
+ manager.get_instance(),
+ &image_raw,
+ &output_image_info
+ );
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Failed to create image object (status: {})\n",
+ static_cast(status));
+ #endif
+ return false; // RAII handles cleanup
+ }
+ UniqueImage image(image_raw);
+
+ // Step 6: Prepare decode parameters
+ nvimgcodecDecodeParams_t decode_params{};
+ decode_params.struct_type = NVIMGCODEC_STRUCTURE_TYPE_DECODE_PARAMS;
+ decode_params.struct_size = sizeof(nvimgcodecDecodeParams_t);
+ decode_params.struct_next = nullptr;
+ decode_params.apply_exif_orientation = 1;
+
+ // Step 7: Schedule decoding (RAII managed)
+ nvimgcodecCodeStream_t roi_stream_ptr = roi_stream.get();
+ nvimgcodecImage_t image_ptr = image.get();
+ nvimgcodecFuture_t decode_future_raw = nullptr;
+ status = nvimgcodecDecoderDecode(decoder,
+ &roi_stream_ptr,
+ &image_ptr,
+ 1,
+ &decode_params,
+ &decode_future_raw);
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Failed to schedule decoding (status: {})\n",
+ static_cast(status));
+ #endif
+ return false; // RAII handles cleanup
+ }
+ UniqueFuture decode_future(decode_future_raw);
+
+ // Step 8: Wait for completion
+ nvimgcodecProcessingStatus_t decode_status = NVIMGCODEC_PROCESSING_STATUS_UNKNOWN;
+ size_t status_size = 1;
+ status = nvimgcodecFutureGetProcessingStatus(decode_future.get(), &decode_status, &status_size);
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Failed to get processing status (status: {})\n", static_cast(status));
+ #endif
+ return false; // RAII handles cleanup
+ }
+
+ if (use_device_memory)
+ {
+ cudaDeviceSynchronize();
+ }
+
+ // Step 9: Check decode status
+ if (decode_status != NVIMGCODEC_PROCESSING_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Decoding failed (status: {})\n", static_cast(decode_status));
+ #endif
+ return false; // RAII handles cleanup
+ }
+
+ #ifdef DEBUG
+ fmt::print("✅ Successfully decoded IFD[{}] region\n", ifd_info.index);
+ #endif
+
+ // Success: release buffer ownership to caller (RAII cleanup skipped for buffer)
+ *output_buffer = reinterpret_cast(decode_buffer.release());
+ #ifdef DEBUG
+ fmt::print("✅ nvImageCodec ROI decode successful: {}x{} at ({}, {})\n",
+ width, height, x, y);
+ #endif
+ return true; // roi_stream, image, decode_future all cleaned up by RAII
+ }
+ catch (const std::exception& e)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Exception in ROI decoding: {}\n", e.what());
+ #endif
+ return false;
+ }
+}
+
+#else // !CUCIM_HAS_NVIMGCODEC
+
+// Fallback stub when nvImageCodec is not available
+// cuslide2 plugin requires nvImageCodec, so this should never be called
+// Forward declaration for types
+struct IfdInfo;
+typedef void* nvimgcodecCodeStream_t;
+
+bool decode_ifd_region_nvimgcodec(const IfdInfo&,
+ nvimgcodecCodeStream_t,
+ uint32_t, uint32_t,
+ uint32_t, uint32_t,
+ uint8_t**,
+ const cucim::io::Device&)
+{
+ throw std::runtime_error("cuslide2 plugin requires nvImageCodec to be enabled at compile time");
+}
+
+#endif // CUCIM_HAS_NVIMGCODEC
+
+} // namespace cuslide2::nvimgcodec
diff --git a/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_decoder.h b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_decoder.h
new file mode 100644
index 000000000..8a7771c8e
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_decoder.h
@@ -0,0 +1,49 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef CUSLIDE2_NVIMGCODEC_DECODER_H
+#define CUSLIDE2_NVIMGCODEC_DECODER_H
+
+#ifdef CUCIM_HAS_NVIMGCODEC
+#include
+#endif
+
+#include
+#include
+
+namespace cuslide2::nvimgcodec
+{
+
+#ifdef CUCIM_HAS_NVIMGCODEC
+// Forward declaration
+struct IfdInfo;
+
+/**
+ * Decode a region of interest (ROI) from an IFD using nvImageCodec
+ *
+ * Uses nvImageCodec's CodeStreamView with region specification for
+ * memory-efficient decoding of specific image areas.
+ *
+ * @param ifd_info Parsed IFD information with sub_code_stream
+ * @param main_code_stream Main TIFF code stream (for creating ROI views)
+ * @param x Starting x coordinate (column)
+ * @param y Starting y coordinate (row)
+ * @param width Width of region in pixels
+ * @param height Height of region in pixels
+ * @param output_buffer Pointer to receive allocated buffer (caller must free)
+ * @param out_device Output device ("cpu" or "cuda")
+ * @return true if successful, false otherwise
+ */
+bool decode_ifd_region_nvimgcodec(const IfdInfo& ifd_info,
+ nvimgcodecCodeStream_t main_code_stream,
+ uint32_t x, uint32_t y,
+ uint32_t width, uint32_t height,
+ uint8_t** output_buffer,
+ const cucim::io::Device& out_device);
+#endif // CUCIM_HAS_NVIMGCODEC
+
+} // namespace cuslide2::nvimgcodec
+
+#endif // CUSLIDE2_NVIMGCODEC_DECODER_H
diff --git a/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_tiff_parser.cpp b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_tiff_parser.cpp
new file mode 100644
index 000000000..6c67c24b1
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_tiff_parser.cpp
@@ -0,0 +1,1245 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "nvimgcodec_tiff_parser.h"
+
+#include // for std::transform
+#include // for strlen
+
+#ifdef CUCIM_HAS_NVIMGCODEC
+#include
+#include
+#endif
+
+#include
+#include
+#include
+#include
+#include
+
+namespace cuslide2::nvimgcodec
+{
+
+#ifdef CUCIM_HAS_NVIMGCODEC
+
+// Helper function to convert TiffTagValue variant to string representation
+static std::string tiff_tag_value_to_string(const TiffTagValue& value)
+{
+ return std::visit([](const auto& v) -> std::string {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v)
+ {
+ return ""; // Empty/unset
+ }
+ else if constexpr (std::is_same_v)
+ {
+ return v;
+ }
+ else if constexpr (std::is_same_v>)
+ {
+ return fmt::format("[{} bytes]", v.size());
+ }
+ else if constexpr (std::is_same_v>)
+ {
+ std::string result;
+ for (size_t i = 0; i < v.size() && i < 10; ++i)
+ {
+ if (i > 0) result += ",";
+ result += std::to_string(v[i]);
+ }
+ if (v.size() > 10) result += ",...";
+ return result;
+ }
+ else if constexpr (std::is_same_v>)
+ {
+ std::string result;
+ for (size_t i = 0; i < v.size() && i < 10; ++i)
+ {
+ if (i > 0) result += ",";
+ result += std::to_string(v[i]);
+ }
+ if (v.size() > 10) result += ",...";
+ return result;
+ }
+ else if constexpr (std::is_same_v>)
+ {
+ std::string result;
+ for (size_t i = 0; i < v.size() && i < 10; ++i)
+ {
+ if (i > 0) result += ",";
+ result += std::to_string(v[i]);
+ }
+ if (v.size() > 10) result += ",...";
+ return result;
+ }
+ else if constexpr (std::is_same_v>)
+ {
+ std::string result;
+ for (size_t i = 0; i < v.size() && i < 10; ++i)
+ {
+ if (i > 0) result += ",";
+ result += std::to_string(v[i]);
+ }
+ if (v.size() > 10) result += ",...";
+ return result;
+ }
+ else if constexpr (std::is_same_v>)
+ {
+ std::string result;
+ for (size_t i = 0; i < v.size() && i < 10; ++i)
+ {
+ if (i > 0) result += ",";
+ result += std::to_string(v[i]);
+ }
+ if (v.size() > 10) result += ",...";
+ return result;
+ }
+ else if constexpr (std::is_same_v || std::is_same_v)
+ {
+ return fmt::format("{}", v);
+ }
+ else
+ {
+ return std::to_string(v);
+ }
+ }, value);
+}
+
+// Template helper to extract single scalar value from TIFF tag metadata
+// Per nvImageCodec team: value_count check is sufficient, buffer_size check is redundant
+template
+static bool extract_single_value(const std::vector& buffer,
+ int value_count,
+ TiffTagValue& out_value)
+{
+ if (value_count == 1)
+ {
+ T val = *reinterpret_cast(buffer.data());
+ out_value = val;
+ return true;
+ }
+ return false;
+}
+
+// Template helper to extract array of values as vector
+template
+static bool extract_value_array(const std::vector& buffer,
+ int value_count,
+ TiffTagValue& out_value)
+{
+ if (value_count > 1)
+ {
+ const T* vals = reinterpret_cast(buffer.data());
+ out_value = std::vector(vals, vals + value_count);
+ return true;
+ }
+ return false;
+}
+
+// ============================================================================
+// NvImageCodecTiffParserManager Implementation
+// ============================================================================
+
+NvImageCodecTiffParserManager::NvImageCodecTiffParserManager()
+ : instance_(nullptr), decoder_(nullptr), cpu_decoder_(nullptr), initialized_(false)
+{
+ try
+ {
+ // Create nvImageCodec instance for TIFF parsing (separate from decoder instance)
+ nvimgcodecInstanceCreateInfo_t create_info{};
+ create_info.struct_type = NVIMGCODEC_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ create_info.struct_size = sizeof(nvimgcodecInstanceCreateInfo_t);
+ create_info.struct_next = nullptr;
+ create_info.load_builtin_modules = 1; // Load JPEG, PNG, etc.
+ create_info.load_extension_modules = 1; // Load JPEG2K, TIFF, etc.
+ create_info.extension_modules_path = nullptr;
+ create_info.create_debug_messenger = 0; // Disable debug for TIFF parser
+ create_info.debug_messenger_desc = nullptr;
+ create_info.message_severity = 0;
+ create_info.message_category = 0;
+
+ nvimgcodecStatus_t status = nvimgcodecInstanceCreate(&instance_, &create_info);
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ status_message_ = fmt::format("Failed to create nvImageCodec instance for TIFF parsing (status: {})",
+ static_cast(status));
+ #ifdef DEBUG
+ fmt::print("⚠️ {}\n", status_message_);
+ #endif // DEBUG
+ return;
+ }
+
+ // Create decoder for metadata extraction (not for image decoding)
+ // This decoder is used exclusively for nvimgcodecDecoderGetMetadata() calls
+ nvimgcodecExecutionParams_t exec_params{};
+ exec_params.struct_type = NVIMGCODEC_STRUCTURE_TYPE_EXECUTION_PARAMS;
+ exec_params.struct_size = sizeof(nvimgcodecExecutionParams_t);
+ exec_params.struct_next = nullptr;
+ exec_params.device_allocator = nullptr;
+ exec_params.pinned_allocator = nullptr;
+ exec_params.max_num_cpu_threads = 0;
+ exec_params.executor = nullptr;
+ exec_params.device_id = NVIMGCODEC_DEVICE_CPU_ONLY; // CPU-only for metadata extraction
+ exec_params.pre_init = 0;
+ exec_params.skip_pre_sync = 0;
+ exec_params.num_backends = 0;
+ exec_params.backends = nullptr;
+
+ status = nvimgcodecDecoderCreate(instance_, &decoder_, &exec_params, nullptr);
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ nvimgcodecInstanceDestroy(instance_);
+ instance_ = nullptr;
+ status_message_ = fmt::format("Failed to create decoder for metadata extraction (status: {})",
+ static_cast(status));
+ #ifdef DEBUG
+ fmt::print("⚠️ {}\n", status_message_);
+ #endif // DEBUG
+ return;
+ }
+
+ // Create CPU-only decoder for native CPU decoding
+ nvimgcodecBackendKind_t cpu_backend_kind = NVIMGCODEC_BACKEND_KIND_CPU_ONLY;
+ nvimgcodecBackendParams_t cpu_backend_params{};
+ cpu_backend_params.struct_type = NVIMGCODEC_STRUCTURE_TYPE_BACKEND_PARAMS;
+ cpu_backend_params.struct_size = sizeof(nvimgcodecBackendParams_t);
+ cpu_backend_params.struct_next = nullptr;
+
+ nvimgcodecBackend_t cpu_backend{};
+ cpu_backend.struct_type = NVIMGCODEC_STRUCTURE_TYPE_BACKEND;
+ cpu_backend.struct_size = sizeof(nvimgcodecBackend_t);
+ cpu_backend.struct_next = nullptr;
+ cpu_backend.kind = cpu_backend_kind;
+ cpu_backend.params = cpu_backend_params;
+
+ nvimgcodecExecutionParams_t cpu_exec_params = exec_params;
+ cpu_exec_params.num_backends = 1;
+ cpu_exec_params.backends = &cpu_backend;
+
+ if (nvimgcodecDecoderCreate(instance_, &cpu_decoder_, &cpu_exec_params, nullptr) == NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print("✅ CPU-only decoder created successfully (TIFF parser)\n");
+ #endif // DEBUG
+ }
+ else
+ {
+ #ifdef DEBUG
+ fmt::print("⚠️ Failed to create CPU-only decoder (CPU decoding will use fallback)\n");
+ #endif // DEBUG
+ cpu_decoder_ = nullptr;
+ }
+
+ initialized_ = true;
+ status_message_ = "nvImageCodec TIFF parser initialized successfully (with metadata extraction support)";
+ #ifdef DEBUG
+ fmt::print("✅ {}\n", status_message_);
+ #endif // DEBUG
+ }
+ catch (const std::exception& e)
+ {
+ status_message_ = fmt::format("nvImageCodec TIFF parser initialization exception: {}", e.what());
+ #ifdef DEBUG
+ fmt::print("❌ {}\n", status_message_);
+ #endif // DEBUG
+ initialized_ = false;
+ }
+}
+
+NvImageCodecTiffParserManager::~NvImageCodecTiffParserManager()
+{
+ // Proper cleanup: destroy decoders first, then instance
+ // Per nvImageCodec team: all code streams should be destroyed before this point
+ // (handled by TiffFileParser destructors which are called before singleton destruction)
+
+ if (cpu_decoder_)
+ {
+ nvimgcodecDecoderDestroy(cpu_decoder_);
+ cpu_decoder_ = nullptr;
+ }
+
+ if (decoder_)
+ {
+ nvimgcodecDecoderDestroy(decoder_);
+ decoder_ = nullptr;
+ }
+
+ if (instance_)
+ {
+ nvimgcodecInstanceDestroy(instance_);
+ instance_ = nullptr;
+ }
+}
+
+// ============================================================================
+// TiffFileParser Implementation
+// ============================================================================
+
+TiffFileParser::TiffFileParser(const std::string& file_path)
+ : file_path_(file_path), initialized_(false),
+ main_code_stream_(nullptr)
+{
+ auto& manager = NvImageCodecTiffParserManager::instance();
+
+ if (!manager.is_available())
+ {
+ throw std::runtime_error(fmt::format("nvImageCodec not available: {}",
+ manager.get_status()));
+ }
+
+ try
+ {
+ // Step 1: Create code stream from TIFF file
+ nvimgcodecStatus_t status = nvimgcodecCodeStreamCreateFromFile(
+ manager.get_instance(),
+ &main_code_stream_,
+ file_path.c_str()
+ );
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ throw std::runtime_error(fmt::format("Failed to create code stream from file: {} (status: {})",
+ file_path, static_cast(status)));
+ }
+
+ #ifdef DEBUG
+ fmt::print("✅ Opened TIFF file: {}\n", file_path);
+ #endif // DEBUG
+
+ // Step 2: Parse TIFF structure (metadata only)
+ parse_tiff_structure();
+
+ initialized_ = true;
+ #ifdef DEBUG
+ fmt::print("✅ TIFF parser initialized with {} IFDs\n", ifd_infos_.size());
+ #endif // DEBUG
+ }
+ catch (const std::exception& e)
+ {
+ // Don't explicitly destroy main_code_stream_ here - let instance cleanup handle it
+ // (See destructor comment for explanation of static destruction order issues)
+ main_code_stream_ = nullptr;
+
+ throw; // Re-throw
+ }
+}
+
+TiffFileParser::~TiffFileParser()
+{
+ // Per nvImageCodec team: each code stream (parent or sub) has its own state
+ // and MUST be explicitly destroyed. Sub-streams are NOT automatically cleaned
+ // up when the parent is destroyed.
+
+ // Destroy sub-code streams first (IFD streams)
+ for (auto& ifd_info : ifd_infos_)
+ {
+ if (ifd_info.sub_code_stream)
+ {
+ nvimgcodecCodeStreamDestroy(ifd_info.sub_code_stream);
+ ifd_info.sub_code_stream = nullptr;
+ }
+ }
+
+ // Then destroy main code stream
+ if (main_code_stream_)
+ {
+ nvimgcodecCodeStreamDestroy(main_code_stream_);
+ main_code_stream_ = nullptr;
+ }
+
+ ifd_infos_.clear();
+}
+
+void TiffFileParser::parse_tiff_structure()
+{
+ // Get TIFF structure information
+ nvimgcodecCodeStreamInfo_t stream_info{};
+ stream_info.struct_type = NVIMGCODEC_STRUCTURE_TYPE_CODE_STREAM_INFO;
+ stream_info.struct_size = sizeof(nvimgcodecCodeStreamInfo_t);
+ stream_info.struct_next = nullptr;
+
+ nvimgcodecStatus_t status = nvimgcodecCodeStreamGetCodeStreamInfo(
+ main_code_stream_, &stream_info);
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ throw std::runtime_error(fmt::format("Failed to get code stream info (status: {})",
+ static_cast(status)));
+ }
+
+ uint32_t num_ifds = stream_info.num_images;
+ #ifdef DEBUG
+ fmt::print(" TIFF has {} IFDs (resolution levels)\n", num_ifds);
+ #endif // DEBUG
+
+ if (stream_info.codec_name[0] != '\0')
+ {
+ #ifdef DEBUG
+ fmt::print(" Codec: {}\n", stream_info.codec_name);
+ #endif // DEBUG
+ }
+
+ // Get information for each IFD
+ for (uint32_t i = 0; i < num_ifds; ++i)
+ {
+ IfdInfo ifd_info;
+ ifd_info.index = i;
+
+ // Create view for this IFD
+ nvimgcodecCodeStreamView_t view{};
+ view.struct_type = NVIMGCODEC_STRUCTURE_TYPE_CODE_STREAM_VIEW;
+ view.struct_size = sizeof(nvimgcodecCodeStreamView_t);
+ view.struct_next = nullptr;
+ view.image_idx = i; // Note: nvImageCodec uses 'image_idx' not 'image_index'
+
+ // Get sub-code stream for this IFD
+ status = nvimgcodecCodeStreamGetSubCodeStream(main_code_stream_,
+ &ifd_info.sub_code_stream,
+ &view);
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Failed to get sub-code stream for IFD {} (status: {})\n",
+ i, static_cast(status));
+ #endif // DEBUG
+ #ifdef DEBUG
+ fmt::print(" This IFD will be SKIPPED and cannot be decoded.\n");
+ #endif // DEBUG
+ // Set sub_code_stream to nullptr explicitly to mark as invalid
+ ifd_info.sub_code_stream = nullptr;
+ continue;
+ }
+
+ // Get image information for this IFD
+ nvimgcodecImageInfo_t image_info{};
+ image_info.struct_type = NVIMGCODEC_STRUCTURE_TYPE_IMAGE_INFO;
+ image_info.struct_size = sizeof(nvimgcodecImageInfo_t);
+ image_info.struct_next = nullptr;
+
+ status = nvimgcodecCodeStreamGetImageInfo(ifd_info.sub_code_stream, &image_info);
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print("❌ Failed to get image info for IFD {} (status: {})\n",
+ i, static_cast(status));
+ #endif // DEBUG
+ #ifdef DEBUG
+ fmt::print(" This IFD will be SKIPPED and cannot be decoded.\n");
+ #endif // DEBUG
+ // NOTE: Do NOT destroy sub_code_stream here - it's a view into main_code_stream
+ // Main stream destruction will handle cleanup. Just mark as invalid.
+ ifd_info.sub_code_stream = nullptr;
+ continue;
+ }
+
+ // Extract IFD metadata
+ ifd_info.width = image_info.plane_info[0].width;
+ ifd_info.height = image_info.plane_info[0].height;
+ ifd_info.num_channels = image_info.num_planes;
+
+ // Extract bits per sample from sample type
+ // sample_type encoding: bytes_per_element = (type >> 11) & 0xFF
+ // Convert bytes to bits
+ auto sample_type = image_info.plane_info[0].sample_type;
+ int bytes_per_element = (static_cast(sample_type) >> 11) & 0xFF;
+ ifd_info.bits_per_sample = bytes_per_element * 8; // Convert bytes to bits
+
+ // NOTE: image_info.codec_name typically contains "tiff" (the container format)
+ // We need to determine the actual compression codec (jpeg2000, jpeg, etc.)
+ if (image_info.codec_name[0] != '\0')
+ {
+ ifd_info.codec = image_info.codec_name;
+ }
+
+ // Extract metadata for this IFD using nvimgcodecDecoderGetMetadata
+ // Extract vendor-specific metadata (Aperio, Philips, etc.)
+ extract_ifd_metadata(ifd_info);
+
+ // Extract TIFF metadata using available methods
+ extract_tiff_tags(ifd_info);
+
+ // TODO(nvImageCodec 0.7.0): Use direct TIFF tag queries when 0.7.0 is released
+ // Individual TIFF tag access (e.g., COMPRESSION tag 259) will be available in 0.7.0
+ // Example: metadata = decoder.get_metadata(scs, name="Compression")
+ //
+ // Current limitation (0.6.0):
+ // - codec_name returns "tiff" (container format) not compression type
+ // - Individual TIFF tags not exposed through metadata API
+ // - Only vendor-specific metadata blobs available (MED_APERIO, MED_PHILIPS, etc.)
+ //
+ // Workaround: Infer compression from chroma_subsampling and file extension
+ // Reference: https://nvidia.slack.com/archives/C092X06LK9U (Oct 27, 2024)
+ if (ifd_info.codec == "tiff")
+ {
+ // Try to infer compression from TIFF metadata first
+ bool compression_inferred = false;
+
+ // Check if we have TIFF Compression tag (stored as typed value)
+ auto compression_it = ifd_info.tiff_tags.find("COMPRESSION");
+ if (compression_it != ifd_info.tiff_tags.end())
+ {
+ // COMPRESSION tag is always SHORT (uint16_t) per TIFF spec
+ // Check type before extracting to avoid exceptions
+ if (std::holds_alternative(compression_it->second))
+ {
+ uint16_t compression_value = std::get(compression_it->second);
+
+ switch (compression_value)
+ {
+ case 1: // COMPRESSION_NONE
+ // Keep as "tiff" for uncompressed
+ #ifdef DEBUG
+ fmt::print(" ℹ️ Detected uncompressed TIFF\n");
+ #endif // DEBUG
+ compression_inferred = true;
+ break;
+ case 5: // COMPRESSION_LZW
+ ifd_info.codec = "tiff"; // nvImageCodec handles as tiff
+ compression_inferred = true;
+ #ifdef DEBUG
+ fmt::print(" ℹ️ Detected LZW compression (TIFF codec)\n");
+ #endif // DEBUG
+ break;
+ case 7: // COMPRESSION_JPEG
+ ifd_info.codec = "jpeg"; // Use JPEG decoder!
+ compression_inferred = true;
+ #ifdef DEBUG
+ fmt::print(" ℹ️ Detected JPEG compression → using JPEG codec\n");
+ #endif // DEBUG
+ break;
+ case 8: // COMPRESSION_DEFLATE (Adobe-style)
+ case 32946: // COMPRESSION_DEFLATE (old-style)
+ ifd_info.codec = "tiff";
+ compression_inferred = true;
+ #ifdef DEBUG
+ fmt::print(" ℹ️ Detected DEFLATE compression (TIFF codec)\n");
+ #endif // DEBUG
+ break;
+ case 33003: // Aperio JPEG2000 YCbCr
+ case 33005: // Aperio JPEG2000 RGB
+ case 34712: // JPEG2000
+ ifd_info.codec = "jpeg2000";
+ compression_inferred = true;
+ #ifdef DEBUG
+ fmt::print(" ℹ️ Detected JPEG2000 compression\n");
+ #endif // DEBUG
+ break;
+ default:
+ #ifdef DEBUG
+ fmt::print(" ⚠️ Unknown TIFF compression value: {}\n", compression_value);
+ #endif // DEBUG
+ break;
+ }
+ }
+ else
+ {
+ #ifdef DEBUG
+ fmt::print(" ⚠️ COMPRESSION tag is not uint16_t (unexpected type)\n");
+ #endif // DEBUG
+ }
+ }
+
+ // Fallback to filename-based heuristics if metadata didn't help
+ if (!compression_inferred)
+ {
+ // Aperio JPEG2000 files typically have "JP2K" in filename
+ if (file_path_.find("JP2K") != std::string::npos ||
+ file_path_.find("jp2k") != std::string::npos)
+ {
+ ifd_info.codec = "jpeg2000";
+ #ifdef DEBUG
+ fmt::print(" ℹ️ Inferred codec 'jpeg2000' from filename (JP2K pattern)\n");
+ #endif // DEBUG
+ compression_inferred = true;
+ }
+ }
+
+ // Warning if we still couldn't infer compression
+ if (!compression_inferred && ifd_info.tiff_tags.empty())
+ {
+ #ifdef DEBUG
+ fmt::print(" ⚠️ Warning: codec is 'tiff' but could not infer compression.\n");
+ fmt::print(" File: {}\n", file_path_);
+ fmt::print(" This may limit CPU decoder availability.\n");
+ #endif
+ }
+ }
+
+ ifd_infos_.push_back(std::move(ifd_info));
+ }
+
+ // Report parsing results
+ if (ifd_infos_.size() == num_ifds)
+ {
+ #ifdef DEBUG
+ fmt::print("✅ TIFF parser initialized with {} IFDs (all successful)\n", ifd_infos_.size());
+ #endif // DEBUG
+ }
+ else
+ {
+ #ifdef DEBUG
+ fmt::print("⚠️ TIFF parser initialized with {} IFDs ({} out of {} total)\n",
+ ifd_infos_.size(), ifd_infos_.size(), num_ifds);
+ #endif // DEBUG
+ #ifdef DEBUG
+ fmt::print(" {} IFDs were skipped due to parsing errors\n", num_ifds - ifd_infos_.size());
+ #endif // DEBUG
+ }
+}
+
+void TiffFileParser::extract_ifd_metadata(IfdInfo& ifd_info)
+{
+ auto& manager = NvImageCodecTiffParserManager::instance();
+
+ #ifdef DEBUG
+ fmt::print("🔍 Extracting metadata for IFD[{}]...\n", ifd_info.index);
+ #endif
+
+ if (!manager.get_decoder() || !ifd_info.sub_code_stream)
+ {
+ if (!manager.get_decoder())
+ fmt::print(" ⚠️ Decoder not available\n");
+ if (!ifd_info.sub_code_stream)
+ fmt::print(" ⚠️ No sub-code stream for this IFD\n");
+ return; // No decoder or stream available
+ }
+
+ // Step 1: Get metadata count (first call with nullptr)
+ int metadata_count = 0;
+ nvimgcodecStatus_t status = nvimgcodecDecoderGetMetadata(
+ manager.get_decoder(),
+ ifd_info.sub_code_stream,
+ nullptr, // First call: get count only
+ &metadata_count
+ );
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print(" ⚠️ Metadata query failed with status: {}\n", static_cast(status));
+ #endif
+ return;
+ }
+
+ if (metadata_count == 0)
+ {
+ #ifdef DEBUG
+ fmt::print(" ℹ️ No metadata entries found for this IFD\n");
+ #endif
+ return; // No metadata
+ }
+
+ #ifdef DEBUG
+ fmt::print(" ✅ Found {} metadata entries for IFD[{}]\n", metadata_count, ifd_info.index);
+ #endif
+
+ // Step 2: Allocate metadata structures AND buffers
+ // nvImageCodec requires us to allocate buffers based on buffer_size from first call
+ std::vector metadata_structs(metadata_count);
+ std::vector metadata_ptrs(metadata_count);
+ std::vector> metadata_buffers(metadata_count); // Storage for actual data
+
+ // First, query to get buffer sizes (metadata structs must be initialized)
+ for (int i = 0; i < metadata_count; i++)
+ {
+ metadata_structs[i].struct_type = NVIMGCODEC_STRUCTURE_TYPE_METADATA;
+ metadata_structs[i].struct_size = sizeof(nvimgcodecMetadata_t);
+ metadata_structs[i].struct_next = nullptr;
+ metadata_structs[i].buffer = nullptr; // Query mode: get sizes
+ metadata_structs[i].buffer_size = 0;
+ metadata_ptrs[i] = &metadata_structs[i];
+ }
+
+ // Query call to get buffer sizes
+ status = nvimgcodecDecoderGetMetadata(
+ manager.get_decoder(),
+ ifd_info.sub_code_stream,
+ metadata_ptrs.data(),
+ &metadata_count
+ );
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print(" ⚠️ Failed to query metadata sizes (status: {})\n", static_cast(status));
+ #endif
+ return;
+ }
+
+ // Now allocate buffers based on reported sizes
+ for (int i = 0; i < metadata_count; i++)
+ {
+ size_t required_size = metadata_structs[i].buffer_size;
+ if (required_size > 0)
+ {
+ metadata_buffers[i].resize(required_size);
+ metadata_structs[i].buffer = metadata_buffers[i].data();
+ #ifdef DEBUG
+ fmt::print(" 📦 Allocated {} bytes for metadata[{}]\n", required_size, i);
+ #endif
+ }
+ }
+
+ // Step 3: Get actual metadata content (buffers now allocated)
+ status = nvimgcodecDecoderGetMetadata(
+ manager.get_decoder(),
+ ifd_info.sub_code_stream,
+ metadata_ptrs.data(),
+ &metadata_count
+ );
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ #ifdef DEBUG
+ fmt::print(" ⚠️ Failed to retrieve metadata content (status: {})\n", static_cast(status));
+ #endif
+ return;
+ }
+
+ #ifdef DEBUG
+ fmt::print(" ✅ Successfully retrieved {} metadata entries with content\n", metadata_count);
+ #endif
+
+ // Step 4: Process each metadata entry
+ for (int j = 0; j < metadata_count; ++j)
+ {
+ if (!metadata_ptrs[j])
+ continue;
+
+ nvimgcodecMetadata_t* metadata = metadata_ptrs[j];
+
+ // Extract metadata fields
+ int kind = metadata->kind;
+ int format = metadata->format;
+ size_t buffer_size = metadata->buffer_size;
+ const uint8_t* buffer = static_cast(metadata->buffer);
+
+ #ifdef DEBUG
+ // Map kind to human-readable name for debugging
+ const char* kind_name = "UNKNOWN";
+ switch (kind) {
+ case NVIMGCODEC_METADATA_KIND_UNKNOWN: kind_name = "UNKNOWN"; break;
+ case NVIMGCODEC_METADATA_KIND_TIFF_TAG: kind_name = "TIFF_TAG"; break;
+ case NVIMGCODEC_METADATA_KIND_ICC_PROFILE: kind_name = "ICC_PROFILE"; break;
+ case NVIMGCODEC_METADATA_KIND_EXIF: kind_name = "EXIF"; break;
+ case NVIMGCODEC_METADATA_KIND_GEO: kind_name = "GEO"; break;
+ case NVIMGCODEC_METADATA_KIND_MED_APERIO: kind_name = "MED_APERIO"; break;
+ case NVIMGCODEC_METADATA_KIND_MED_PHILIPS: kind_name = "MED_PHILIPS"; break;
+ case NVIMGCODEC_METADATA_KIND_MED_VENTANA: kind_name = "MED_VENTANA"; break;
+ case NVIMGCODEC_METADATA_KIND_MED_LEICA: kind_name = "MED_LEICA"; break;
+ case NVIMGCODEC_METADATA_KIND_MED_TRESTLE: kind_name = "MED_TRESTLE"; break;
+ }
+ fmt::print(" Metadata[{}]: kind={} ({}), format={}, size={}\n",
+ j, kind, kind_name, format, buffer_size);
+ #endif
+
+ // Store in metadata_blobs map
+ if (buffer && buffer_size > 0)
+ {
+ IfdInfo::MetadataBlob blob;
+ blob.format = format;
+ blob.data.assign(buffer, buffer + buffer_size);
+ ifd_info.metadata_blobs[kind] = std::move(blob);
+
+ // Note: ImageDescription is now extracted directly via TIFF tag 270
+ // in extract_tiff_tags() using nvImageCodec 0.7.0's direct tag query API.
+ // The vendor metadata blobs (MED_APERIO, MED_PHILIPS, etc.) are stored
+ // above for format detection and vendor-specific parsing.
+ }
+ }
+}
+
+const IfdInfo& TiffFileParser::get_ifd(uint32_t index) const
+{
+ if (index >= ifd_infos_.size())
+ {
+ throw std::out_of_range(fmt::format("IFD index {} out of range (have {} IFDs)",
+ index, ifd_infos_.size()));
+ }
+ return ifd_infos_[index];
+}
+
+std::string TiffFileParser::get_tiff_tag(uint32_t ifd_index, const std::string& tag_name) const
+{
+ if (ifd_index >= ifd_infos_.size())
+ return "";
+
+ auto it = ifd_infos_[ifd_index].tiff_tags.find(tag_name);
+ if (it != ifd_infos_[ifd_index].tiff_tags.end())
+ return tiff_tag_value_to_string(it->second);
+
+ return "";
+}
+
+void TiffFileParser::extract_tiff_tags(IfdInfo& ifd_info)
+{
+ auto& manager = NvImageCodecTiffParserManager::instance();
+
+ if (!manager.get_decoder())
+ {
+ #ifdef DEBUG
+ fmt::print(" ⚠️ Cannot extract TIFF tags: decoder not available\n");
+ #endif // DEBUG
+ return;
+ }
+
+ if (!ifd_info.sub_code_stream)
+ {
+ #ifdef DEBUG
+ fmt::print(" ⚠️ Cannot extract TIFF tags: sub_code_stream is null\n");
+ #endif // DEBUG
+ return;
+ }
+
+ // ========================================================================
+ // nvImageCodec 0.7.0: Direct TIFF Tag Retrieval by ID
+ // ========================================================================
+ // Python API example:
+ // tag_value = decoder.get_metadata(scs, id=tag_id).value
+ //
+ // C API equivalent:
+ // 1. Set metadata.kind = NVIMGCODEC_METADATA_KIND_TIFF_TAG
+ // 2. Set metadata.id = (e.g., 270 for ImageDescription)
+ // 3. Call nvimgcodecDecoderGetMetadata() to retrieve the specific tag
+
+ // Map of TIFF tag IDs to names for tags we want to extract
+ std::vector> tiff_tags_to_query = {
+ {254, "SUBFILETYPE"}, // Image type classification (0=full, 1=reduced, etc.)
+ {256, "IMAGEWIDTH"},
+ {257, "IMAGELENGTH"},
+ {258, "BITSPERSAMPLE"},
+ {259, "COMPRESSION"}, // Critical for codec detection!
+ {262, "PHOTOMETRIC"},
+ {270, "IMAGEDESCRIPTION"}, // Vendor metadata
+ {271, "MAKE"}, // Scanner manufacturer
+ {272, "MODEL"}, // Scanner model
+ {277, "SAMPLESPERPIXEL"},
+ {305, "SOFTWARE"},
+ {306, "DATETIME"},
+ {322, "TILEWIDTH"},
+ {323, "TILELENGTH"},
+ {330, "SUBIFD"}, // SubIFD offsets (for OME-TIFF, etc.)
+ {339, "SAMPLEFORMAT"},
+ {347, "JPEGTABLES"} // Shared JPEG tables
+ };
+
+ #ifdef DEBUG
+ fmt::print(" 📋 Extracting TIFF tags (nvImageCodec 0.7.0 - query by ID)...\n");
+ #endif // DEBUG
+
+ int extracted_count = 0;
+
+ // Query each tag individually by ID (following Python API pattern)
+ for (const auto& [tag_id, tag_name] : tiff_tags_to_query)
+ {
+ // Set up metadata request for specific tag
+ nvimgcodecMetadata_t metadata{};
+ metadata.struct_type = NVIMGCODEC_STRUCTURE_TYPE_METADATA;
+ metadata.struct_size = sizeof(nvimgcodecMetadata_t);
+ metadata.struct_next = nullptr;
+ metadata.kind = NVIMGCODEC_METADATA_KIND_TIFF_TAG;
+ metadata.id = tag_id;
+ metadata.buffer = nullptr;
+ metadata.buffer_size = 0;
+
+ nvimgcodecMetadata_t* metadata_ptr = &metadata;
+ int metadata_count = 1;
+
+ // First call: query buffer size
+ nvimgcodecStatus_t status = nvimgcodecDecoderGetMetadata(
+ manager.get_decoder(),
+ ifd_info.sub_code_stream,
+ &metadata_ptr,
+ &metadata_count
+ );
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ // API error - log warning for unexpected failures
+ // Note: Some status codes may indicate "tag not found" which is normal
+ #ifdef DEBUG
+ fmt::print(" ⚠️ TIFF tag {} query failed (status: {})\n", tag_id, static_cast(status));
+ #endif
+ continue;
+ }
+
+ if (metadata.buffer_size == 0)
+ {
+ // Tag not present in this IFD - this is normal, not all tags exist
+ continue;
+ }
+
+ // Allocate buffer for tag value
+ std::vector buffer(metadata.buffer_size);
+ metadata.buffer = buffer.data();
+
+ // Second call: retrieve actual value
+ status = nvimgcodecDecoderGetMetadata(
+ manager.get_decoder(),
+ ifd_info.sub_code_stream,
+ &metadata_ptr,
+ &metadata_count
+ );
+
+ if (status != NVIMGCODEC_STATUS_SUCCESS)
+ {
+ // Unexpected: first call succeeded but second failed
+ #ifdef DEBUG
+ fmt::print(" ⚠️ TIFF tag {} retrieval failed (status: {})\n", tag_id, static_cast(status));
+ #endif
+ continue;
+ }
+
+ if (metadata.buffer_size == 0)
+ {
+ // Unexpected: buffer was allocated but size is now 0
+ continue;
+ }
+
+ // Convert value based on type and store as typed variant
+ // The variant is initialized to std::monostate by default
+ TiffTagValue tag_value;
+
+ switch (metadata.value_type)
+ {
+ case NVIMGCODEC_METADATA_VALUE_TYPE_ASCII:
+ {
+ // ASCII string
+ std::string str_val;
+ str_val.assign(reinterpret_cast(buffer.data()), metadata.buffer_size);
+ // Remove trailing null(s) if present
+ while (!str_val.empty() && str_val.back() == '\0')
+ str_val.pop_back();
+ if (!str_val.empty())
+ {
+ tag_value = std::move(str_val);
+ }
+ break;
+ }
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_SHORT:
+ extract_single_value(buffer, metadata.value_count, tag_value) ||
+ extract_value_array(buffer, metadata.value_count, tag_value);
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_LONG:
+ extract_single_value(buffer, metadata.value_count, tag_value) ||
+ extract_value_array(buffer, metadata.value_count, tag_value);
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_BYTE:
+ if (metadata.value_count == 1)
+ {
+ tag_value = buffer[0];
+ }
+ else
+ {
+ // Binary data - store as vector
+ std::vector vec(buffer.begin(), buffer.begin() + metadata.buffer_size);
+ tag_value = std::move(vec);
+ }
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_SBYTE:
+ if (metadata.value_count == 1)
+ {
+ tag_value = static_cast(buffer[0]);
+ }
+ else
+ {
+ // Signed byte array - store as vector (reinterpret as needed)
+ std::vector vec(buffer.begin(), buffer.begin() + metadata.buffer_size);
+ tag_value = std::move(vec);
+ }
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_UNDEFINED:
+ // UNDEFINED type - binary data, store as vector
+ {
+ std::vector vec(buffer.begin(), buffer.begin() + metadata.buffer_size);
+ tag_value = std::move(vec);
+ }
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_SSHORT:
+ extract_single_value(buffer, metadata.value_count, tag_value) ||
+ extract_value_array(buffer, metadata.value_count, tag_value);
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_SLONG:
+ extract_single_value(buffer, metadata.value_count, tag_value) ||
+ extract_value_array(buffer, metadata.value_count, tag_value);
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_LONG8:
+ case NVIMGCODEC_METADATA_VALUE_TYPE_IFD8:
+ extract_single_value(buffer, metadata.value_count, tag_value) ||
+ extract_value_array(buffer, metadata.value_count, tag_value);
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_SLONG8:
+ extract_single_value(buffer, metadata.value_count, tag_value) ||
+ extract_value_array(buffer, metadata.value_count, tag_value);
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_FLOAT:
+ extract_single_value(buffer, metadata.value_count, tag_value) ||
+ extract_value_array(buffer, metadata.value_count, tag_value);
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_DOUBLE:
+ extract_single_value(buffer, metadata.value_count, tag_value) ||
+ extract_value_array(buffer, metadata.value_count, tag_value);
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_RATIONAL:
+ if (metadata.value_count == 1 && metadata.buffer_size >= 8)
+ {
+ // Single Rational = two LONGs (numerator, denominator) - store as string
+ uint32_t num = *reinterpret_cast(buffer.data());
+ uint32_t den = *reinterpret_cast(buffer.data() + 4);
+ if (den != 0)
+ tag_value = fmt::format("{}/{}", num, den);
+ else
+ tag_value = std::to_string(num);
+ }
+ else if (metadata.value_count > 1)
+ {
+ // Array of Rationals - store as comma-separated string
+ size_t rational_size = 8; // 2 × uint32_t
+ std::string result;
+ for (size_t i = 0; i < metadata.value_count; ++i)
+ {
+ const uint8_t* ptr = buffer.data() + i * rational_size;
+ uint32_t num = *reinterpret_cast(ptr);
+ uint32_t den = *reinterpret_cast(ptr + 4);
+ if (i > 0) result += ", ";
+ if (den != 0)
+ result += fmt::format("{}/{}", num, den);
+ else
+ result += std::to_string(num);
+ }
+ tag_value = std::move(result);
+ }
+ break;
+
+ case NVIMGCODEC_METADATA_VALUE_TYPE_SRATIONAL:
+ if (metadata.value_count == 1 && metadata.buffer_size >= 8)
+ {
+ // Single Signed Rational = two SLONGs (numerator, denominator) - store as string
+ int32_t num = *reinterpret_cast(buffer.data());
+ int32_t den = *reinterpret_cast(buffer.data() + 4);
+ if (den != 0)
+ tag_value = fmt::format("{}/{}", num, den);
+ else
+ tag_value = std::to_string(num);
+ }
+ else if (metadata.value_count > 1)
+ {
+ // Array of Signed Rationals - store as comma-separated string
+ size_t rational_size = 8; // 2 × int32_t
+ std::string result;
+ for (size_t i = 0; i < metadata.value_count; ++i)
+ {
+ const uint8_t* ptr = buffer.data() + i * rational_size;
+ int32_t num = *reinterpret_cast(ptr);
+ int32_t den = *reinterpret_cast(ptr + 4);
+ if (i > 0) result += ", ";
+ if (den != 0)
+ result += fmt::format("{}/{}", num, den);
+ else
+ result += std::to_string(num);
+ }
+ tag_value = std::move(result);
+ }
+ break;
+
+ default:
+ // For unknown types, store as binary data or string
+ if (metadata.buffer_size > 0)
+ {
+ if (metadata.buffer_size <= 8 && metadata.value_count == 1)
+ {
+ // Small value - try to interpret as number and store as string
+ uint64_t val = 0;
+ std::memcpy(&val, buffer.data(), std::min(metadata.buffer_size, sizeof(val)));
+ tag_value = std::to_string(val);
+ }
+ else
+ {
+ // Store raw bytes - optionally limit size to prevent storing huge blobs
+ // Use configurable limit (0 = unlimited, default)
+ size_t store_size = metadata.buffer_size;
+ if (max_binary_tag_size_ > 0 && metadata.buffer_size > max_binary_tag_size_)
+ {
+ store_size = max_binary_tag_size_;
+ #ifdef DEBUG
+ fmt::print(" ℹ️ TIFF tag {} binary data truncated: {} -> {} bytes\n",
+ tag_id, metadata.buffer_size, store_size);
+ #endif
+ }
+ std::vector vec(buffer.begin(), buffer.begin() + store_size);
+ tag_value = std::move(vec);
+ }
+ }
+ break;
+ }
+
+ // Check if a value was successfully stored (not monostate)
+ if (!std::holds_alternative(tag_value))
+ {
+ ifd_info.tiff_tags[tag_name] = std::move(tag_value);
+ extracted_count++;
+
+ #ifdef DEBUG
+ // Format value for debug output
+ std::string debug_str = std::visit([](const auto& v) -> std::string {
+ using T = std::decay_t;
+ if constexpr (std::is_same_v)
+ return "";
+ else if constexpr (std::is_same_v)
+ return v.length() > 60 ? v.substr(0, 60) + "..." : v;
+ else if constexpr (std::is_same_v>)
+ return fmt::format("[{} bytes]", v.size());
+ else if constexpr (std::is_same_v>)
+ return fmt::format("[{} uint16 values]", v.size());
+ else if constexpr (std::is_same_v>)
+ return fmt::format("[{} uint32 values]", v.size());
+ else if constexpr (std::is_same_v>)
+ return fmt::format("[{} uint64 values]", v.size());
+ else if constexpr (std::is_same_v>)
+ return fmt::format("[{} float values]", v.size());
+ else if constexpr (std::is_same_v>)
+ return fmt::format("[{} double values]", v.size());
+ else if constexpr (std::is_same_v || std::is_same_v)
+ return fmt::format("{}", v);
+ else
+ return std::to_string(v);
+ }, ifd_info.tiff_tags[tag_name]);
+ fmt::print(" ✅ Tag {} ({}): {}\n", tag_id, tag_name, debug_str);
+ #endif // DEBUG
+ }
+ }
+
+ if (extracted_count > 0)
+ {
+ #ifdef DEBUG
+ fmt::print(" ✅ Extracted {} TIFF tags using nvImageCodec 0.7.0 API\n", extracted_count);
+ #endif // DEBUG
+
+ // Store ImageDescription if available from tags
+ auto desc_it = ifd_info.tiff_tags.find("IMAGEDESCRIPTION");
+ if (desc_it != ifd_info.tiff_tags.end() && ifd_info.image_description.empty())
+ {
+ ifd_info.image_description = tiff_tag_value_to_string(desc_it->second);
+ }
+
+ return; // Success
+ }
+
+ // Fallback: File extension heuristics for older nvImageCodec versions
+ #ifdef DEBUG
+ fmt::print(" ⚠️ Using file extension heuristics (no TIFF tags retrieved)\n");
+ #endif // DEBUG
+
+ std::string ext;
+ size_t dot_pos = file_path_.rfind('.');
+ if (dot_pos != std::string::npos)
+ {
+ ext = file_path_.substr(dot_pos);
+ std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
+ }
+
+ // Aperio SVS, Hamamatsu NDPI, Hamamatsu VMS/VMU typically use JPEG compression
+ if (ext == ".svs" || ext == ".ndpi" || ext == ".vms" || ext == ".vmu")
+ {
+ ifd_info.tiff_tags["COMPRESSION"] = static_cast(7); // TIFF_COMPRESSION_JPEG
+ #ifdef DEBUG
+ fmt::print(" ✅ Inferred JPEG compression (WSI format: {})\n", ext);
+ #endif // DEBUG
+ }
+}
+
+int TiffFileParser::get_subfile_type(uint32_t ifd_index) const
+{
+ std::string subfile_str = get_tiff_tag(ifd_index, "SUBFILETYPE");
+ if (subfile_str.empty())
+ return -1;
+
+ try {
+ return std::stoi(subfile_str);
+ } catch (...) {
+ return -1;
+ }
+}
+
+std::vector TiffFileParser::query_metadata_kinds(uint32_t ifd_index) const
+{
+ std::vector kinds;
+
+ if (ifd_index >= ifd_infos_.size())
+ return kinds;
+
+ // Return all metadata kinds found in this IFD
+ for (const auto& [kind, blob] : ifd_infos_[ifd_index].metadata_blobs)
+ {
+ kinds.push_back(kind);
+ }
+
+ // Also add TIFF_TAG kind if any tags were extracted
+ // nvImageCodec 0.7.0: TIFF_TAG = 1 (not 0!)
+ if (!ifd_infos_[ifd_index].tiff_tags.empty())
+ {
+ kinds.insert(kinds.begin(), NVIMGCODEC_METADATA_KIND_TIFF_TAG);
+ }
+
+ return kinds;
+}
+
+std::string TiffFileParser::get_detected_format() const
+{
+ if (ifd_infos_.empty())
+ return "Unknown";
+
+ // Check first IFD for vendor-specific metadata
+ // nvImageCodec 0.7.0: Use proper enum values
+ const auto& kinds = query_metadata_kinds(0);
+
+ for (int kind : kinds)
+ {
+ switch (kind)
+ {
+ case NVIMGCODEC_METADATA_KIND_MED_APERIO:
+ return "Aperio SVS";
+ case NVIMGCODEC_METADATA_KIND_MED_PHILIPS:
+ return "Philips TIFF";
+ case NVIMGCODEC_METADATA_KIND_MED_LEICA:
+ return "Leica SCN";
+ case NVIMGCODEC_METADATA_KIND_MED_VENTANA:
+ return "Ventana";
+ case NVIMGCODEC_METADATA_KIND_MED_TRESTLE:
+ return "Trestle";
+ default:
+ break;
+ }
+ }
+
+ // Fallback: Generic TIFF with detected codec
+ if (!ifd_infos_.empty() && !ifd_infos_[0].codec.empty())
+ {
+ return fmt::format("Generic TIFF ({})", ifd_infos_[0].codec);
+ }
+
+ return "Generic TIFF";
+}
+
+#endif // CUCIM_HAS_NVIMGCODEC
+
+} // namespace cuslide2::nvimgcodec
+
diff --git a/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_tiff_parser.h b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_tiff_parser.h
new file mode 100644
index 000000000..22e539018
--- /dev/null
+++ b/cpp/plugins/cucim.kit.cuslide2/src/cuslide/nvimgcodec/nvimgcodec_tiff_parser.h
@@ -0,0 +1,498 @@
+/*
+ * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#pragma once
+
+#ifdef CUCIM_HAS_NVIMGCODEC
+#include
+#endif
+
+#include
+#include
+#include
+#include