Skip to content

Commit c8bb77d

Browse files
authored
Merge pull request #490 from thewtex/image-to-string
.iwi.cbor.zstd Image IO support
2 parents eea33d4 + 476ea2d commit c8bb77d

17 files changed

+671
-37
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ FetchContent_Declare(
7373
)
7474
set(WITH_TESTS OFF CACHE BOOL "Build libcbor tests")
7575
set(WITH_EXAMPLES OFF CACHE BOOL "Build libcbor examples")
76-
76+
set(SANITIZE OFF CACHE BOOL "Enable sanitizers in Debug mode in libcbor")
7777

7878
list(APPEND CMAKE_MODULE_PATH ${libcbor_SOURCE_DIR}/CMakeModules)
7979
FetchContent_MakeAvailable(rapidjson_lib cli11 rang libcbor)

include/itkWASMImageIO.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ namespace itk
2727
{
2828
/** \class WASMImageIO
2929
*
30-
* \brief Read and write the an itk::Image in format.
30+
* \brief Read and write an itk::Image in a web-friendly format.
3131
*
32-
* This format is intended to facilitage data exchange in itk-wasm.
33-
* It reads and writes an itk-wasm Image object where TypedArrays are
34-
* replaced by binary files on the file system or in a ZIP file.
32+
* This format is intended to facilitate data exchange in itk-wasm.
33+
* It reads and writes an itk-wasm Image object in a CbOR file on the
34+
* filesystem with JSON files and binary files for TypedArrays.
35+
*
36+
* The file extensions used are .iwi and .iwi.cbor.
3537
*
3638
* \ingroup IOFilters
3739
* \ingroup WebAssemblyInterface
@@ -94,8 +96,8 @@ class WebAssemblyInterface_EXPORT WASMImageIO: public StreamingImageIOBase
9496
return 0;
9597
}
9698

97-
void ReadCBOR(void * buffer = nullptr);
98-
void WriteCBOR(const void * buffer = nullptr);
99+
void ReadCBOR(void * buffer = nullptr, unsigned char * cborBuffer = nullptr, size_t cborBufferLength = 0);
100+
size_t WriteCBOR(const void * buffer = nullptr, unsigned char ** cborBuffer = nullptr, bool allocateCBORBuffer = false);
99101

100102
private:
101103
ITK_DISALLOW_COPY_AND_ASSIGN(WASMImageIO);

include/itkWASMStringStream.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
#include "itkWASMDataObject.h"
2222
#include "rapidjson/document.h"
23+
#include <string_view>
2324

2425
namespace itk
2526
{

src/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ if(BUILD_ITK_WASM_IO_MODULES)
2929
set(common_link_flags " -s ALLOW_MEMORY_GROWTH=1 -s FORCE_FILESYSTEM=1 -s MODULARIZE=1 -s WASM=1 -lnodefs.js -s EXIT_RUNTIME=0 -s INVOKE_RUN=1 --post-js ${CMAKE_CURRENT_SOURCE_DIR}/emscripten-module/itkJSPost.js")
3030
set(esm_link_flags " -s EXPORT_ES6=1 -s USE_ES6_IMPORT_META=0")
3131

32+
include(${CMAKE_CURRENT_SOURCE_DIR}/io/internal/pipelines/common/CompressStringify/BuildZstd.cmake)
33+
add_subdirectory(io/internal/pipelines/common/CompressStringify)
3234
add_subdirectory(io/internal/pipelines/image/ConvertImage)
3335
add_subdirectory(io/internal/pipelines/image/ReadDICOM)
3436

src/io/getFileExtension.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ function getFileExtension (filePath: string): string {
66
} else if (extension.toLowerCase() === 'cbor') {
77
const index = filePath.slice(0, -5).lastIndexOf('.')
88
extension = filePath.slice((index - 1 >>> 0) + 2)
9+
} else if (extension.toLowerCase() === 'zstd') {
10+
// .iwi.cbor.zstd
11+
const index = filePath.slice(0, -10).lastIndexOf('.')
12+
extension = filePath.slice((index - 1 >>> 0) + 2)
913
} else if (extension.toLowerCase() === 'zip') {
1014
const index = filePath.slice(0, -4).lastIndexOf('.')
1115
extension = filePath.slice((index - 1 >>> 0) + 2)

src/io/internal/extensionToImageIO.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const extensionToIO = new Map([
1717

1818
['iwi', 'itkWASMImageIO'],
1919
['iwi.cbor', 'itkWASMImageIO'],
20+
['iwi.cbor.zstd', 'itkWASMZstdImageIO'],
2021

2122
['lsm', 'itkLSMImageIO'],
2223

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
include(FetchContent)
2+
option(ZSTD_BUILD_CONTRIB "BUILD_CONTRIB" OFF)
3+
option(ZSTD_BUILD_PROGRAMS "BUILD_PROGRAMS" OFF)
4+
option(ZSTD_BUILD_SHARED "BUILD_SHARED" OFF)
5+
option(ZSTD_BUILD_STATIC "BUILD_STATIC" ON)
6+
option(ZSTD_BUILD_TESTS "BUILD_TESTS" OFF)
7+
option(ZSTD_BUILD_LEGACY_SUPPORT "BUILD_LEGACY_SUPPORT" OFF)
8+
option(ZSTD_MULTITHREAD_SUPPORT "BUILD_MULTITHREAD_SUPPORT" OFF)
9+
option(ZSTD_BUILD_PROGRAMS_LINK_SHARED "BUILD_PROGRAMS_LINK_SHARED" OFF)
10+
option(ZSTD_BUILD_LZ4 "BUILD_LZ4" OFF)
11+
option(ZSTD_BUILD_LZMA "BUILD_LZMA" OFF)
12+
option(ZSTD_BUILD_ZLIB "BUILD_ZLIB" OFF)
13+
set(zstd_GIT_REPOSITORY "https://github.com/facebook/zstd.git")
14+
# v1.5.2
15+
set(zstd_GIT_TAG c9c7be85f49f45a581ec00c309afda5c62ba9ef2)
16+
FetchContent_Declare(
17+
zstd_lib
18+
GIT_REPOSITORY ${zstd_GIT_REPOSITORY}
19+
GIT_TAG ${zstd_GIT_TAG}
20+
)
21+
22+
FetchContent_MakeAvailable(zstd_lib)
23+
set(zstd_lib_INCLUDE_DIR "${zstd_lib_SOURCE_DIR}/lib")
24+
include_directories(${zstd_lib_INCLUDE_DIR})
25+
add_subdirectory("${zstd_lib_SOURCE_DIR}/build/cmake" "${zstd_lib_BINARY_DIR}")
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(CompressStringify)
3+
4+
set(CMAKE_CXX_STANDARD 17)
5+
6+
include(FetchContent)
7+
if (NOT TARGET libzstd_static)
8+
include(${CMAKE_CURRENT_SOURCE_DIR}/BuildZstd.cmake)
9+
endif()
10+
11+
set(cpp_base64_GIT_REPOSITORY "https://github.com/thewtex/cpp-base64.git")
12+
set(cpp_base64_GET_TAG 9144cd53be930b37235ae552a92b5d2aa51e9325)
13+
FetchContent_Declare(
14+
cpp_base64
15+
GIT_REPOSITORY ${cpp_base64_GIT_REPOSITORY}
16+
GIT_TAG ${cpp_base64_GIT_TAG}
17+
)
18+
19+
FetchContent_MakeAvailable(cpp_base64)
20+
21+
find_package(ITK REQUIRED COMPONENTS WebAssemblyInterface)
22+
include(${ITK_USE_FILE})
23+
24+
add_executable(CompressStringify CompressStringify.cxx)
25+
target_include_directories(CompressStringify PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${zstd_lib_INCLUDE_DIR})
26+
target_link_libraries(CompressStringify PUBLIC libzstd_static cpp-base64 ${ITK_LIBRARIES})
27+
28+
add_executable(ParseStringDecompress ParseStringDecompress.cxx)
29+
target_include_directories(ParseStringDecompress PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${zstd_lib_INCLUDE_DIR})
30+
target_link_libraries(ParseStringDecompress PUBLIC libzstd_static cpp-base64 ${ITK_LIBRARIES})
31+
32+
if (DEFINED WebAssemblyInterface_SOURCE_DIR)
33+
foreach(target CompressStringify CompressStringify.umd ParseStringDecompress ParseStringDecompress.umd)
34+
itk_module_target_label(${target})
35+
itk_module_target_export(${target})
36+
itk_module_target_install(${target})
37+
set_property(TARGET ${target}
38+
PROPERTY RUNTIME_OUTPUT_DIRECTORY
39+
${WebAssemblyInterface_BINARY_DIR}/image-io
40+
)
41+
endforeach()
42+
endif()
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*=========================================================================
2+
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
#include <fstream>
20+
#include <string>
21+
#include <iostream>
22+
#include <sstream>
23+
#include <vector>
24+
#include <iterator>
25+
26+
#include "zstd.h"
27+
#include "cpp-base64/base64.h"
28+
29+
#include "itkPipeline.h"
30+
#include "itkInputBinaryStream.h"
31+
#include "itkOutputTextStream.h"
32+
#include "itkOutputBinaryStream.h"
33+
34+
int compress(itk::wasm::Pipeline & pipeline, itk::wasm::InputBinaryStream & inputBinaryStream, int compressionLevel)
35+
{
36+
itk::wasm::OutputBinaryStream outputBinaryStream;
37+
pipeline.add_option("Output", outputBinaryStream, "Output compressed binary");
38+
39+
ITK_WASM_PARSE(pipeline);
40+
41+
std::string inputBinary;
42+
inputBinary.assign( (std::istreambuf_iterator<char>(inputBinaryStream.Get()) ),
43+
(std::istreambuf_iterator<char>()) );
44+
45+
46+
const size_t compressedBufferSize = ZSTD_compressBound(inputBinary.size());
47+
std::vector<char> compressedBinary(compressedBufferSize);
48+
49+
const size_t compressedSize = ZSTD_compress(compressedBinary.data(), compressedBufferSize, inputBinary.data(), inputBinary.size(), compressionLevel);
50+
compressedBinary.resize(compressedSize);
51+
52+
std::ostream_iterator<char> oIt(outputBinaryStream.Get());
53+
std::copy(compressedBinary.begin(), compressedBinary.end(), oIt);
54+
55+
return EXIT_SUCCESS;
56+
}
57+
58+
int compressStringify(itk::wasm::Pipeline & pipeline, itk::wasm::InputBinaryStream & inputBinaryStream, int compressionLevel, const std::string & dataURLPrefix)
59+
{
60+
itk::wasm::OutputTextStream outputTextStream;
61+
pipeline.add_option("Output", outputTextStream, "Output dataURL+base64 compressed binary");
62+
63+
ITK_WASM_PARSE(pipeline);
64+
65+
std::string inputBinary;
66+
inputBinary.assign( (std::istreambuf_iterator<char>(inputBinaryStream.Get()) ),
67+
(std::istreambuf_iterator<char>()) );
68+
69+
70+
const size_t compressedBufferSize = ZSTD_compressBound(inputBinary.size());
71+
std::string compressedBinary;
72+
compressedBinary.resize(compressedBufferSize);
73+
74+
const size_t compressedSize = ZSTD_compress(compressedBinary.data(), compressedBufferSize, inputBinary.data(), inputBinary.size(), compressionLevel);
75+
compressedBinary.resize(compressedSize);
76+
77+
// Do we want/need this?
78+
constexpr bool urlFriendly = false;
79+
auto outputText = base64_encode(compressedBinary, urlFriendly);
80+
81+
outputTextStream.Get() << dataURLPrefix;
82+
outputTextStream.Get() << outputText;
83+
84+
return EXIT_SUCCESS;
85+
}
86+
87+
int main(int argc, char * argv[])
88+
{
89+
itk::wasm::Pipeline pipeline("Given a binary, compress optionally base64 encode", argc, argv);
90+
91+
itk::wasm::InputBinaryStream inputBinaryStream;
92+
pipeline.add_option("InputBinary", inputBinaryStream, "Input binary");
93+
94+
bool stringify = false;
95+
pipeline.add_flag("-s,--stringify", stringify, "Stringify the output");
96+
97+
int compressionLevel = 3;
98+
pipeline.add_option("-c,--compression-level", compressionLevel, "Compression level, typically 1-9");
99+
100+
std::string dataURLPrefix("data:application/iwi+cbor+zstd;base64,");
101+
pipeline.add_option("-p,--data-url-prefix", dataURLPrefix, "dataURL prefix");
102+
103+
ITK_WASM_PRE_PARSE(pipeline);
104+
105+
if(stringify)
106+
{
107+
return compressStringify(pipeline, inputBinaryStream, compressionLevel, dataURLPrefix);
108+
}
109+
return compress(pipeline, inputBinaryStream, compressionLevel);
110+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
#include <fstream>
20+
#include <string>
21+
#include <iostream>
22+
#include <sstream>
23+
#include <vector>
24+
#include <iterator>
25+
26+
#include "zstd.h"
27+
#include "cpp-base64/base64.h"
28+
29+
#include "itkPipeline.h"
30+
#include "itkInputBinaryStream.h"
31+
#include "itkInputTextStream.h"
32+
#include "itkOutputBinaryStream.h"
33+
34+
int decompress(itk::wasm::Pipeline & pipeline)
35+
{
36+
itk::wasm::InputBinaryStream inputBinaryStream;
37+
pipeline.add_option("Input", inputBinaryStream, "Compressed input");
38+
39+
itk::wasm::OutputBinaryStream outputBinaryStream;
40+
pipeline.add_option("Output", outputBinaryStream, "Output decompressed binary");
41+
42+
ITK_WASM_PARSE(pipeline);
43+
44+
std::string inputBinary;
45+
inputBinary.assign( (std::istreambuf_iterator<char>(inputBinaryStream.Get()) ),
46+
(std::istreambuf_iterator<char>()) );
47+
48+
49+
const size_t decompressedBufferSize = ZSTD_getFrameContentSize(inputBinary.data(), inputBinary.size());
50+
std::vector<char> decompressedBinary(decompressedBufferSize);
51+
52+
const size_t decompressedSize = ZSTD_decompress(decompressedBinary.data(), decompressedBufferSize, inputBinary.data(), inputBinary.size());
53+
decompressedBinary.resize(decompressedSize);
54+
55+
std::ostream_iterator<char> oIt(outputBinaryStream.Get());
56+
std::copy(decompressedBinary.begin(), decompressedBinary.end(), oIt);
57+
58+
return EXIT_SUCCESS;
59+
}
60+
61+
int decodeDecompress(itk::wasm::Pipeline & pipeline)
62+
{
63+
itk::wasm::InputTextStream inputTextStream;
64+
pipeline.add_option("Input", inputTextStream, "Compressed input");
65+
66+
itk::wasm::OutputBinaryStream outputBinaryStream;
67+
pipeline.add_option("Output", outputBinaryStream, "Output decompressed binary");
68+
69+
ITK_WASM_PARSE(pipeline);
70+
71+
// Skip dataURLPrefix
72+
auto inputTextIt = std::istream_iterator<char>(inputTextStream.Get());
73+
while (*inputTextIt != ',')
74+
{
75+
inputTextIt++;
76+
}
77+
inputTextIt++;
78+
79+
std::string inputText;
80+
inputText.assign( (inputTextIt),
81+
(std::istream_iterator<char>()) );
82+
83+
auto inputBinary = base64_decode(inputText);
84+
85+
const size_t decompressedBufferSize = ZSTD_getFrameContentSize(inputBinary.data(), inputBinary.size());
86+
std::vector<char> decompressedBinary(decompressedBufferSize);
87+
88+
const size_t decompressedSize = ZSTD_decompress(decompressedBinary.data(), decompressedBufferSize, inputBinary.data(), inputBinary.size());
89+
decompressedBinary.resize(decompressedSize);
90+
91+
std::ostream_iterator<char> oIt(outputBinaryStream.Get());
92+
std::copy(decompressedBinary.begin(), decompressedBinary.end(), oIt);
93+
94+
return EXIT_SUCCESS;
95+
}
96+
97+
int main(int argc, char * argv[])
98+
{
99+
itk::wasm::Pipeline pipeline("Given a binary or string produced with CompressedStringify, decompress optionally base64 deencode", argc, argv);
100+
101+
bool parseString = false;
102+
pipeline.add_flag("-s,--parse-string", parseString, "Parse the input string before decompression");
103+
104+
ITK_WASM_PRE_PARSE(pipeline);
105+
106+
if(parseString)
107+
{
108+
return decodeDecompress(pipeline);
109+
}
110+
return decompress(pipeline);
111+
}
112+

0 commit comments

Comments
 (0)