Skip to content

Commit

Permalink
Support Aperio SVS with CPU LZW and jpeg2k decoder
Browse files Browse the repository at this point in the history
- Update Jpeg decoder to consider colorspace of the JPEG image
  - We need to set a proper color space from TIFF metadata to the decoder
  - Update tjDecompress2() method call with jpeg_decode_buffer() that modified
    tjDecompress2() implementation
  - Add `jpeg_color_space_` to IFD Class.
- Move additional includes to their own cmake files
- Add Jpeg2k decoder with OpenJpeg
  - Added fast color conversion logic with pre-calculated table
- Add LZW decoder with libtiff's implementation
  - Take part of the code to provide only LZW decoder part
- Add `rows_per_strip_` and `predictor_` attributers to IFD class, to support
  LZW
- Refactor TIFF:resolve_vendor_format() to support Aperio SVS metadata
- Support LZW compressed image (with multi strips) for associated image.

Addresses #17
  • Loading branch information
gigony committed Nov 20, 2021
1 parent b39672f commit 0a9803e
Show file tree
Hide file tree
Showing 22 changed files with 2,257 additions and 998 deletions.
5 changes: 5 additions & 0 deletions LICENSE-3rdparty.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ libtiff
- Copyright:
- Sam Leffler
- Silicon Graphics, Inc.
- Files:
- cpp/plugins/cucim.kit.cuslide/src/cuslide/lzw/lzw_libtiff.cpp : Implementation of lzw decoder.

fmt
- License: MIT License
Expand Down Expand Up @@ -106,6 +108,9 @@ OpenJPEG
- David Janssens
- Centre National d'Etudes Spatiales (CNES), France
- CS Systemes d'Information, France
- Files:
- cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/libopenjpeg.cpp : Implementation of jpeg2k decoder.
- cpp/plugins/cucim.kit.cuslide/src/cuslide/jpeg2k/color_conversion.cpp : Implementation of color conversion methods.

NVIDIA nvJPEG
- License: NVIDIA License
Expand Down
25 changes: 22 additions & 3 deletions cpp/plugins/cucim.kit.cuslide/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ file(STRINGS ${CMAKE_CURRENT_LIST_DIR}/VERSION VERSION)
# Append local cmake module path
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules")

project(cuslide VERSION ${VERSION} DESCRIPTION "cuslide" LANGUAGES CXX CUDA)
project(cuslide VERSION ${VERSION} DESCRIPTION "cuslide" LANGUAGES C CXX CUDA)
set(CUCIM_PLUGIN_NAME "cucim.kit.cuslide")

################################################################################
Expand Down Expand Up @@ -126,6 +126,7 @@ add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) # TODO: create two library, one with
#superbuild_depend(cucim)
superbuild_depend(fmt)
superbuild_depend(libjpeg-turbo) # libjpeg-turbo should be located before libtiff as libtiff depends on libjpeg-turbo
superbuild_depend(libopenjpeg)
superbuild_depend(libtiff)
superbuild_depend(catch2)
superbuild_depend(openslide)
Expand Down Expand Up @@ -172,6 +173,16 @@ add_library(${CUCIM_PLUGIN_NAME}
src/cuslide/deflate/deflate.h
src/cuslide/jpeg/libjpeg_turbo.cpp
src/cuslide/jpeg/libjpeg_turbo.h
src/cuslide/jpeg2k/color_conversion.cpp
src/cuslide/jpeg2k/color_conversion.h
src/cuslide/jpeg2k/color_table.h
src/cuslide/jpeg2k/libopenjpeg.cpp
src/cuslide/jpeg2k/libopenjpeg.h
${deps-libopenjpeg_SOURCE_DIR}/src/bin/common/color.c # for color_sycc_to_rgb() and color_apply_icc_profile()
src/cuslide/lzw/lzw.cpp
src/cuslide/lzw/lzw.h
src/cuslide/lzw/lzw_libtiff.cpp
src/cuslide/lzw/lzw_libtiff.h
src/cuslide/raw/raw.cpp
src/cuslide/raw/raw.h
src/cuslide/tiff/ifd.cpp
Expand All @@ -185,6 +196,14 @@ add_library(${CUCIM_PLUGIN_NAME}
# Note: Since cuslide.cpp is using nlohmann/json.hpp which nvcc cannot parse properly, set other files for cudart.
set_source_files_properties(src/cuslide/jpeg/libjpeg_turbo.cpp PROPERTIES LANGUAGE CUDA)

# compile color.c for libopenjpeg with c++
set_source_files_properties(${deps-libopenjpeg_SOURCE_DIR}/src/bin/common/color.c
PROPERTIES
LANGUAGE C
CMAKE_CXX_VISIBILITY_PRESET default
CMAKE_C_VISIBILITY_PRESET default
CMAKE_VISIBILITY_INLINES_HIDDEN OFF)

# Compile options
set_target_properties(${CUCIM_PLUGIN_NAME}
PROPERTIES
Expand All @@ -211,6 +230,8 @@ target_link_libraries(${CUCIM_PLUGIN_NAME}
cucim::cucim
deps::libtiff
deps::libjpeg-turbo
deps::libopenjpeg
deps::libopenjpeg-lcms2
deps::pugixml
deps::json
deps::libdeflate
Expand All @@ -222,8 +243,6 @@ target_include_directories(${CUCIM_PLUGIN_NAME}
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE
${CMAKE_CURRENT_LIST_DIR}/src
# turbojpeg.h is not included in 'turbojpeg-static' so manually include
${deps-libjpeg-turbo_SOURCE_DIR}
)

# Do not generate SONAME as this would be used as plugin
Expand Down
7 changes: 6 additions & 1 deletion cpp/plugins/cucim.kit.cuslide/cmake/deps/libjpeg-turbo.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ if (NOT TARGET deps::libjpeg-turbo)
cucim_restore_build_shared_libs()

add_library(deps::libjpeg-turbo INTERFACE IMPORTED GLOBAL)

target_link_libraries(deps::libjpeg-turbo INTERFACE turbojpeg-static)
target_include_directories(deps::libjpeg-turbo
INTERFACE
# turbojpeg.h is not included in 'turbojpeg-static' so manually include
${deps-libjpeg-turbo_SOURCE_DIR}
)

set(deps-libjpeg-turbo_SOURCE_DIR ${deps-libjpeg-turbo_SOURCE_DIR} CACHE INTERNAL "" FORCE)
mark_as_advanced(deps-libjpeg-turbo_SOURCE_DIR)
set(deps-libjpeg-turbo_BINARY_DIR ${deps-libjpeg-turbo_BINARY_DIR} CACHE INTERNAL "" FORCE)
Expand Down
112 changes: 112 additions & 0 deletions cpp/plugins/cucim.kit.cuslide/cmake/deps/libopenjpeg.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# Apache License, Version 2.0
# Copyright 2020-2021 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.

if (NOT TARGET deps::libopenjpeg)

FetchContent_Declare(
deps-libopenjpeg
GIT_REPOSITORY https://github.com/uclouvain/openjpeg.git
GIT_TAG v2.4.0
GIT_SHALLOW TRUE
)
FetchContent_GetProperties(deps-libopenjpeg)
if (NOT deps-libopenjpeg_POPULATED)
message(STATUS "Fetching libopenjpeg sources")
FetchContent_Populate(deps-libopenjpeg)
message(STATUS "Fetching libopenjpeg sources - done")
endif ()

# Create static library
# It build a static library when both BUILD_SHARED_LIBS and BUILD_STATIC_LIBS are ON
# (build-debug/_deps/deps-libopenjpeg-src/src/lib/openjp2/CMakeLists.txt:94)
#
# if(BUILD_SHARED_LIBS AND BUILD_STATIC_LIBS)
cucim_set_build_shared_libs(ON)

###########################################################################
# Build liblcms2 with the source in libopenjpeg
###########################################################################
message(" ##GG ${deps-libopenjpeg_SOURCE_DIR}")

add_subdirectory(${deps-libopenjpeg_SOURCE_DIR}/thirdparty/liblcms2 ${deps-libopenjpeg_BINARY_DIR}/thirdparty/liblcms2)

# Override the output library folder path
set_target_properties(lcms2
PROPERTIES
OUTPUT_NAME "lcms2"
ARCHIVE_OUTPUT_DIRECTORY ${deps-libopenjpeg_BINARY_DIR}/thirdparty/lib)

# Override definition of OPJ_HAVE_LIBLCMS2 to build color_apply_icc_profile() method
target_compile_definitions(lcms2
PUBLIC
OPJ_HAVE_LIBLCMS2=1
)

# Set PIC to prevent the following error message
# : /usr/bin/ld: _deps/deps-libopenjpeg-build/thirdparty/lib/liblcms2.a(cmserr.c.o): relocation R_X86_64_PC32 against symbol `_cmsMemPluginChunk' can not be used when making a shared object; recompile with -fPIC
# /usr/bin/ld: final link failed: bad value
set_target_properties(lcms2 PROPERTIES POSITION_INDEPENDENT_CODE ON)

add_library(deps::libopenjpeg-lcms2 INTERFACE IMPORTED GLOBAL)
target_link_libraries(deps::libopenjpeg-lcms2 INTERFACE lcms2)
target_include_directories(deps::libopenjpeg-lcms2
INTERFACE
# lcms2.h is not included in 'lcms2' so manually include
${deps-libopenjpeg_SOURCE_DIR}/thirdparty/liblcms2/include
)

set(deps-libopenjpeg-lcms2_SOURCE_DIR ${deps-libopenjpeg_SOURCE_DIR}/thirdparty/liblcms2 CACHE INTERNAL "" FORCE)
mark_as_advanced(deps-libopenjpeg-lcms2_SOURCE_DIR)
set(deps-libopenjpeg-lcms2_BINARY_DIR ${deps-libopenjpeg_BINARY_DIR}/thirdparty/liblcms2 CACHE INTERNAL "" FORCE)
mark_as_advanced(deps-libopenjpeg-lcms2_BINARY_DIR)

###########################################################################

add_subdirectory(${deps-libopenjpeg_SOURCE_DIR} ${deps-libopenjpeg_BINARY_DIR} EXCLUDE_FROM_ALL)

# Disable visibility to not expose unnecessary symbols
set_target_properties(openjp2_static
PROPERTIES
C_VISIBILITY_PRESET hidden
CXX_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
)
# target_compile_options(openjp2_static PRIVATE $<$<CXX_COMPILER_ID:GNU>:-march=core-avx2>)

# Set PIC to prevent the following error message
# : /usr/bin/ld: lib/libopenjp2.a(cio.c.o): relocation R_X86_64_PC32 against symbol `opj_stream_read_skip' can not be used when making a shared object; recompile with -fPIC
# /usr/bin/ld: final link failed: bad value
set_target_properties(openjp2_static PROPERTIES POSITION_INDEPENDENT_CODE ON)
cucim_restore_build_shared_libs()

add_library(deps::libopenjpeg INTERFACE IMPORTED GLOBAL)
target_link_libraries(deps::libopenjpeg INTERFACE openjp2_static)
target_include_directories(deps::libopenjpeg
INTERFACE
# openjpeg.h is not included in 'openjp2_static' so manually include
${deps-libopenjpeg_SOURCE_DIR}/src/lib/openjp2
# opj_config.h is not included in openjp2_static so manually include
${deps-libopenjpeg_BINARY_DIR}/src/lib/openjp2
# color.h is not included in 'openjp2_static' so manually include
${deps-libopenjpeg_SOURCE_DIR}/src/bin/common
# opj_apps_config.h is not included in 'openjp2_static' so manually include
${deps-libopenjpeg_BINARY_DIR}/src/bin/common
)

set(deps-libopenjpeg_SOURCE_DIR ${deps-libopenjpeg_SOURCE_DIR} CACHE INTERNAL "" FORCE)
mark_as_advanced(deps-libopenjpeg_SOURCE_DIR)
set(deps-libopenjpeg_BINARY_DIR ${deps-libopenjpeg_BINARY_DIR} CACHE INTERNAL "" FORCE)
mark_as_advanced(deps-libopenjpeg_BINARY_DIR)
endif ()
49 changes: 18 additions & 31 deletions cpp/plugins/cucim.kit.cuslide/src/cuslide/cuslide.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ static bool CUCIM_ABI checker_is_valid(const char* file_name, const char* buf, s
(void)size;
auto file = std::filesystem::path(file_name);
auto extension = file.extension().string();
if (extension.compare(".tif") == 0 || extension.compare(".tiff") == 0)
if (extension.compare(".tif") == 0 || extension.compare(".tiff") == 0 || extension.compare(".svs") == 0)
{
return true;
}
Expand All @@ -100,42 +100,29 @@ static bool CUCIM_ABI parser_parse(CuCIMFileHandle* handle, cucim::io::format::I

auto tif = static_cast<cuslide::tiff::TIFF*>(handle->client_data);

std::vector<size_t> main_ifd_list;

size_t ifd_count = tif->ifd_count();
size_t level_count = tif->level_count();
for (size_t i = 0; i < ifd_count; i++)

// If not Aperio SVS format (== Ordinary Pyramid TIFF image)
if (tif->ifd(0)->image_description().rfind("Aperio", 0) != 0)
{
const std::shared_ptr<cuslide::tiff::IFD>& ifd = tif->ifd(i);

// const char* char_ptr = ifd->model().c_str();
// uint32_t width = ifd->width();
// uint32_t height = ifd->height();
// uint32_t bits_per_sample = ifd->bits_per_sample();
// uint32_t samples_per_pixel = ifd->samples_per_pixel();
uint64_t subfile_type = ifd->subfile_type();
// printf("image_description:\n%s\n", ifd->image_description().c_str());
// printf("model=%s, width=%u, height=%u, model=%p bits_per_sample:%u, samples_per_pixel=%u, %lu \n",
// char_ptr,
// width, height, char_ptr, bits_per_sample, samples_per_pixel, subfile_type);
if (subfile_type == 0)
std::vector<size_t> main_ifd_list;
for (size_t i = 0; i < ifd_count; i++)
{
main_ifd_list.push_back(i);
const std::shared_ptr<cuslide::tiff::IFD>& 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!"));
}

// Explicitly forbid loading SVS format (#17)
if (tif->ifd(0)->image_description().rfind("Aperio", 0) == 0)
{
throw std::runtime_error(
fmt::format("cuCIM doesn't support Aperio SVS for now (https://github.com/rapidsai/cucim/issues/17)."));
// 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!"));
}
}

//
Expand Down
Loading

0 comments on commit 0a9803e

Please sign in to comment.