diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 8ec9ffdc..240fcc0b 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -26,24 +26,29 @@ jobs: - name: Setup dependencies shell: bash run: | - sudo apt-get update - sudo apt-get install -y curl cmake build-essential libopencv-dev zlib1g-dev - sudo apt-get clean - sudo rm -rf /var/lib/apt/lists/* - - mkdir ${HOME}/halide - curl -L https://github.com/halide/Halide/releases/download/v16.0.0/Halide-16.0.0-x86-64-linux-1e963ff817ef0968cc25d811a25a7350c8953ee6.tar.gz | tar zx -C ${HOME}/halide --strip-components 1 - find ${HOME}/halide -type d | xargs chmod 755 - sudo cp -r ${HOME}/halide/* /usr/ - - mkdir ${HOME}/onnxruntime - curl -L https://github.com/microsoft/onnxruntime/releases/download/v1.16.3/onnxruntime-linux-x64-1.16.3.tgz | tar zx -C ${HOME}/onnxruntime --strip-components 1 - find ${HOME}/onnxruntime -type d | xargs chmod 755 - sudo cp -r ${HOME}/onnxruntime/* /usr/ + sudo apt-get update + sudo apt-get install -y curl cmake build-essential libopencv-dev + sudo apt-get clean + sudo rm -rf /var/lib/apt/lists/* + + # Halide + mkdir ${HOME}/halide + curl -L https://github.com/halide/Halide/releases/download/v16.0.0/Halide-16.0.0-x86-64-linux-1e963ff817ef0968cc25d811a25a7350c8953ee6.tar.gz | tar zx -C ${HOME}/halide --strip-components 1 + find ${HOME}/halide -type d | xargs chmod 755 + sudo cp -r ${HOME}/halide/* /usr/ + + # ONNXRuntime + mkdir ${HOME}/onnxruntime + curl -L https://github.com/microsoft/onnxruntime/releases/download/v1.16.3/onnxruntime-linux-x64-1.16.3.tgz | tar zx -C ${HOME}/onnxruntime --strip-components 1 + find ${HOME}/onnxruntime -type d | xargs chmod 755 + sudo cp -r ${HOME}/onnxruntime/* /usr/ + + # zlib, libjpeg, libpng + vcpkg install zlib libjpeg-turbo libpng - name: Configure shell: bash - run: cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -D ION_BUILD_TEST=ON -D ION_BUILD_EXAMPLE=ON $GITHUB_WORKSPACE + run: cmake -D CMAKE_TOOLCHAIN_FILE=${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake -DCMAKE_BUILD_TYPE=$BUILD_TYPE -D ION_BUILD_TEST=ON -D ION_BUILD_EXAMPLE=ON $GITHUB_WORKSPACE - name: Build shell: bash diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f2eaa88..02edeaf2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,18 +19,22 @@ jobs: run: | # Packages sudo apt-get update - sudo apt-get install -y doxygen curl build-essential zlib1g-dev + sudo apt-get install -y doxygen curl build-essential sudo apt-get clean sudo rm -rf /var/lib/apt/lists/* - name: Download Halide package run: | - mkdir ${HOME}/Halide - curl -L https://github.com/halide/Halide/releases/download/v16.0.0/Halide-16.0.0-x86-64-linux-1e963ff817ef0968cc25d811a25a7350c8953ee6.tar.gz | tar zx -C ${HOME}/Halide --strip-components 1 - find ${HOME}/Halide -type d | xargs chmod 755 + # Halide + mkdir ${HOME}/Halide + curl -L https://github.com/halide/Halide/releases/download/v16.0.0/Halide-16.0.0-x86-64-linux-1e963ff817ef0968cc25d811a25a7350c8953ee6.tar.gz | tar zx -C ${HOME}/Halide --strip-components 1 + find ${HOME}/Halide -type d | xargs chmod 755 + + # zlib, libjpeg, libpng + vcpkg install zlib libjpeg-turbo libpng - name: Configure - run: cmake -D CMAKE_BUILD_TYPE=Release -D ION_BUILD_TEST=OFF -D ION_BUILD_EXAMPLE=OFF -D ION_BUNDLE_HALIDE=ON -D HalideHelpers_DIR=${HOME}/Halide/lib/cmake/HalideHelpers -D Halide_DIR=${HOME}/Halide/lib/cmake/Halide $GITHUB_WORKSPACE + run: cmake -D CMAKE_TOOLCHAIN_FILE=${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake -D CMAKE_BUILD_TYPE=Release -D ION_BUILD_TEST=OFF -D ION_BUILD_EXAMPLE=OFF -D ION_BUNDLE_HALIDE=ON -D HalideHelpers_DIR=${HOME}/Halide/lib/cmake/HalideHelpers -D Halide_DIR=${HOME}/Halide/lib/cmake/Halide $GITHUB_WORKSPACE - name: Build run: cmake --build . --config Release --target package @@ -65,9 +69,12 @@ jobs: rm Halide.zip mv Halide*/* ${HOME}/Halide + # zlib, libjpeg, libpng + vcpkg install zlib:x64-windows-static libjpeg-turbo:x64-windows-static libpng:x64-windows-static + - name: Configure shell: bash - run: cmake -G "Visual Studio 16 2019" -A x64 -D ION_BUILD_DOC=OFF -D ION_BUILD_TEST=OFF -D ION_BUILD_EXAMPLE=OFF -D ION_BUNDLE_HALIDE=ON -D HalideHelpers_DIR=${HOME}/Halide/lib/cmake/HalideHelpers -D Halide_DIR=${HOME}/Halide/lib/cmake/Halide $GITHUB_WORKSPACE + run: cmake -G "Visual Studio 16 2019" -A x64 -D VCPKG_TARGET_TRIPLET=x64-windows-static -D CMAKE_TOOLCHAIN_FILE=${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake -D ION_BUILD_DOC=OFF -D ION_BUILD_TEST=OFF -D ION_BUILD_EXAMPLE=OFF -D ION_BUNDLE_HALIDE=ON -D HalideHelpers_DIR=${HOME}/Halide/lib/cmake/HalideHelpers -D Halide_DIR=${HOME}/Halide/lib/cmake/Halide $GITHUB_WORKSPACE - name: Build shell: bash diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 12605e7c..c55a7a05 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -36,9 +36,12 @@ jobs: rm Halide.zip mv Halide*/* ${HOME}/Halide + # zlib, libjpeg, libpng + vcpkg install zlib:x64-windows-static libjpeg-turbo:x64-windows-static libpng:x64-windows-static + - name: Configure shell: bash - run: cmake -G "Visual Studio 16 2019" -A x64 -D ION_BUILD_TEST=ON -D ION_BUILD_EXAMPLE=ON -D HalideHelpers_DIR=${HOME}/Halide/lib/cmake/HalideHelpers -D Halide_DIR=${HOME}/Halide/lib/cmake/Halide $GITHUB_WORKSPACE + run: cmake -G "Visual Studio 16 2019" -A x64 -D VCPKG_TARGET_TRIPLET=x64-windows-static -D CMAKE_TOOLCHAIN_FILE=${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake -D ION_BUILD_TEST=ON -D ION_BUILD_EXAMPLE=ON -D HalideHelpers_DIR=${HOME}/Halide/lib/cmake/HalideHelpers -D Halide_DIR=${HOME}/Halide/lib/cmake/Halide $GITHUB_WORKSPACE - name: Build shell: bash diff --git a/CMakeLists.txt b/CMakeLists.txt index 899952dd..acf9f910 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ project(ion-kit LANGUAGES C CXX) option(ION_BUILD_DOC "Build documents." OFF) option(ION_BUILD_TEST "Enable to build tests" ON) option(ION_BUILD_EXAMPLE "Enable to build examples" ON) -option(ION_BUNDLE_HALIDE "Enable to bundle Halide binary into package" OFF) option(ION_ENABLE_HALIDE_FPGA_BACKEND "Enable to Halide FPGA backend" OFF) # @@ -146,32 +145,30 @@ install(FILES DESTINATION license) # Halide -if (ION_BUNDLE_HALIDE) - set(HALIDE_ROOT ${Halide_DIR}/../../..) - - file(GLOB HALIDE_HEADERS ${HALIDE_ROOT}/include/Halide*.h ${HALIDE_ROOT}/include/wasm*.h) - install(FILES ${HALIDE_HEADERS} DESTINATION include) - - file(GLOB HALIDE_BINS - ${HALIDE_ROOT}/bin/featurization_to_sample* - ${HALIDE_ROOT}/bin/get_host_target* - ${HALIDE_ROOT}/bin/retrain_cost_model* - ${HALIDE_ROOT}/bin/weightsdir_to_weightsfile*) - install(FILES ${HALIDE_BINS} DESTINATION bin) - - install(DIRECTORY ${HALIDE_ROOT}/lib/cmake/Halide DESTINATION lib/cmake/) - if (EXISTS ${HALIDE_ROOT}/lib/cmake/HalideHelpers) - install(DIRECTORY ${HALIDE_ROOT}/lib/cmake/HalideHelpers DESTINATION lib/cmake/) - endif() - if (UNIX) - file(GLOB HALIDE_LIBRARIES ${HALIDE_ROOT}/lib/libHalide*) - install(FILES ${HALIDE_LIBRARIES} DESTINATION lib) - file(GLOB HALIDE_LIBRARIES ${HALIDE_ROOT}/lib/libautoschedule*) - install(FILES ${HALIDE_LIBRARIES} DESTINATION lib) - else() - install(FILES ${HALIDE_ROOT}/bin/Release/Halide.dll DESTINATION bin) - install(FILES ${HALIDE_ROOT}/lib/Release/Halide.lib DESTINATION lib) - endif() +set(HALIDE_ROOT ${Halide_DIR}/../../..) + +file(GLOB HALIDE_HEADERS ${HALIDE_ROOT}/include/Halide*.h ${HALIDE_ROOT}/include/wasm*.h) +install(FILES ${HALIDE_HEADERS} DESTINATION include) + +file(GLOB HALIDE_BINS + ${HALIDE_ROOT}/bin/featurization_to_sample* + ${HALIDE_ROOT}/bin/get_host_target* + ${HALIDE_ROOT}/bin/retrain_cost_model* + ${HALIDE_ROOT}/bin/weightsdir_to_weightsfile*) +install(FILES ${HALIDE_BINS} DESTINATION bin) + +install(DIRECTORY ${HALIDE_ROOT}/lib/cmake/Halide DESTINATION lib/cmake/) +if (EXISTS ${HALIDE_ROOT}/lib/cmake/HalideHelpers) + install(DIRECTORY ${HALIDE_ROOT}/lib/cmake/HalideHelpers DESTINATION lib/cmake/) +endif() +if (UNIX) + file(GLOB HALIDE_LIBRARIES ${HALIDE_ROOT}/lib/libHalide*) + install(FILES ${HALIDE_LIBRARIES} DESTINATION lib) + file(GLOB HALIDE_LIBRARIES ${HALIDE_ROOT}/lib/libautoschedule*) + install(FILES ${HALIDE_LIBRARIES} DESTINATION lib) +else() + install(FILES ${HALIDE_ROOT}/bin/Release/Halide.dll DESTINATION bin) + install(FILES ${HALIDE_ROOT}/lib/Release/Halide.lib DESTINATION lib) endif() # diff --git a/README.md b/README.md index f3100e5f..81c4f180 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ The pipeline can be ran just in time or compiled into binary object. ## Depedencies * [Halide (v16.0.0)](https://github.com/halide/Halide/releases/tag/v16.0.0) +* [libjpeg](https://libjpeg-turbo.org/) +* [libpng](http://www.libpng.org/) +* [zlib](https://www.zlib.net/) ## Quick start diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 51d9b9a2..8b5eec9d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -12,14 +12,14 @@ if(${ION_BB_BUILD_base}) endif() endif() -if(${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_sgm}) +if(${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_sgm} AND OpenCV_FOUND) ion_aot_executable(sgm SRCS_COMPILE sgm_compile.cc SRCS_RUN sgm_run.cc INCS ${OPenCV_INCLUDE_DIR} LIBS ion-bb ${OpenCV_LIBRARIES} TARGET_STRING "host-profile") if(${CUDA_FOUND}) ion_aot_executable(sgm_gpu SRCS_COMPILE sgm_compile.cc SRCS_RUN sgm_run.cc INCS ${OPenCV_INCLUDE_DIR} LIBS ion-bb ${OpenCV_LIBRARIES} TARGET_STRING "host-cuda-cuda_capability_50-profile") endif() endif() -if(${ION_BB_BUILD_base} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_dnn}) +if(${ION_BB_BUILD_base} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_dnn} AND OpenCV_FOUND) ion_aot_executable(dnn SRCS_COMPILE dnn_compile.cc SRCS_RUN dnn_run.cc INCS ${OpenCV_INCLUDE_DIR} LIBS ion-bb ${OpenCV_LIBRARIES} TARGET_STRING "host-profile") if(${CUDA_FOUND}) ion_aot_executable(dnn_gpu SRCS_COMPILE dnn_compile.cc SRCS_RUN dnn_run.cc INCS ${OpenCV_INCLUDE_DIR} LIBS ion-bb ${OpenCV_LIBRARIES} TARGET_STRING "host-cuda-cuda_capability_50-profile") @@ -27,7 +27,7 @@ if(${ION_BB_BUILD_base} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_dnn}) ion_jit_executable(dnn SRCS dnn.cc INCS ${OpenCV_INCLUDE_DIRS} LIBS ${OpenCV_LIBRARIES}) endif() -if(${ION_BB_BUILD_base} AND ${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_image-io}) +if(${ION_BB_BUILD_base} AND ${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_image-io} AND OpenCV_FOUND) ion_aot_executable(demo SRCS_COMPILE demo_compile.cc SRCS_RUN demo_run.cc INCS ${OpenCV_INCLUDE_DIR} LIBS ion-bb ${OpenCV_LIBRARIES} TARGET_STRING "host-profile") if(${CUDA_FOUND}) ion_aot_executable(demo_gpu SRCS_COMPILE demo_compile.cc SRCS_RUN demo_run.cc INCS ${OpenCV_INCLUDE_DIR} LIBS ion-bb ${OpenCV_LIBRARIES} TARGET_STRING "host-cuda-cuda_capability_50-profile") @@ -35,15 +35,15 @@ if(${ION_BB_BUILD_base} AND ${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_ ion_jit_executable(demo SRCS demo.cc INCS ${OpenCV_INCLUDE_DIR} LIBS ion-bb ${OpenCV_LIBRARIES}) endif() -if(${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_image-io}) +if(${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_image-io} AND OpenCV_FOUND) ion_jit_executable(isp SRCS isp.cc INCS ${OpenCV_INCLUDE_DIR} LIBS ${OpenCV_LIBRARIES}) endif() -if(${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_sgm}) +if(${ION_BB_BUILD_image-processing} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_sgm} AND OpenCV_FOUND) ion_jit_executable(isp_and_sgm SRCS isp_and_sgm.cc INCS ${OpenCV_INCLUDE_DIR} LIBS ${OpenCV_LIBRARIES}) endif() -if(${ION_BB_BUILD_base} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_image-processing}) +if(${ION_BB_BUILD_base} AND ${ION_BB_BUILD_image-io} AND ${ION_BB_BUILD_image-processing} AND OpenCV_FOUND) ion_jit_executable(imx219_isp_display_jit SRCS imx219_isp_display.cc) endif() @@ -55,7 +55,7 @@ endif() # ion_jit_executable(u3v_jit SRCS u3v.cc) #endif() -if(${ION_BB_BUILD_image-io} AND UNIX AND NOT APPLE) +if(${ION_BB_BUILD_image-io} AND OpenCV_FOUND AND UNIX AND NOT APPLE) ion_jit_executable(v4l2_jit SRCS v4l2.cc) ion_jit_executable(realsense_jit SRCS realsense.cc INCS ${OpenCV_INCLUDE_DIR} LIBS ${OpenCV_LIBRARIES}) endif() diff --git a/example/demo_compile.cc b/example/demo_compile.cc index b55c3ed0..51829b07 100644 --- a/example/demo_compile.cc +++ b/example/demo_compile.cc @@ -57,7 +57,7 @@ int main(int argc, char *argv[]) { // IMX219 Node imx = b.add("image_io_imx219") .set_param(Param{"index", std::to_string(i)}, - Param{"url", "http://ion-kit.s3.us-west-2.amazonaws.com/images/pedestrian.jpg"}); + Param{"url", "http://ion-kit.s3.us-west-2.amazonaws.com/images/pedestrian.png"}); // ISP Node downscale = b.add("image_processing_bayer_downscale_uint16") diff --git a/example/dnn.cc b/example/dnn.cc index e99e0a5c..630a4eb8 100644 --- a/example/dnn.cc +++ b/example/dnn.cc @@ -18,7 +18,7 @@ int main(int argc, char *argv[]) { b.with_bb_module("ion-bb"); Node n; - n = b.add("image_io_color_data_loader").set_param(Param("url", "http://ion-kit.s3.us-west-2.amazonaws.com/images/pedestrian.jpg"), Param("width", width), Param("height", height)); + n = b.add("image_io_color_data_loader").set_param(Param("url", "http://ion-kit.s3.us-west-2.amazonaws.com/images/pedestrian.png"), Param("width", width), Param("height", height)); n = b.add("base_normalize_3d_uint8")(n["output"]); n = b.add("base_reorder_buffer_3d_float")(n["output"]).set_param(Param("dim0", 2), Param("dim1", 0), Param("dim2", 1)); // CHW -> HWC n = b.add("dnn_object_detection")(n["output"]); diff --git a/example/dnn_compile.cc b/example/dnn_compile.cc index 50a57e62..cab344d3 100644 --- a/example/dnn_compile.cc +++ b/example/dnn_compile.cc @@ -13,7 +13,7 @@ int main(int argc, char *argv[]) { b.with_bb_module("ion-bb"); Node n; - n = b.add("image_io_color_data_loader").set_param(Param{"url", "http://ion-kit.s3.us-west-2.amazonaws.com/images/pedestrian.jpg"}, Param{"width", std::to_string(input_width)}, Param{"height", std::to_string(input_height)}); + n = b.add("image_io_color_data_loader").set_param(Param{"url", "http://ion-kit.s3.us-west-2.amazonaws.com/images/pedestrian.png"}, Param{"width", std::to_string(input_width)}, Param{"height", std::to_string(input_height)}); n = b.add("base_normalize_3d_uint8")(n["output"]); n = b.add("base_reorder_buffer_3d_float")(n["output"]).set_param(Param{"dim0", "2"}, Param{"dim1", "0"}, Param{"dim2", "1"}); // CHW -> HWC n = b.add("dnn_object_detection")(n["output"]); diff --git a/example/opencv_bb.cc b/example/opencv_bb.cc new file mode 100644 index 00000000..121c97ac --- /dev/null +++ b/example/opencv_bb.cc @@ -0,0 +1,47 @@ +#include + +using namespace ion; + +int main(int argc, char *argv[]) { + try { + const int width = 640; + const int height = 480; + + Builder b; + b.set_target(Halide::get_target_from_environment()); + b.with_bb_module("ion-bb"); + + Port in{"input", Halide::type_of(), 3}; + + Node n; + n = b.add("opencv_median_blur")(in); + n = b.add("opencv_display")(n["output"]); + + Halide::Buffer in_buf(3, width, height); + for (int y=0; y r = Halide::Buffer::make_scalar(); + + PortMap pm; + pm.set(in, in_buf); + pm.set(n["output"], r); + + for (int i=0; i<1000; ++i) { + b.run(pm); + } + + } catch (const std::exception &e) { + std::cerr << e.what() << std::endl; + return -1; + } catch (...) { + return -1; + } + + return 0; +} diff --git a/src/bb/CMakeLists.txt b/src/bb/CMakeLists.txt index b1112f20..099f60a5 100644 --- a/src/bb/CMakeLists.txt +++ b/src/bb/CMakeLists.txt @@ -28,6 +28,7 @@ endforeach() add_library(ion-bb SHARED bb.cc) target_include_directories(ion-bb PUBLIC ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR} ${ION_BB_INCLUDE_DIRS}) + target_link_libraries(ion-bb PUBLIC ion-core ${ION_BB_LIBRARIES}) if(UNIX) target_compile_options(ion-bb PUBLIC -fno-rtti) # For Halide::Generator diff --git a/src/bb/image-io/bb.h b/src/bb/image-io/bb.h index 68dad109..8381fe53 100644 --- a/src/bb/image-io/bb.h +++ b/src/bb/image-io/bb.h @@ -689,9 +689,9 @@ class ImageSaver : public ion::BuildingBlock { Func in(static_cast(gc_prefix) + "input"); Var x, y, c; in(c, x, y) = mux(c, - {input(x, y, 2), + {input(x, y, 0), input(x, y, 1), - input(x, y, 0)}); + input(x, y, 2)}); in.compute_root(); if (get_target().has_gpu_feature()) { Var xo, yo, xi, yi; @@ -729,7 +729,7 @@ class U3VCamera1 : public ion::BuildingBlock> { Func camera1("u3v_camera1"); { Buffer id_buf = this->get_id(); - + const std::string gain_key(gain_key_ptr); Buffer gain_key_buf(static_cast(gain_key.size() + 1)); gain_key_buf.fill(0); @@ -749,14 +749,14 @@ class U3VCamera1 : public ion::BuildingBlock> { camera1.compute_root(); output0(_) = camera1(_); } - + Func camera1_frame_count; { Buffer id_buf = this->get_id(); camera1_frame_count.define_extern("ion_bb_image_io_u3v_camera1_frame_count",{camera1, 1, static_cast(frame_sync), static_cast(realtime_diaplay_mode), id_buf}, type_of(), 1); camera1_frame_count.compute_root(); frame_count(_) = camera1_frame_count(_); - } + } this->register_disposer("u3v_dispose"); @@ -812,7 +812,7 @@ class U3VCamera2 : public ion::BuildingBlock> { output0(_) = camera2(_)[0]; output1(_) = camera2(_)[1]; } - + Func camera2_frame_count;{ Buffer id_buf = this->get_id(); camera2_frame_count.define_extern("ion_bb_image_io_u3v_camera2_frame_count", { camera2, 2, static_cast(frame_sync), static_cast(realtime_diaplay_mode), id_buf}, type_of(), 1); @@ -935,7 +935,7 @@ class U3VCameraN : public ion::BuildingBlock> { } this->register_disposer("u3v_dispose"); } - + }; using U3VCameraN_U8x3 = U3VCameraN; diff --git a/src/bb/image-io/config.cmake b/src/bb/image-io/config.cmake index be554a59..c5c368ba 100644 --- a/src/bb/image-io/config.cmake +++ b/src/bb/image-io/config.cmake @@ -1,37 +1,32 @@ -find_package(OpenCV 4 QUIET) +find_package(JPEG) +find_package(PNG) +find_package(ZLIB) -if (${OpenCV_FOUND}) +if(JPEG_FOUND AND PNG_FOUND AND ZLIB_FOUND) set(ION_BB_BUILD_image-io TRUE) - if (UNIX) - add_compile_options(-Wno-format-security) - endif() + set(INCLUDE_DIRS ${JPEG_INCLUDE_DIR} ${PNG_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR}) + set(LIBRARIES ${JPEG_LIBRARY} ${PNG_LIBRARY} ${ZLIB_LIBRARY}) + + find_package(OpenCV 4 QUIET) + if (OpenCV_FOUND) + add_compile_definitions(HAS_OPENCV) - set(INCLUDE_DIRS - ${OpenCV_INCLUDE_DIRS}) + if (UNIX) + add_compile_options(-Wno-format-security) + endif() - set(LINK_DIRS - ${OpenCV_DIR}/lib) + list(APPEND INCLUDE_DIRS ${OpenCV_INCLUDE_DIRS}) + list(APPEND LINK_DIRS ${OpenCV_DIR}/lib) + list(APPEND LIBRARIES ${OpenCV_LIBRARIES}) - if (APPLE) - set(LIBRARIES - dl - pthread - m - ${OpenCV_LIBRARIES}) - list(APPEND RUNTIME_ENVS LD_LIBRARY_PATH ${OpenCV_DIR}/lib) - elseif (UNIX) - set(LIBRARIES - rt - dl - pthread - m - ${OpenCV_LIBRARIES}) - list(APPEND RUNTIME_ENVS LD_LIBRARY_PATH ${OpenCV_DIR}/lib) - else() - set(LIBRARIES - ${OpenCV_LIBRARIES}) - list(APPEND RUNTIME_ENVS PATH ${OpenCV_DIR}/x64/vc15/bin) + if (APPLE) + list(APPEND RUNTIME_ENVS DYLD_LIBRARY_PATH ${OpenCV_DIR}/lib) + elseif (UNIX) + list(APPEND RUNTIME_ENVS LD_LIBRARY_PATH ${OpenCV_DIR}/lib) + else() + list(APPEND RUNTIME_ENVS PATH ${OpenCV_DIR}/x64/vc15/bin) + endif() endif() else() set(ION_BB_BUILD_image-io FALSE) diff --git a/src/bb/image-io/halide_image_io.h b/src/bb/image-io/halide_image_io.h new file mode 100644 index 00000000..07ea5c6a --- /dev/null +++ b/src/bb/image-io/halide_image_io.h @@ -0,0 +1,2396 @@ +// This simple IO library works the Halide::Buffer type or any +// other image type with the same API. + +#ifndef HALIDE_IMAGE_IO_H +#define HALIDE_IMAGE_IO_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef HALIDE_NO_PNG +#include "png.h" +#endif + +#ifndef HALIDE_NO_JPEG +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#endif +#include "jpeglib.h" +#endif + +#include "HalideRuntime.h" // for halide_type_t + +namespace Halide { +namespace Tools { + +struct FormatInfo { + halide_type_t type; + int dimensions; + + bool operator<(const FormatInfo &other) const { + if (type.code < other.type.code) { + return true; + } else if (type.code > other.type.code) { + return false; + } + if (type.bits < other.type.bits) { + return true; + } else if (type.bits > other.type.bits) { + return false; + } + if (type.lanes < other.type.lanes) { + return true; + } else if (type.lanes > other.type.lanes) { + return false; + } + return (dimensions < other.dimensions); + } +}; + +namespace Internal { + +typedef bool (*CheckFunc)(bool condition, const char *msg); + +inline bool CheckFail(bool condition, const char *msg) { + if (!condition) { + fprintf(stderr, "%s\n", msg); + abort(); + } + return condition; +} + +inline bool CheckReturn(bool condition, const char *msg) { + return condition; +} + +template +To convert(const From &from); + +// Convert to bool +template<> +inline bool convert(const bool &in) { + return in; +} +template<> +inline bool convert(const uint8_t &in) { + return in != 0; +} +template<> +inline bool convert(const uint16_t &in) { + return in != 0; +} +template<> +inline bool convert(const uint32_t &in) { + return in != 0; +} +template<> +inline bool convert(const uint64_t &in) { + return in != 0; +} +template<> +inline bool convert(const int8_t &in) { + return in != 0; +} +template<> +inline bool convert(const int16_t &in) { + return in != 0; +} +template<> +inline bool convert(const int32_t &in) { + return in != 0; +} +template<> +inline bool convert(const int64_t &in) { + return in != 0; +} +template<> +inline bool convert(const float &in) { + return in != 0; +} +template<> +inline bool convert(const double &in) { + return in != 0; +} + +// Convert to u8 +template<> +inline uint8_t convert(const bool &in) { + return in; +} +template<> +inline uint8_t convert(const uint8_t &in) { + return in; +} +template<> +inline uint8_t convert(const uint16_t &in) { + uint32_t tmp = (uint32_t)(in) + 0x80; + // Fast approximation of div-by-257: see http://research.swtch.com/divmult + return ((tmp * 255 + 255) >> 16); +} +template<> +inline uint8_t convert(const uint32_t &in) { + return (uint8_t)((((uint64_t)in) + 0x00808080) / 0x01010101); +} +// uint64 -> 8 just discards the lower 32 bits: if you were expecting more precision, well, sorry +template<> +inline uint8_t convert(const uint64_t &in) { + return convert(uint32_t(in >> 32)); +} +template<> +inline uint8_t convert(const int8_t &in) { + return convert(in); +} +template<> +inline uint8_t convert(const int16_t &in) { + return convert(in); +} +template<> +inline uint8_t convert(const int32_t &in) { + return convert(in); +} +template<> +inline uint8_t convert(const int64_t &in) { + return convert(in); +} +template<> +inline uint8_t convert(const float &in) { + return (uint8_t)std::lround(in * 255.0f); +} +template<> +inline uint8_t convert(const double &in) { + return (uint8_t)std::lround(in * 255.0); +} + +// Convert to u16 +template<> +inline uint16_t convert(const bool &in) { + return in; +} +template<> +inline uint16_t convert(const uint8_t &in) { + return uint16_t(in) * 0x0101; +} +template<> +inline uint16_t convert(const uint16_t &in) { + return in; +} +template<> +inline uint16_t convert(const uint32_t &in) { + return in >> 16; +} +template<> +inline uint16_t convert(const uint64_t &in) { + return in >> 48; +} +template<> +inline uint16_t convert(const int8_t &in) { + return convert(in); +} +template<> +inline uint16_t convert(const int16_t &in) { + return convert(in); +} +template<> +inline uint16_t convert(const int32_t &in) { + return convert(in); +} +template<> +inline uint16_t convert(const int64_t &in) { + return convert(in); +} +template<> +inline uint16_t convert(const float &in) { + return (uint16_t)std::lround(in * 65535.0f); +} +template<> +inline uint16_t convert(const double &in) { + return (uint16_t)std::lround(in * 65535.0); +} + +// Convert to u32 +template<> +inline uint32_t convert(const bool &in) { + return in; +} +template<> +inline uint32_t convert(const uint8_t &in) { + return uint32_t(in) * 0x01010101; +} +template<> +inline uint32_t convert(const uint16_t &in) { + return uint32_t(in) * 0x00010001; +} +template<> +inline uint32_t convert(const uint32_t &in) { + return in; +} +template<> +inline uint32_t convert(const uint64_t &in) { + return (uint32_t)(in >> 32); +} +template<> +inline uint32_t convert(const int8_t &in) { + return convert(in); +} +template<> +inline uint32_t convert(const int16_t &in) { + return convert(in); +} +template<> +inline uint32_t convert(const int32_t &in) { + return convert(in); +} +template<> +inline uint32_t convert(const int64_t &in) { + return convert(in); +} +template<> +inline uint32_t convert(const float &in) { + return (uint32_t)std::llround(in * 4294967295.0); +} +template<> +inline uint32_t convert(const double &in) { + return (uint32_t)std::llround(in * 4294967295.0); +} + +// Convert to u64 +template<> +inline uint64_t convert(const bool &in) { + return in; +} +template<> +inline uint64_t convert(const uint8_t &in) { + return uint64_t(in) * 0x0101010101010101LL; +} +template<> +inline uint64_t convert(const uint16_t &in) { + return uint64_t(in) * 0x0001000100010001LL; +} +template<> +inline uint64_t convert(const uint32_t &in) { + return uint64_t(in) * 0x0000000100000001LL; +} +template<> +inline uint64_t convert(const uint64_t &in) { + return in; +} +template<> +inline uint64_t convert(const int8_t &in) { + return convert(in); +} +template<> +inline uint64_t convert(const int16_t &in) { + return convert(in); +} +template<> +inline uint64_t convert(const int32_t &in) { + return convert(in); +} +template<> +inline uint64_t convert(const int64_t &in) { + return convert(in); +} +template<> +inline uint64_t convert(const float &in) { + return convert((uint32_t)std::llround(in * 4294967295.0)); +} +template<> +inline uint64_t convert(const double &in) { + return convert((uint32_t)std::llround(in * 4294967295.0)); +} + +// Convert to i8 +template<> +inline int8_t convert(const bool &in) { + return in; +} +template<> +inline int8_t convert(const uint8_t &in) { + return convert(in); +} +template<> +inline int8_t convert(const uint16_t &in) { + return convert(in); +} +template<> +inline int8_t convert(const uint32_t &in) { + return convert(in); +} +template<> +inline int8_t convert(const uint64_t &in) { + return convert(in); +} +template<> +inline int8_t convert(const int8_t &in) { + return convert(in); +} +template<> +inline int8_t convert(const int16_t &in) { + return convert(in); +} +template<> +inline int8_t convert(const int32_t &in) { + return convert(in); +} +template<> +inline int8_t convert(const int64_t &in) { + return convert(in); +} +template<> +inline int8_t convert(const float &in) { + return convert(in); +} +template<> +inline int8_t convert(const double &in) { + return convert(in); +} + +// Convert to i16 +template<> +inline int16_t convert(const bool &in) { + return in; +} +template<> +inline int16_t convert(const uint8_t &in) { + return convert(in); +} +template<> +inline int16_t convert(const uint16_t &in) { + return convert(in); +} +template<> +inline int16_t convert(const uint32_t &in) { + return convert(in); +} +template<> +inline int16_t convert(const uint64_t &in) { + return convert(in); +} +template<> +inline int16_t convert(const int8_t &in) { + return convert(in); +} +template<> +inline int16_t convert(const int16_t &in) { + return convert(in); +} +template<> +inline int16_t convert(const int32_t &in) { + return convert(in); +} +template<> +inline int16_t convert(const int64_t &in) { + return convert(in); +} +template<> +inline int16_t convert(const float &in) { + return convert(in); +} +template<> +inline int16_t convert(const double &in) { + return convert(in); +} + +// Convert to i32 +template<> +inline int32_t convert(const bool &in) { + return in; +} +template<> +inline int32_t convert(const uint8_t &in) { + return convert(in); +} +template<> +inline int32_t convert(const uint16_t &in) { + return convert(in); +} +template<> +inline int32_t convert(const uint32_t &in) { + return convert(in); +} +template<> +inline int32_t convert(const uint64_t &in) { + return convert(in); +} +template<> +inline int32_t convert(const int8_t &in) { + return convert(in); +} +template<> +inline int32_t convert(const int16_t &in) { + return convert(in); +} +template<> +inline int32_t convert(const int32_t &in) { + return convert(in); +} +template<> +inline int32_t convert(const int64_t &in) { + return convert(in); +} +template<> +inline int32_t convert(const float &in) { + return convert(in); +} +template<> +inline int32_t convert(const double &in) { + return convert(in); +} + +// Convert to i64 +template<> +inline int64_t convert(const bool &in) { + return in; +} +template<> +inline int64_t convert(const uint8_t &in) { + return convert(in); +} +template<> +inline int64_t convert(const uint16_t &in) { + return convert(in); +} +template<> +inline int64_t convert(const uint32_t &in) { + return convert(in); +} +template<> +inline int64_t convert(const uint64_t &in) { + return convert(in); +} +template<> +inline int64_t convert(const int8_t &in) { + return convert(in); +} +template<> +inline int64_t convert(const int16_t &in) { + return convert(in); +} +template<> +inline int64_t convert(const int32_t &in) { + return convert(in); +} +template<> +inline int64_t convert(const int64_t &in) { + return convert(in); +} +template<> +inline int64_t convert(const float &in) { + return convert(in); +} +template<> +inline int64_t convert(const double &in) { + return convert(in); +} + +// Convert to f32 +template<> +inline float convert(const bool &in) { + return in; +} +template<> +inline float convert(const uint8_t &in) { + return in / 255.0f; +} +template<> +inline float convert(const uint16_t &in) { + return in / 65535.0f; +} +template<> +inline float convert(const uint32_t &in) { + return (float)(in / 4294967295.0); +} +template<> +inline float convert(const uint64_t &in) { + return convert(uint32_t(in >> 32)); +} +template<> +inline float convert(const int8_t &in) { + return convert(in); +} +template<> +inline float convert(const int16_t &in) { + return convert(in); +} +template<> +inline float convert(const int32_t &in) { + return convert(in); +} +template<> +inline float convert(const int64_t &in) { + return convert(in); +} +template<> +inline float convert(const float &in) { + return in; +} +template<> +inline float convert(const double &in) { + return (float)in; +} + +// Convert to f64 +template<> +inline double convert(const bool &in) { + return in; +} +template<> +inline double convert(const uint8_t &in) { + return in / 255.0f; +} +template<> +inline double convert(const uint16_t &in) { + return in / 65535.0f; +} +template<> +inline double convert(const uint32_t &in) { + return (double)(in / 4294967295.0); +} +template<> +inline double convert(const uint64_t &in) { + return convert(uint32_t(in >> 32)); +} +template<> +inline double convert(const int8_t &in) { + return convert(in); +} +template<> +inline double convert(const int16_t &in) { + return convert(in); +} +template<> +inline double convert(const int32_t &in) { + return convert(in); +} +template<> +inline double convert(const int64_t &in) { + return convert(in); +} +template<> +inline double convert(const float &in) { + return (double)in; +} +template<> +inline double convert(const double &in) { + return in; +} + +inline std::string to_lowercase(const std::string &s) { + std::string r = s; + std::transform(r.begin(), r.end(), r.begin(), ::tolower); + return r; +} + +inline std::string get_lowercase_extension(const std::string &path) { + size_t last_dot = path.rfind('.'); + if (last_dot == std::string::npos) { + return ""; + } + return to_lowercase(path.substr(last_dot + 1)); +} + +template +ElemType read_big_endian(const uint8_t *src); + +template<> +inline uint8_t read_big_endian(const uint8_t *src) { + return *src; +} + +template<> +inline uint16_t read_big_endian(const uint8_t *src) { + return (((uint16_t)src[0]) << 8) | ((uint16_t)src[1]); +} + +template +void write_big_endian(const ElemType &src, uint8_t *dst); + +template<> +inline void write_big_endian(const uint8_t &src, uint8_t *dst) { + *dst = src; +} + +template<> +inline void write_big_endian(const uint16_t &src, uint8_t *dst) { + dst[0] = src >> 8; + dst[1] = src & 0xff; +} + +struct FileOpener { + FileOpener(const std::string &filename, const char *mode) + : f(fopen(filename.c_str(), mode)) { + // nothing + } + + ~FileOpener() { + if (f != nullptr) { + fclose(f); + } + } + + // read a line of data, skipping lines that begin with '#" + char *read_line(char *buf, int maxlen) { + char *status; + do { + status = fgets(buf, maxlen, f); + } while (status && buf[0] == '#'); + return (status); + } + + // call read_line and to a sscanf() on it + int scan_line(const char *fmt, ...) { + char buf[1024]; + if (!read_line(buf, 1024)) { + return 0; + } + va_list args; + va_start(args, fmt); + int result = vsscanf(buf, fmt, args); + va_end(args); + return result; + } + + bool read_bytes(void *data, size_t count) { + return fread(data, 1, count, f) == count; + } + + template + bool read_array(T (&data)[N]) { + return read_bytes(&data[0], sizeof(T) * N); + } + + template + bool read_vector(std::vector *v) { + return read_bytes(v->data(), v->size() * sizeof(T)); + } + + bool write_bytes(const void *data, size_t count) { + return fwrite(data, 1, count, f) == count; + } + + template + bool write_vector(const std::vector &v) { + return write_bytes(v.data(), v.size() * sizeof(T)); + } + + template + bool write_array(const T (&data)[N]) { + return write_bytes(&data[0], sizeof(T) * N); + } + + FILE *const f; +}; + +constexpr int AnyDims = -1; + +// Read a row of ElemTypes from a byte buffer and copy them into a specific image row. +// Multibyte elements are assumed to be big-endian. +template +void read_big_endian_row(const uint8_t *src, int y, ImageType *im) { + auto im_typed = im->template as(); + const int xmin = im_typed.dim(0).min(); + const int xmax = im_typed.dim(0).max(); + if (im_typed.dimensions() > 2) { + const int cmin = im_typed.dim(2).min(); + const int cmax = im_typed.dim(2).max(); + for (int x = xmin; x <= xmax; x++) { + for (int c = cmin; c <= cmax; c++) { + im_typed(x, y, c + cmin) = read_big_endian(src); + src += sizeof(ElemType); + } + } + } else { + for (int x = xmin; x <= xmax; x++) { + im_typed(x, y) = read_big_endian(src); + src += sizeof(ElemType); + } + } +} + +// Copy a row from an image into a byte buffer. +// Multibyte elements are written in big-endian layout. +template +void write_big_endian_row(const ImageType &im, int y, uint8_t *dst) { + auto im_typed = im.template as::type, AnyDims>(); + const int xmin = im_typed.dim(0).min(); + const int xmax = im_typed.dim(0).max(); + if (im_typed.dimensions() > 2) { + const int cmin = im_typed.dim(2).min(); + const int cmax = im_typed.dim(2).max(); + for (int x = xmin; x <= xmax; x++) { + for (int c = cmin; c <= cmax; c++) { + write_big_endian(im_typed(x, y, c), dst); + dst += sizeof(ElemType); + } + } + } else { + for (int x = xmin; x <= xmax; x++) { + write_big_endian(im_typed(x, y), dst); + dst += sizeof(ElemType); + } + } +} + +#ifndef HALIDE_NO_PNG + +template +bool load_png(const std::string &filename, ImageType *im) { + static_assert(!ImageType::has_static_halide_type, ""); + + /* open file and test for it being a png */ + Internal::FileOpener f(filename, "rb"); + if (!check(f.f != nullptr, "File could not be opened for reading")) { + return false; + } + png_byte header[8]; + if (!check(f.read_array(header), "File ended before end of header")) { + return false; + } + if (!check(!png_sig_cmp(header, 0, 8), "File is not recognized as a PNG file")) { + return false; + } + + /* initialize stuff */ + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!check(png_ptr != nullptr, "png_create_read_struct failed")) { + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!check(info_ptr != nullptr, "png_create_info_struct failed")) { + return false; + } + + if (!check(!setjmp(png_jmpbuf(png_ptr)), "Error loading PNG")) { + return false; + } + + png_init_io(png_ptr, f.f); + png_set_sig_bytes(png_ptr, 8); + + png_read_info(png_ptr, info_ptr); + + const int width = png_get_image_width(png_ptr, info_ptr); + const int height = png_get_image_height(png_ptr, info_ptr); + const int channels = png_get_channels(png_ptr, info_ptr); + const int bit_depth = png_get_bit_depth(png_ptr, info_ptr); + + const halide_type_t im_type(halide_type_uint, bit_depth); + std::vector im_dimensions = {width, height}; + if (channels != 1) { + im_dimensions.push_back(channels); + } + + *im = ImageType(im_type, im_dimensions); + + png_read_update_info(png_ptr, info_ptr); + + auto copy_to_image = bit_depth == 8 ? + Internal::read_big_endian_row : + Internal::read_big_endian_row; + + std::vector row(png_get_rowbytes(png_ptr, info_ptr)); + const int ymin = im->dim(1).min(); + const int ymax = im->dim(1).max(); + for (int y = ymin; y <= ymax; ++y) { + png_read_row(png_ptr, row.data(), nullptr); + copy_to_image(row.data(), y, im); + } + + png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); + + return true; +} + +inline const std::set &query_png() { + static std::set info = { + {halide_type_t(halide_type_uint, 8), 2}, + {halide_type_t(halide_type_uint, 16), 2}, + {halide_type_t(halide_type_uint, 8), 3}, + {halide_type_t(halide_type_uint, 16), 3}}; + return info; +} + +// "im" is not const-ref because copy_to_host() is not const. +template +bool save_png(ImageType &im, const std::string &filename) { + static_assert(!ImageType::has_static_halide_type, ""); + + if (!check(im.copy_to_host() == halide_error_code_success, "copy_to_host() failed.")) { + return false; + } + + const int width = im.width(); + const int height = im.height(); + const int channels = im.channels(); + + if (!check(channels >= 1 && channels <= 4, + "Can't write PNG files that have other than 1, 2, 3, or 4 channels")) { + return false; + } + + const png_byte color_types[4] = { + PNG_COLOR_TYPE_GRAY, + PNG_COLOR_TYPE_GRAY_ALPHA, + PNG_COLOR_TYPE_RGB, + PNG_COLOR_TYPE_RGB_ALPHA}; + png_byte color_type = color_types[channels - 1]; + + // open file + Internal::FileOpener f(filename, "wb"); + if (!check(f.f != nullptr, "[write_png_file] File could not be opened for writing")) { + return false; + } + + // initialize stuff + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (!check(png_ptr != nullptr, "[write_png_file] png_create_write_struct failed")) { + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!check(info_ptr != nullptr, "[write_png_file] png_create_info_struct failed")) { + return false; + } + + if (!check(!setjmp(png_jmpbuf(png_ptr)), "Error saving PNG")) { + return false; + } + + png_init_io(png_ptr, f.f); + + const halide_type_t im_type = im.type(); + const int bit_depth = im_type.bits; + + png_set_IHDR(png_ptr, info_ptr, width, height, + bit_depth, color_type, PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png_write_info(png_ptr, info_ptr); + + auto copy_from_image = bit_depth == 8 ? + Internal::write_big_endian_row : + Internal::write_big_endian_row; + + std::vector row(png_get_rowbytes(png_ptr, info_ptr)); + const int ymin = im.dim(1).min(); + const int ymax = im.dim(1).max(); + for (int y = ymin; y <= ymax; ++y) { + copy_from_image(im, y, row.data()); + png_write_row(png_ptr, row.data()); + } + png_write_end(png_ptr, nullptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + return true; +} + +#endif // not HALIDE_NO_PNG + +template +bool read_pnm_header(Internal::FileOpener &f, const std::string &hdr_fmt, int *width, int *height, int *bit_depth) { + if (!check(f.f != nullptr, "File could not be opened for reading")) { + return false; + } + + char header[256]; + if (!check(f.scan_line("%255s", header) == 1, "Could not read header")) { + return false; + } + + if (!check(to_lowercase(hdr_fmt) == to_lowercase(header), "Unexpected file header")) { + return false; + } + + if (!check(f.scan_line("%d %d\n", width, height) == 2, "Could not read width and height")) { + return false; + } + + int maxval; + if (!check(f.scan_line("%d", &maxval) == 1, "Could not read max value")) { + return false; + } + if (maxval == 255) { + *bit_depth = 8; + } else if (maxval == 65535) { + *bit_depth = 16; + } else { + *bit_depth = 0; + return check(false, "Invalid bit depth"); + } + + return true; +} + +template +bool load_pnm(const std::string &filename, int channels, ImageType *im) { + static_assert(!ImageType::has_static_halide_type, ""); + + const char *hdr_fmt = channels == 3 ? "P6" : "P5"; + + Internal::FileOpener f(filename, "rb"); + int width, height, bit_depth; + if (!Internal::read_pnm_header(f, hdr_fmt, &width, &height, &bit_depth)) { + return false; + } + + const halide_type_t im_type(halide_type_uint, bit_depth); + std::vector im_dimensions = {width, height}; + if (channels > 1) { + im_dimensions.push_back(channels); + } + *im = ImageType(im_type, im_dimensions); + + auto copy_to_image = bit_depth == 8 ? + Internal::read_big_endian_row : + Internal::read_big_endian_row; + + std::vector row(width * channels * (bit_depth / 8)); + const int ymin = im->dim(1).min(); + const int ymax = im->dim(1).max(); + for (int y = ymin; y <= ymax; ++y) { + if (!check(f.read_vector(&row), "Could not read data")) { + return false; + } + copy_to_image(row.data(), y, im); + } + + return true; +} + +template +bool save_pnm(ImageType &im, const int channels, const std::string &filename) { + static_assert(!ImageType::has_static_halide_type, ""); + + if (!check(im.channels() == channels, "Wrong number of channels")) { + return false; + } + + if (!check(im.copy_to_host() == halide_error_code_success, "copy_to_host() failed.")) { + return false; + } + + const halide_type_t im_type = im.type(); + const int width = im.width(); + const int height = im.height(); + const int bit_depth = im_type.bits; + + Internal::FileOpener f(filename, "wb"); + if (!check(f.f != nullptr, "File could not be opened for writing")) { + return false; + } + const char *hdr_fmt = channels == 3 ? "P6" : "P5"; + fprintf(f.f, "%s\n%d %d\n%d\n", hdr_fmt, width, height, (1 << bit_depth) - 1); + + auto copy_from_image = bit_depth == 8 ? + Internal::write_big_endian_row : + Internal::write_big_endian_row; + + std::vector row(width * channels * (bit_depth / 8)); + const int ymin = im.dim(1).min(); + const int ymax = im.dim(1).max(); + for (int y = ymin; y <= ymax; ++y) { + copy_from_image(im, y, row.data()); + if (!check(f.write_vector(row), "Could not write data")) { + return false; + } + } + + return true; +} + +template +bool load_pgm(const std::string &filename, ImageType *im) { + return Internal::load_pnm(filename, 1, im); +} + +inline const std::set &query_pgm() { + static std::set info = { + {halide_type_t(halide_type_uint, 8), 2}, + {halide_type_t(halide_type_uint, 16), 2}}; + return info; +} + +// "im" is not const-ref because copy_to_host() is not const. +template +bool save_pgm(ImageType &im, const std::string &filename) { + return Internal::save_pnm(im, 1, filename); +} + +template +bool load_ppm(const std::string &filename, ImageType *im) { + return Internal::load_pnm(filename, 3, im); +} + +inline const std::set &query_ppm() { + static std::set info = { + {halide_type_t(halide_type_uint, 8), 3}, + {halide_type_t(halide_type_uint, 16), 3}}; + return info; +} + +// "im" is not const-ref because copy_to_host() is not const. +template +bool save_ppm(ImageType &im, const std::string &filename) { + return Internal::save_pnm(im, 3, filename); +} + +#ifndef HALIDE_NO_JPEG + +template +bool load_jpg(const std::string &filename, ImageType *im) { + static_assert(!ImageType::has_static_halide_type, ""); + + Internal::FileOpener f(filename, "rb"); + if (!check(f.f != nullptr, "File could not be opened for reading")) { + return false; + } + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_decompress(&cinfo); + jpeg_stdio_src(&cinfo, f.f); + jpeg_read_header(&cinfo, TRUE); + jpeg_start_decompress(&cinfo); + + const int width = cinfo.output_width; + const int height = cinfo.output_height; + const int channels = cinfo.output_components; + + const halide_type_t im_type(halide_type_uint, 8); + std::vector im_dimensions = {width, height}; + if (channels > 1) { + im_dimensions.push_back(channels); + } + *im = ImageType(im_type, im_dimensions); + + auto copy_to_image = Internal::read_big_endian_row; + + std::vector row(width * channels); + const int ymin = im->dim(1).min(); + const int ymax = im->dim(1).max(); + for (int y = ymin; y <= ymax; ++y) { + uint8_t *src = row.data(); + jpeg_read_scanlines(&cinfo, &src, 1); + copy_to_image(row.data(), y, im); + } + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + return true; +} + +inline const std::set &query_jpg() { + static std::set info = { + {halide_type_t(halide_type_uint, 8), 2}, + {halide_type_t(halide_type_uint, 8), 3}, + }; + return info; +} + +template +bool save_jpg(ImageType &im, const std::string &filename) { + static_assert(!ImageType::has_static_halide_type, ""); + + if (!check(im.copy_to_host() == halide_error_code_success, "copy_to_host() failed.")) { + return false; + } + + const int width = im.width(); + const int height = im.height(); + const int channels = im.channels(); + if (!check(channels == 1 || channels == 3, "Wrong number of channels")) { + return false; + } + + Internal::FileOpener f(filename, "wb"); + if (!check(f.f != nullptr, "File could not be opened for writing")) { + return false; + } + + // TODO: Make this an argument? + constexpr int quality = 99; + + struct jpeg_compress_struct cinfo; + struct jpeg_error_mgr jerr; + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, f.f); + cinfo.image_width = width; + cinfo.image_height = height; + cinfo.input_components = channels; + cinfo.in_color_space = (channels == 3) ? JCS_RGB : JCS_GRAYSCALE; + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, TRUE); + jpeg_start_compress(&cinfo, TRUE); + + auto copy_from_image = Internal::write_big_endian_row; + + std::vector row(width * channels); + const int ymin = im.dim(1).min(); + const int ymax = im.dim(1).max(); + for (int y = ymin; y <= ymax; ++y) { + uint8_t *dst = row.data(); + copy_from_image(im, y, dst); + jpeg_write_scanlines(&cinfo, &dst, 1); + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + return true; +} + +#endif // not HALIDE_NO_JPEG + +constexpr int kNumTmpCodes = 10; + +inline const halide_type_t *tmp_code_to_halide_type() { + static const halide_type_t tmp_code_to_halide_type_[kNumTmpCodes] = { + {halide_type_float, 32}, + {halide_type_float, 64}, + {halide_type_uint, 8}, + {halide_type_int, 8}, + {halide_type_uint, 16}, + {halide_type_int, 16}, + {halide_type_uint, 32}, + {halide_type_int, 32}, + {halide_type_uint, 64}, + {halide_type_int, 64}}; + return tmp_code_to_halide_type_; +} + +// return true iff the buffer storage has no padding between +// any elements, and is in strictly planar order. +template +bool buffer_is_compact_planar(ImageType &im) { + const halide_type_t im_type = im.type(); + const size_t elem_size = (im_type.bits / 8); + if (((const uint8_t *)im.begin() + (im.number_of_elements() * elem_size)) != (const uint8_t *)im.end()) { + return false; + } + for (int d = 1; d < im.dimensions(); ++d) { + if (im.dim(d - 1).stride() > im.dim(d).stride()) { + return false; + } + // Strides can only match if the previous dimension has extent 1 + // (this can happen when artificially adding dimension(s), e.g. + // to write a .tmp file) + if (im.dim(d - 1).stride() == im.dim(d).stride() && im.dim(d - 1).extent() != 1) { + return false; + } + } + return true; +} + +// ".tmp" is a file format used by the ImageStack tool (see https://github.com/abadams/ImageStack) +template +bool load_tmp(const std::string &filename, ImageType *im) { + static_assert(!ImageType::has_static_halide_type, ""); + + FileOpener f(filename, "rb"); + if (!check(f.f != nullptr, "File could not be opened for reading")) { + return false; + } + + int32_t header[5]; + if (!check(f.read_array(header), "Count not read .tmp header")) { + return false; + } + + if (!check(header[0] > 0 && header[1] > 0 && header[2] > 0 && header[3] > 0 && + header[4] >= 0 && header[4] < kNumTmpCodes, + "Bad header on .tmp file")) { + return false; + } + + const halide_type_t im_type = tmp_code_to_halide_type()[header[4]]; + std::vector im_dimensions = {header[0], header[1], header[2], header[3]}; + *im = ImageType(im_type, im_dimensions); + + // This should never fail unless the default Buffer<> constructor behavior changes. + if (!check(buffer_is_compact_planar(*im), "load_tmp() requires compact planar images")) { + return false; + } + + if (!check(f.read_bytes(im->begin(), im->size_in_bytes()), "Count not read .tmp payload")) { + return false; + } + + im->set_host_dirty(); + return true; +} + +inline const std::set &query_tmp() { + // TMP files require exactly 4 dimensions. + static std::set info = { + {halide_type_t(halide_type_float, 32), 4}, + {halide_type_t(halide_type_float, 64), 4}, + {halide_type_t(halide_type_uint, 8), 4}, + {halide_type_t(halide_type_int, 8), 4}, + {halide_type_t(halide_type_uint, 16), 4}, + {halide_type_t(halide_type_int, 16), 4}, + {halide_type_t(halide_type_uint, 32), 4}, + {halide_type_t(halide_type_int, 32), 4}, + {halide_type_t(halide_type_uint, 64), 4}, + {halide_type_t(halide_type_int, 64), 4}, + }; + return info; +} + +template +bool write_planar_payload(ImageType &im, FileOpener &f) { + if (im.dimensions() == 0 || buffer_is_compact_planar(im)) { + // Contiguous buffer! Write it all in one swell foop. + if (!check(f.write_bytes(im.begin(), im.size_in_bytes()), "Count not write .tmp payload")) { + return false; + } + } else { + // We have to do this the hard way. + int d = im.dimensions() - 1; + for (int i = im.dim(d).min(); i <= im.dim(d).max(); i++) { + auto slice = im.sliced(d, i); + if (!write_planar_payload(slice, f)) { + return false; + } + } + } + return true; +} + +// ".tmp" is a file format used by the ImageStack tool (see https://github.com/abadams/ImageStack) +template +bool save_tmp(ImageType &im, const std::string &filename) { + static_assert(!ImageType::has_static_halide_type, ""); + + if (!check(im.copy_to_host() == halide_error_code_success, "copy_to_host() failed.")) { + return false; + } + + int32_t header[5] = {1, 1, 1, 1, -1}; + for (int i = 0; i < im.dimensions(); ++i) { + header[i] = im.dim(i).extent(); + } + const auto *table = tmp_code_to_halide_type(); + for (int i = 0; i < kNumTmpCodes; i++) { + if (im.type() == table[i]) { + header[4] = i; + break; + } + } + if (!check(header[4] >= 0, "Unsupported type for .tmp file")) { + return false; + } + + FileOpener f(filename, "wb"); + if (!check(f.f != nullptr, "File could not be opened for writing")) { + return false; + } + if (!check(f.write_array(header), "Could not write .tmp header")) { + return false; + } + + if (!write_planar_payload(im, f)) { + return false; + } + + return true; +} + +// ".mat" is the matlab level 5 format documented here: +// http://www.mathworks.com/help/pdf_doc/matlab/matfile_format.pdf + +enum MatlabTypeCode { + miINT8 = 1, + miUINT8 = 2, + miINT16 = 3, + miUINT16 = 4, + miINT32 = 5, + miUINT32 = 6, + miSINGLE = 7, + miDOUBLE = 9, + miINT64 = 12, + miUINT64 = 13, + miMATRIX = 14, + miCOMPRESSED = 15, + miUTF8 = 16, + miUTF16 = 17, + miUTF32 = 18 +}; + +enum MatlabClassCode { + mxCHAR_CLASS = 3, + mxDOUBLE_CLASS = 6, + mxSINGLE_CLASS = 7, + mxINT8_CLASS = 8, + mxUINT8_CLASS = 9, + mxINT16_CLASS = 10, + mxUINT16_CLASS = 11, + mxINT32_CLASS = 12, + mxUINT32_CLASS = 13, + mxINT64_CLASS = 14, + mxUINT64_CLASS = 15 +}; + +template +bool load_mat(const std::string &filename, ImageType *im) { + static_assert(!ImageType::has_static_halide_type, ""); + + FileOpener f(filename, "rb"); + if (!check(f.f != nullptr, "File could not be opened for reading")) { + return false; + } + + uint8_t header[128]; + if (!check(f.read_array(header), "Could not read .mat header\n")) { + return false; + } + + // Matrix header + uint32_t matrix_header[2]; + if (!check(f.read_array(matrix_header), "Could not read .mat header\n")) { + return false; + } + if (!check(matrix_header[0] == miMATRIX, "Could not parse this .mat file: bad matrix header\n")) { + return false; + } + + // Array flags + uint32_t flags[4]; + if (!check(f.read_array(flags), "Could not read .mat header\n")) { + return false; + } + if (!check(flags[0] == miUINT32 && flags[1] == 8, "Could not parse this .mat file: bad flags\n")) { + return false; + } + + // Shape + uint32_t shape_header[2]; + if (!check(f.read_array(shape_header), "Could not read .mat header\n")) { + return false; + } + if (!check(shape_header[0] == miINT32, "Could not parse this .mat file: bad shape header\n")) { + return false; + } + int dims = shape_header[1] / 4; + std::vector extents(dims); + if (!check(f.read_vector(&extents), "Could not read .mat header\n")) { + return false; + } + if (dims & 1) { + uint32_t padding; + if (!check(f.read_bytes(&padding, 4), "Could not read .mat header\n")) { + return false; + } + } + + // Skip over the name + uint32_t name_header[2]; + if (!check(f.read_array(name_header), "Could not read .mat header\n")) { + return false; + } + + if (name_header[0] >> 16) { + // Name must be fewer than 4 chars, and so the whole name + // field was stored packed into 8 bytes + } else { + if (!check(name_header[0] == miINT8, "Could not parse this .mat file: bad name header\n")) { + return false; + } + std::vector scratch((name_header[1] + 7) / 8); + if (!check(f.read_vector(&scratch), "Could not read .mat header\n")) { + return false; + } + } + + // Payload header + uint32_t payload_header[2]; + if (!check(f.read_array(payload_header), "Could not read .mat header\n")) { + return false; + } + halide_type_t type; + switch (payload_header[0]) { + case miINT8: + type = halide_type_of(); + break; + case miINT16: + type = halide_type_of(); + break; + case miINT32: + type = halide_type_of(); + break; + case miINT64: + type = halide_type_of(); + break; + case miUINT8: + type = halide_type_of(); + break; + case miUINT16: + type = halide_type_of(); + break; + case miUINT32: + type = halide_type_of(); + break; + case miUINT64: + type = halide_type_of(); + break; + case miSINGLE: + type = halide_type_of(); + break; + case miDOUBLE: + type = halide_type_of(); + break; + } + + *im = ImageType(type, extents); + + // This should never fail unless the default Buffer<> constructor behavior changes. + if (!check(buffer_is_compact_planar(*im), "load_mat() requires compact planar images")) { + return false; + } + + if (!check(f.read_bytes(im->begin(), im->size_in_bytes()), "Could not read .tmp payload")) { + return false; + } + + im->set_host_dirty(); + return true; +} + +inline const std::set &query_mat() { + // MAT files must have at least 2 dimensions, but there's no upper + // bound. Our support arbitrarily stops at 16 dimensions. + static std::set info = []() { + std::set s; + for (int i = 2; i < 16; i++) { + s.insert({halide_type_t(halide_type_float, 32), i}); + s.insert({halide_type_t(halide_type_float, 64), i}); + s.insert({halide_type_t(halide_type_uint, 8), i}); + s.insert({halide_type_t(halide_type_int, 8), i}); + s.insert({halide_type_t(halide_type_uint, 16), i}); + s.insert({halide_type_t(halide_type_int, 16), i}); + s.insert({halide_type_t(halide_type_uint, 32), i}); + s.insert({halide_type_t(halide_type_int, 32), i}); + s.insert({halide_type_t(halide_type_uint, 64), i}); + s.insert({halide_type_t(halide_type_int, 64), i}); + } + return s; + }(); + return info; +} + +template +bool save_mat(ImageType &im, const std::string &filename) { + static_assert(!ImageType::has_static_halide_type, ""); + + if (!check(im.copy_to_host() == halide_error_code_success, "copy_to_host() failed.")) { + return false; + } + + uint32_t class_code = 0, type_code = 0; + switch (im.raw_buffer()->type.code) { + case halide_type_int: + switch (im.raw_buffer()->type.bits) { + case 8: + class_code = mxINT8_CLASS; + type_code = miINT8; + break; + case 16: + class_code = mxINT16_CLASS; + type_code = miINT16; + break; + case 32: + class_code = mxINT32_CLASS; + type_code = miINT32; + break; + case 64: + class_code = mxINT64_CLASS; + type_code = miINT64; + break; + default: + check(false, "unreachable"); + }; + break; + case halide_type_uint: + switch (im.raw_buffer()->type.bits) { + case 8: + class_code = mxUINT8_CLASS; + type_code = miUINT8; + break; + case 16: + class_code = mxUINT16_CLASS; + type_code = miUINT16; + break; + case 32: + class_code = mxUINT32_CLASS; + type_code = miUINT32; + break; + case 64: + class_code = mxUINT64_CLASS; + type_code = miUINT64; + break; + default: + check(false, "unreachable"); + }; + break; + case halide_type_float: + switch (im.raw_buffer()->type.bits) { + case 16: + check(false, "float16 not supported by .mat"); + break; + case 32: + class_code = mxSINGLE_CLASS; + type_code = miSINGLE; + break; + case 64: + class_code = mxDOUBLE_CLASS; + type_code = miDOUBLE; + break; + default: + check(false, "unreachable"); + }; + break; + case halide_type_bfloat: + check(false, "bfloat not supported by .mat"); + break; + default: + check(false, "unreachable"); + } + + FileOpener f(filename, "wb"); + if (!check(f.f != nullptr, "File could not be opened for writing")) { + return false; + } + + // Pick a name for the array + size_t idx = filename.rfind('.'); + std::string name = filename.substr(0, idx); + idx = filename.rfind('/'); + if (idx != std::string::npos) { + name = name.substr(idx + 1); + } + + // Matlab variable names conform to similar rules as C + if (name.empty() || !std::isalpha(name[0])) { + name = "v" + name; + } + for (char &c : name) { + if (!std::isalnum(c)) { + c = '_'; + } + } + + uint32_t name_size = (int)name.size(); + while (name.size() & 0x7) { + name += '\0'; + } + + char header[128] = "MATLAB 5.0 MAT-file, produced by Halide"; + int len = strlen(header); + memset(header + len, ' ', sizeof(header) - len); + + // Version + *((uint16_t *)(header + 124)) = 0x0100; + + // Endianness check + header[126] = 'I'; + header[127] = 'M'; + + uint64_t payload_bytes = im.size_in_bytes(); + + if (!check((payload_bytes >> 32) == 0, "Buffer too large to save as .mat")) { + return false; + } + + int dims = im.dimensions(); + if (dims < 2) { + dims = 2; + } + int padded_dims = dims + (dims & 1); + + uint32_t padding_bytes = 7 - ((payload_bytes - 1) & 7); + + // Matrix header + uint32_t matrix_header[2] = { + miMATRIX, 40 + padded_dims * 4 + (uint32_t)name.size() + (uint32_t)payload_bytes + padding_bytes}; + + // Array flags + uint32_t flags[4] = { + miUINT32, 8, class_code, 1}; + + // Shape + int32_t shape[2] = { + miINT32, + im.dimensions() * 4, + }; + std::vector extents(im.dimensions()); + for (int d = 0; d < im.dimensions(); d++) { + extents[d] = im.dim(d).extent(); + } + while ((int)extents.size() < dims) { + extents.push_back(1); + } + while ((int)extents.size() < padded_dims) { + extents.push_back(0); + } + + // Name + uint32_t name_header[2] = { + miINT8, name_size}; + + // Payload header + uint32_t payload_header[2] = { + type_code, (uint32_t)payload_bytes}; + + bool success = + f.write_array(header) && + f.write_array(matrix_header) && + f.write_array(flags) && + f.write_array(shape) && + f.write_vector(extents) && + f.write_array(name_header) && + f.write_bytes(&name[0], name.size()) && + f.write_array(payload_header); + + if (!check(success, "Could not write .mat header")) { + return false; + } + + if (!write_planar_payload(im, f)) { + return false; + } + + // Padding + if (!check(padding_bytes < 8, "Too much padding!\n")) { + return false; + } + uint64_t padding = 0; + if (!f.write_bytes(&padding, padding_bytes)) { + return false; + } + + return true; +} + +template +bool load_tiff(const std::string &filename, ImageType *im) { + static_assert(!ImageType::has_static_halide_type, ""); + check(false, "Reading TIFF is not yet supported"); + return false; +} + +inline const std::set &query_tiff() { + auto build_set = []() -> std::set { + std::set s; + for (halide_type_code_t code : {halide_type_int, halide_type_uint, halide_type_float}) { + for (int bits : {8, 16, 32, 64}) { + for (int dims : {1, 2, 3, 4}) { + if (code == halide_type_float && bits < 32) { + continue; + } + s.insert({halide_type_t(code, bits), dims}); + } + } + } + return s; + }; + + static std::set info = build_set(); + return info; +} + +#pragma pack(push) +#pragma pack(2) + +struct halide_tiff_tag { + uint16_t tag_code; + int16_t type_code; + int32_t count; + union { + int8_t i8; + int16_t i16; + int32_t i32; + } value; + + void assign16(uint16_t tag_code, int32_t count, int16_t value) { + this->tag_code = tag_code; + this->type_code = 3; // SHORT + this->count = count; + this->value.i16 = value; + } + + void assign32(uint16_t tag_code, int32_t count, int32_t value) { + this->tag_code = tag_code; + this->type_code = 4; // LONG + this->count = count; + this->value.i32 = value; + } + + void assign32(uint16_t tag_code, int16_t type_code, int32_t count, int32_t value) { + this->tag_code = tag_code; + this->type_code = type_code; + this->count = count; + this->value.i32 = value; + } +}; + +struct halide_tiff_header { + int16_t byte_order_marker; + int16_t version; + int32_t ifd0_offset; + int16_t entry_count; + halide_tiff_tag entries[15]; + int32_t ifd0_end; + int32_t width_resolution[2]; + int32_t height_resolution[2]; +}; + +#pragma pack(pop) + +template +struct ElemWriter { + ElemWriter(FileOpener *f) + : f(f), next(&buf[0]) { + } + ~ElemWriter() { + flush(); + } + + void operator()(const ElemType &elem) { + if (!ok) { + return; + } + + *next++ = elem; + if (next == &buf[BUFFER_SIZE]) { + flush(); + } + } + + void flush() { + if (!ok) { + return; + } + + if (next > buf) { + if (!f->write_bytes(buf, (next - buf) * sizeof(ElemType))) { + ok = false; + } + next = buf; + } + } + + FileOpener *const f; + ElemType buf[BUFFER_SIZE]; + ElemType *next; + bool ok = true; +}; + +// Note that this is a fairly simpleminded TIFF writer that doesn't +// do any compression. It would be desirable to (optionally) support using libtiff +// here instead, which would also allow us to provide a useful implementation +// for TIFF reading. +template +bool save_tiff(ImageType &im, const std::string &filename) { + static_assert(!ImageType::has_static_halide_type, ""); + + if (!check(im.copy_to_host() == halide_error_code_success, "copy_to_host() failed.")) { + return false; + } + + if (!check(im.dimensions() <= 4, "Can only save TIFF files with <= 4 dimensions")) { + return false; + } + + FileOpener f(filename, "wb"); + if (!check(f.f != nullptr, "File could not be opened for writing")) { + return false; + } + + const size_t elements = im.number_of_elements(); + halide_dimension_t shape[4]; + for (int i = 0; i < im.dimensions() && i < 4; i++) { + const auto &d = im.dim(i); + shape[i].min = d.min(); + shape[i].extent = d.extent(); + shape[i].stride = d.stride(); + } + for (int i = im.dimensions(); i < 4; i++) { + shape[i].min = 0; + shape[i].extent = 1; + shape[i].stride = 0; + } + const halide_type_t im_type = im.type(); + if (!check(im_type.code >= 0 && im_type.code < 3, "Unsupported image type")) { + return false; + } + const int32_t bytes_per_element = im_type.bytes(); + const int32_t width = shape[0].extent; + const int32_t height = shape[1].extent; + int32_t depth = shape[2].extent; + int32_t channels = shape[3].extent; + + if ((channels == 0 || channels == 1) && (depth < 5)) { + channels = depth; + depth = 1; + } + + // TIFF sample type values are: + // 0 => Signed int + // 1 => Unsigned int + // 2 => Floating-point + static const int16_t type_code_to_tiff_sample_type[] = { + 2, 1, 3}; + + struct halide_tiff_header header; + memset(&header, 0, sizeof(header)); + + const int32_t MMII = 0x4d4d4949; + // Select the appropriate two bytes signaling byte order automatically + const char *c = (const char *)&MMII; + header.byte_order_marker = (c[0] << 8) | c[1]; + header.version = 42; + header.ifd0_offset = offsetof(halide_tiff_header, entry_count); + header.entry_count = sizeof(header.entries) / sizeof(header.entries[0]); + + static_assert(sizeof(halide_tiff_tag) == 12, "Unexpected halide_tiff_tag packing"); + halide_tiff_tag *tag = &header.entries[0]; + tag++->assign32(256, 1, width); // ImageWidth + tag++->assign32(257, 1, height); // ImageLength + tag++->assign16(258, 1, int16_t(bytes_per_element * 8)); // BitsPerSample + tag++->assign16(259, 1, 1); // Compression -- none + tag++->assign16(262, 1, channels >= 3 ? 2 : 1); // PhotometricInterpretation -- black is zero or RGB + tag++->assign32(273, channels, sizeof(header)); // StripOffsets + tag++->assign16(277, 1, int16_t(channels)); // SamplesPerPixel + tag++->assign32(278, 1, height); // RowsPerStrip + tag++->assign32(279, channels, // StripByteCounts + (channels == 1) ? + elements * bytes_per_element : + sizeof(header) + channels * sizeof(int32_t)); // for channels > 1, this is an offset + tag++->assign32(282, 5, 1, + offsetof(halide_tiff_header, width_resolution)); // XResolution + tag++->assign32(283, 5, 1, + offsetof(halide_tiff_header, height_resolution)); // YResolution + tag++->assign16(284, 1, channels == 1 ? 1 : 2); // PlanarConfiguration -- contig or planar + tag++->assign16(296, 1, 1); // ResolutionUnit -- none + tag++->assign16(339, 1, type_code_to_tiff_sample_type[im_type.code]); // SampleFormat + tag++->assign32(32997, 1, depth); // Image depth + + // Verify we used exactly the number we declared + assert(tag == &header.entries[header.entry_count]); + + header.ifd0_end = 0; + header.width_resolution[0] = 1; + header.width_resolution[1] = 1; + header.height_resolution[0] = 1; + header.height_resolution[1] = 1; + + if (!check(f.write_bytes(&header, sizeof(header)), "TIFF write failed")) { + return false; + } + + if (channels > 1) { + // Fill in the values for StripOffsets + int32_t offset = sizeof(header) + channels * sizeof(int32_t) * 2; + for (int32_t i = 0; i < channels; i++) { + if (!check(f.write_bytes(&offset, sizeof(offset)), "TIFF write failed")) { + return false; + } + offset += width * height * depth * bytes_per_element; + } + // Fill in the values for StripByteCounts + int32_t count = width * height * depth * bytes_per_element; + for (int32_t i = 0; i < channels; i++) { + if (!check(f.write_bytes(&count, sizeof(count)), "TIFF write failed")) { + return false; + } + } + } + + // If image is dense, we can write it in one fell swoop + if (elements * bytes_per_element == im.size_in_bytes()) { + if (!check(f.write_bytes(im.data(), im.size_in_bytes()), "TIFF write failed")) { + return false; + } + return true; + } + + // Otherwise, write it out via manual traversal. +#define HANDLE_CASE(CODE, BITS, TYPE) \ + case halide_type_t(CODE, BITS).as_u32(): { \ + ElemWriter ew(&f); \ + im.template as().for_each_value(ew); \ + if (!check(ew.ok, "TIFF write failed")) { \ + return false; \ + } \ + break; \ + } + + switch (im_type.element_of().as_u32()) { + HANDLE_CASE(halide_type_float, 32, float) + HANDLE_CASE(halide_type_float, 64, double) + HANDLE_CASE(halide_type_int, 8, int8_t) + HANDLE_CASE(halide_type_int, 16, int16_t) + HANDLE_CASE(halide_type_int, 32, int32_t) + HANDLE_CASE(halide_type_int, 64, int64_t) + HANDLE_CASE(halide_type_uint, 1, bool) + HANDLE_CASE(halide_type_uint, 8, uint8_t) + HANDLE_CASE(halide_type_uint, 16, uint16_t) + HANDLE_CASE(halide_type_uint, 32, uint32_t) + HANDLE_CASE(halide_type_uint, 64, uint64_t) + // Note that we don't attempt to handle halide_type_handle here. + default: + assert(false && "Unsupported type"); + return false; + } +#undef HANDLE_CASE + + return true; +} + +// Given something like ImageType, produce typedef ImageType +template +struct ImageTypeWithDynamicDims { + using type = decltype(std::declval().template as()); +}; + +// Given something like ImageType, produce typedef ImageType +template +struct ImageTypeWithElemType { + using type = decltype(std::declval().template as()); +}; + +// Given something like ImageType, produce typedef ImageType +template +struct ImageTypeWithConstElemType { + using type = decltype(std::declval().template as::type, AnyDims>()); +}; + +template +struct ImageIO { + using ConstImageType = typename ImageTypeWithConstElemType::type; + + std::function load; + std::function save; + std::function &()> query; +}; + +template +bool find_imageio(const std::string &filename, ImageIO *result) { + static_assert(!ImageType::has_static_halide_type, ""); + using ConstImageType = typename ImageTypeWithConstElemType::type; + + const std::map> m = { +#ifndef HALIDE_NO_JPEG + {"jpeg", {load_jpg, save_jpg, query_jpg}}, + {"jpg", {load_jpg, save_jpg, query_jpg}}, +#endif + {"pgm", {load_pgm, save_pgm, query_pgm}}, +#ifndef HALIDE_NO_PNG + {"png", {load_png, save_png, query_png}}, +#endif + {"ppm", {load_ppm, save_ppm, query_ppm}}, + {"tmp", {load_tmp, save_tmp, query_tmp}}, + {"mat", {load_mat, save_mat, query_mat}}, + {"tiff", {load_tiff, save_tiff, query_tiff}}, + }; + std::string ext = Internal::get_lowercase_extension(filename); + auto it = m.find(ext); + if (it != m.end()) { + *result = it->second; + return true; + } + + std::string err = "unsupported file extension \"" + ext + "\", supported are:"; + for (auto &it : m) { + err += " " + it.first; + } + err += "\n"; + return check(false, err.c_str()); +} + +template +FormatInfo best_save_format(const ImageType &im, const std::set &info) { + // A bit ad hoc, but will do for now: + // Perfect score is zero (exact match). + // The larger the score, the worse the match. + int best_score = 0x7fffffff; + FormatInfo best{}; + const halide_type_t im_type = im.type(); + const int im_dimensions = im.dimensions(); + for (const auto &f : info) { + int score = 0; + // If format has too-few dimensions, that's very bad. + score += std::max(0, im_dimensions - f.dimensions) * 1024; + // If format has too-few bits, that's pretty bad. + score += std::max(0, im_type.bits - f.type.bits) * 8; + // If format has too-many bits, that's a little bad. + score += std::max(0, f.type.bits - im_type.bits); + // If format has different code, that's a little bad. + score += (f.type.code != im_type.code) ? 1 : 0; + if (score < best_score) { + best_score = score; + best = f; + } + } + + return best; +} + +} // namespace Internal + +struct ImageTypeConversion { + // Convert an Image from one ElemType to another, where the src and + // dst types are statically known (e.g. Buffer -> Buffer). + // Note that this does conversion with scaling -- intepreting integers + // as fixed-point numbers between 0 and 1 -- not merely C-style casting. + // + // You'd normally call this with an explicit type for DstElemType and + // allow ImageType to be inferred, e.g. + // Buffer src = ...; + // Buffer dst = convert_image(src); + template::value>::type * = nullptr> + static auto convert_image(const ImageType &src) -> + typename Internal::ImageTypeWithElemType::type { + // The enable_if ensures this will never fire; this is here primarily + // as documentation and a backstop against breakage. + static_assert(ImageType::has_static_halide_type, + "This variant of convert_image() requires a statically-typed image"); + + using SrcImageType = ImageType; + using SrcElemType = typename SrcImageType::ElemType; + + using DstImageType = typename Internal::ImageTypeWithElemType::type; + + DstImageType dst = DstImageType::make_with_shape_of(src); + const auto converter = [](DstElemType &dst_elem, SrcElemType src_elem) { + dst_elem = Internal::convert(src_elem); + }; + dst.for_each_value(converter, src); + dst.set_host_dirty(); + + return dst; + } + + // Convert an Image from one ElemType to another, where the dst type is statically + // known but the src type is not (e.g. Buffer<> -> Buffer). + // You'd normally call this with an explicit type for DstElemType and + // allow ImageType to be inferred, e.g. + // Buffer src = ...; + // Buffer dst = convert_image(src); + template::value>::type * = nullptr> + static auto convert_image(const ImageType &src) -> + typename Internal::ImageTypeWithElemType::type { + // The enable_if ensures this will never fire; this is here primarily + // as documentation and a backstop against breakage. + static_assert(!ImageType::has_static_halide_type, + "This variant of convert_image() requires a dynamically-typed image"); + constexpr int AnyDims = Internal::AnyDims; + + const halide_type_t src_type = src.type(); + switch (src_type.element_of().as_u32()) { + case halide_type_t(halide_type_float, 32).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_float, 64).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_int, 8).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_int, 16).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_int, 32).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_int, 64).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_uint, 1).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_uint, 8).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_uint, 16).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_uint, 32).as_u32(): + return convert_image(src.template as()); + case halide_type_t(halide_type_uint, 64).as_u32(): + return convert_image(src.template as()); + default: + assert(false && "Unsupported type"); + using DstImageType = typename Internal::ImageTypeWithElemType::type; + return DstImageType(); + } + } + + // Convert an Image from one ElemType to another, where the src type + // is statically known but the dst type is not + // (e.g. Buffer -> Buffer<>(halide_type_t)). + template::value>::type * = nullptr> + static auto convert_image(const ImageType &src, const halide_type_t &dst_type) -> + typename Internal::ImageTypeWithElemType::type { + // The enable_if ensures this will never fire; this is here primarily + // as documentation and a backstop against breakage. + static_assert(ImageType::has_static_halide_type, + "This variant of convert_image() requires a statically-typed image"); + + // Call the appropriate static-to-static conversion routine + // based on the desired dst type. + switch (dst_type.element_of().as_u32()) { + case halide_type_t(halide_type_float, 32).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_float, 64).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_int, 8).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_int, 16).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_int, 32).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_int, 64).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_uint, 1).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_uint, 8).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_uint, 16).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_uint, 32).as_u32(): + return convert_image(src); + case halide_type_t(halide_type_uint, 64).as_u32(): + return convert_image(src); + default: + assert(false && "Unsupported type"); + using RetImageType = typename Internal::ImageTypeWithDynamicDims::type; + return RetImageType(); + } + } + + // Convert an Image from one ElemType to another, where neither src type + // nor dst type are statically known + // (e.g. Buffer<>(halide_type_t) -> Buffer<>(halide_type_t)). + template::value>::type * = nullptr> + static auto convert_image(const ImageType &src, const halide_type_t &dst_type) -> + typename Internal::ImageTypeWithElemType::type { + // The enable_if ensures this will never fire; this is here primarily + // as documentation and a backstop against breakage. + static_assert(!ImageType::has_static_halide_type, + "This variant of convert_image() requires a dynamically-typed image"); + constexpr int AnyDims = Internal::AnyDims; + + // Sniff the runtime type of src, coerce it to that type using as<>(), + // and call the static-to-dynamic variant of this method. (Note that + // this forces instantiation of the complete any-to-any conversion + // matrix of code.) + const halide_type_t src_type = src.type(); + switch (src_type.element_of().as_u32()) { + case halide_type_t(halide_type_float, 32).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_float, 64).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_int, 8).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_int, 16).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_int, 32).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_int, 64).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_uint, 1).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_uint, 8).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_uint, 16).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_uint, 32).as_u32(): + return convert_image(src.template as(), dst_type); + case halide_type_t(halide_type_uint, 64).as_u32(): + return convert_image(src.template as(), dst_type); + default: + assert(false && "Unsupported type"); + using RetImageType = typename Internal::ImageTypeWithDynamicDims::type; + return RetImageType(); + } + } +}; + +// Load the Image from the given file. +// If output Image has a static type, and the loaded image cannot be stored +// in such an image without losing data, fail. +// Returns false upon failure. +template +bool load(const std::string &filename, ImageType *im) { + using DynamicImageType = typename Internal::ImageTypeWithElemType::type; + Internal::ImageIO imageio; + if (!Internal::find_imageio(filename, &imageio)) { + return false; + } + using DynamicImageType = typename Internal::ImageTypeWithElemType::type; + DynamicImageType im_d; + if (!imageio.load(filename, &im_d)) { + return false; + } + // Allow statically-typed images to be passed as the out-param, but do + // a runtime check to ensure + if (ImageType::has_static_halide_type) { + const halide_type_t expected_type = ImageType::static_halide_type(); + if (!check(im_d.type() == expected_type, "Image loaded did not match the expected type")) { + return false; + } + } + *im = im_d.template as(); + im->set_host_dirty(); + return true; +} + +// Save the Image in the format associated with the filename's extension. +// If the format can't represent the Image without losing data, fail. +// Returns false upon failure. +template +bool save(ImageType &im, const std::string &filename) { + using DynamicImageType = typename Internal::ImageTypeWithElemType::type; + Internal::ImageIO imageio; + if (!Internal::find_imageio(filename, &imageio)) { + return false; + } + if (!check(imageio.query().count({im.type(), im.dimensions()}) > 0, "Image cannot be saved in this format")) { + return false; + } + + // Allow statically-typed images to be passed in, but quietly pass them on + // as dynamically-typed images. + auto im_d = im.template as(); + return imageio.save(im_d, filename); +} + +// Return a set of FormatInfo structs that contain the legal type-and-dimensions +// that can be saved in this format. Most applications won't ever need to use +// this call. Returns false upon failure. +template +bool save_query(const std::string &filename, std::set *info) { + using DynamicImageType = typename Internal::ImageTypeWithElemType::type; + Internal::ImageIO imageio; + if (!Internal::find_imageio(filename, &imageio)) { + return false; + } + *info = imageio.query(); + return true; +} + +// Fancy wrapper to call load() with CheckFail, inferring the return type; +// this allows you to simply use +// +// Image im = load_image("filename"); +// +// without bothering to check error results (all errors simply abort). +// +// Note that if the image being loaded doesn't match the static type and +// dimensions of of the image on the LHS, a runtime error will occur. +class load_image { +public: + load_image(const std::string &f) + : filename(f) { + } + + template + operator ImageType() { + using DynamicImageType = typename Internal::ImageTypeWithElemType::type; + DynamicImageType im_d; + (void)load(filename, &im_d); + Internal::CheckFail(ImageType::can_convert_from(im_d), + "Type mismatch assigning the result of load_image. " + "Did you mean to use load_and_convert_image?"); + return im_d.template as(); + } + +private: + const std::string filename; +}; + +// Like load_image, but quietly convert the loaded image to the type of the LHS +// if necessary, discarding information if necessary. +class load_and_convert_image { +public: + load_and_convert_image(const std::string &f) + : filename(f) { + } + + template + inline operator ImageType() { + using DynamicImageType = typename Internal::ImageTypeWithElemType::type; + DynamicImageType im_d; + (void)load(filename, &im_d); + const halide_type_t expected_type = ImageType::static_halide_type(); + if (im_d.type() == expected_type) { + return im_d.template as(); + } else { + return ImageTypeConversion::convert_image(im_d); + } + } + +private: + const std::string filename; +}; + +// Fancy wrapper to call save() with CheckFail; this allows you to simply use +// +// save_image(im, "filename"); +// +// without bothering to check error results (all errors simply abort). +// +// If the specified image file format cannot represent the image without +// losing data (e.g, a float32 or 4-dimensional image saved as a JPEG), +// a runtime error will occur. +template +void save_image(ImageType &im, const std::string &filename) { + auto im_d = im.template as(); + (void)save(im_d, filename); +} + +// Like save_image, but quietly convert the saved image to a type that the +// specified image file format can hold, discarding information if necessary. +// (Note that the input image is unaffected!) +template +void convert_and_save_image(ImageType &im, const std::string &filename) { + // We'll be doing any conversion on the CPU + if (!check(im.copy_to_host() == halide_error_code_success, "copy_to_host() failed.")) { + return; + } + + std::set info; + (void)save_query::type, check>(filename, &info); + const FormatInfo best = Internal::best_save_format(im, info); + if (best.type == im.type() && best.dimensions == im.dimensions()) { + // It's an exact match, we can save as-is. + using DynamicImageDims = typename Internal::ImageTypeWithDynamicDims::type; + (void)save(im.template as(), filename); + } else { + using DynamicImageType = typename Internal::ImageTypeWithElemType::type; + DynamicImageType im_converted = ImageTypeConversion::convert_image(im, best.type); + while (im_converted.dimensions() < best.dimensions) { + im_converted.add_dimension(); + } + (void)save(im_converted, filename); + } +} + +} // namespace Tools +} // namespace Halide + +#endif // HALIDE_IMAGE_IO_H \ No newline at end of file diff --git a/src/bb/image-io/rt_common.h b/src/bb/image-io/rt_common.h index 43324a85..d7b93df7 100644 --- a/src/bb/image-io/rt_common.h +++ b/src/bb/image-io/rt_common.h @@ -9,8 +9,11 @@ #include #include -#include -#include +#include "opencv_loader.h" + +#include +#include +#include "halide_image_io.h" #include "ion/export.h" @@ -18,7 +21,10 @@ #include "log.h" #include "httplib.h" + +namespace zip_file { #include "zip_file.hpp" +} #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -40,103 +46,103 @@ std::string format(const char *fmt, const Rest &... rest) { return s; } -class DynamicModule { -public: -#ifdef _WIN32 - using Handle = HMODULE; -#else - using Handle = void *; -#endif - - DynamicModule(const std::string &module_name, bool essential) { - ion::log::debug("Load module : Trying to load {}", module_name); - if (module_name == "") { - handle_ = nullptr; - return; - } - -#ifdef _WIN32 - auto file_name = module_name + ".dll"; - ion::log::debug("Load module : Looking for {}", file_name); - handle_ = LoadLibraryA(file_name.c_str()); - - if (handle_ != nullptr){ - //successfully loaded the module without the prefix of "lib". - return; - } - - file_name = "lib" + file_name; - ion::log::debug("Load module : Looking for {}", file_name); - handle_ = LoadLibraryA(file_name.c_str()); - -#else - auto file_name = "lib" + module_name + ".so"; - ion::log::debug("Load module : Looking for {}", file_name); - handle_ = dlopen(file_name.c_str(), RTLD_NOW); -#endif - - if (handle_ == nullptr) { - if (essential) { - throw std::runtime_error(get_error_string()); - } else { - std::cerr << format("WARNING: Not found the not essential dynamic library: %s, it may work as a simulation mode", module_name.c_str()); - } - } - } - - ~DynamicModule() { - if (handle_ != nullptr) { -#ifdef _WIN32 - FreeLibrary(handle_); -#else - dlclose(handle_); -#endif - } - } - - DynamicModule(const std::string &module_name) - : DynamicModule(module_name, true) { - } - - bool is_available(void) const { - return handle_ != NULL; - } - - template - T get_symbol(const std::string &symbol_name) const { -#if defined(_WIN32) - return reinterpret_cast(GetProcAddress(handle_, symbol_name.c_str())); -#else - return reinterpret_cast(dlsym(handle_, symbol_name.c_str())); -#endif - } - -private: - std::string get_error_string(void) const { - std::string error_msg; - -#ifdef _WIN32 - LPVOID lpMsgBuf; - FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, GetLastError(), - MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), - (LPTSTR)&lpMsgBuf, 0, nullptr); - std::size_t len = 0; - wcstombs_s(&len, nullptr, 0, reinterpret_cast(lpMsgBuf), _TRUNCATE); - std::vector buf(len + 1, 0); - wcstombs_s(nullptr, &buf[0], len, reinterpret_cast(lpMsgBuf), _TRUNCATE); - error_msg.assign(buf.begin(), buf.end()); - LocalFree(lpMsgBuf); -#else - const char *buf(dlerror()); - error_msg.assign(buf ? buf : "none"); -#endif - return error_msg; - } - - Handle handle_; -}; +// class DynamicModule { +// public: +// #ifdef _WIN32 +// using Handle = HMODULE; +// #else +// using Handle = void *; +// #endif +// +// DynamicModule(const std::string &module_name, bool essential) { +// ion::log::debug("Load module : Trying to load {}", module_name); +// if (module_name == "") { +// handle_ = nullptr; +// return; +// } +// +// #ifdef _WIN32 +// auto file_name = module_name + ".dll"; +// ion::log::debug("Load module : Looking for {}", file_name); +// handle_ = LoadLibraryA(file_name.c_str()); +// +// if (handle_ != nullptr){ +// //successfully loaded the module without the prefix of "lib". +// return; +// } +// +// file_name = "lib" + file_name; +// ion::log::debug("Load module : Looking for {}", file_name); +// handle_ = LoadLibraryA(file_name.c_str()); +// +// #else +// auto file_name = "lib" + module_name + ".so"; +// ion::log::debug("Load module : Looking for {}", file_name); +// handle_ = dlopen(file_name.c_str(), RTLD_NOW); +// #endif +// +// if (handle_ == nullptr) { +// if (essential) { +// throw std::runtime_error(get_error_string()); +// } else { +// std::cerr << format("WARNING: Not found the not essential dynamic library: %s, it may work as a simulation mode", module_name.c_str()); +// } +// } +// } +// +// ~DynamicModule() { +// if (handle_ != nullptr) { +// #ifdef _WIN32 +// FreeLibrary(handle_); +// #else +// dlclose(handle_); +// #endif +// } +// } +// +// DynamicModule(const std::string &module_name) +// : DynamicModule(module_name, true) { +// } +// +// bool is_available(void) const { +// return handle_ != NULL; +// } +// +// template +// T get_symbol(const std::string &symbol_name) const { +// #if defined(_WIN32) +// return reinterpret_cast(GetProcAddress(handle_, symbol_name.c_str())); +// #else +// return reinterpret_cast(dlsym(handle_, symbol_name.c_str())); +// #endif +// } +// +// private: +// std::string get_error_string(void) const { +// std::string error_msg; +// +// #ifdef _WIN32 +// LPVOID lpMsgBuf; +// FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | +// FORMAT_MESSAGE_IGNORE_INSERTS, +// nullptr, GetLastError(), +// MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), +// (LPTSTR)&lpMsgBuf, 0, nullptr); +// std::size_t len = 0; +// wcstombs_s(&len, nullptr, 0, reinterpret_cast(lpMsgBuf), _TRUNCATE); +// std::vector buf(len + 1, 0); +// wcstombs_s(nullptr, &buf[0], len, reinterpret_cast(lpMsgBuf), _TRUNCATE); +// error_msg.assign(buf.begin(), buf.end()); +// LocalFree(lpMsgBuf); +// #else +// const char *buf(dlerror()); +// error_msg.assign(buf ? buf : "none"); +// #endif +// return error_msg; +// } +// +// Handle handle_; +// }; std::tuple parse_url(const std::string &url) { auto protocol_end_pos = url.find("://"); @@ -149,45 +155,7 @@ std::tuple parse_url(const std::string &url) { return std::tuple(host_name, path_name); } - -cv::Mat get_image(const std::string &url) { - if (url.empty()) { - return {}; - } - - std::string host_name; - std::string path_name; - std::tie(host_name, path_name) = parse_url(url); - - cv::Mat img; - bool img_loaded = false; - if (host_name.empty() || path_name.empty()) { - // fallback to local file - img = cv::imread(url); - if (!img.empty()) { - img_loaded = true; - } - } else { - httplib::Client cli(host_name.c_str()); - cli.set_follow_location(true); - auto res = cli.Get(path_name.c_str()); - if (res && res->status == 200) { - std::vector data(res->body.size()); - std::memcpy(data.data(), res->body.c_str(), res->body.size()); - img = cv::imdecode(cv::InputArray(data), cv::IMREAD_COLOR); - if (!img.empty()) { - img_loaded = true; - } - } - } - - if (img_loaded) { - return img; - } else { - return {}; - } -} - +template class ImageSequence { public: @@ -224,7 +192,7 @@ class ImageSequence { } if (fs::path(url).extension() == ".zip") { - miniz_cpp::zip_file zf(data); + zip_file::miniz_cpp::zip_file zf(data); zf.extractall(dir_path.string()); } else { std::ofstream ofs(dir_path / fs::path(url).filename(), std::ios::binary); @@ -239,29 +207,35 @@ class ImageSequence { } - cv::Mat get(int width, int height, int imread_flags) { + void get(int width, int height, int imread_flags, Halide::Runtime::Buffer &buf) { namespace fs = std::filesystem; - cv::Mat frame; - auto path = paths_[idx_]; + auto size = fs::file_size(path); + std::ifstream ifs(path, std::ios::binary); + std::vector img_data(size); + ifs.read(reinterpret_cast(img_data.data()), size); if (path.extension() == ".raw") { - auto size = fs::file_size(path); switch (imread_flags) { - case cv::IMREAD_GRAYSCALE: + case IMREAD_GRAYSCALE: if (size == width * height * sizeof(uint8_t)) { - frame = cv::Mat(height, width, CV_8UC1); + Halide::Runtime::Buffer buf_8(std::vector{width, height}); //read in 8 bit + memcpy(buf_8.data(), img_data.data(), width * height * sizeof(uint8_t)); // set_img_data + auto buf_16 = Halide::Tools::ImageTypeConversion::convert_image(buf_8, halide_type_of()); + buf.copy_from(buf_16); } else if (size == width * height * sizeof(uint16_t)) { - frame = cv::Mat(height, width, CV_16UC1); + memcpy(buf.data(), img_data.data(), width * height * sizeof(T)); } else { throw std::runtime_error("Unsupported raw format"); } break; - case cv::IMREAD_COLOR: + case IMREAD_COLOR: if (size == 3 * width * height * sizeof(uint8_t)) { // Expect interleaved RGB - frame = cv::Mat(height, width, CV_8UC3); + Halide::Runtime::Buffer buf_interleaved = Halide::Runtime::Buffer ::make_interleaved(width, height, 3); ; + auto buffer_planar = buf_interleaved.copy_to_planar(); + buf.copy_from(buffer_planar); } else { throw std::runtime_error("Unsupported raw format"); } @@ -269,17 +243,26 @@ class ImageSequence { default: throw std::runtime_error("Unsupported flags"); } - std::ifstream ifs(path, std::ios::binary); - ifs.read(reinterpret_cast(frame.ptr()), size); } else { - frame = cv::imread(path.string(), imread_flags); - if (frame.empty()) { - throw std::runtime_error("Failed to load data : " + path.string()); + switch (imread_flags) { + case IMREAD_GRAYSCALE: + { Halide::Runtime::Buffer img_buf = Halide::Tools::load_and_convert_image(path.string()); + std::memcpy(buf.data(), img_buf.data(), height*width*sizeof(T)); + } + break; + case IMREAD_COLOR: + + { Halide::Buffer img_buf = Halide::Tools::load_and_convert_image(path.string()); + std::memcpy(buf.data(), img_buf.data(), 3* height*width*sizeof(T)); + } + break; + default: + throw std::runtime_error("Unsupported flags"); } + } idx_ = ((idx_+1) % paths_.size()); - - return frame; + return; } private: diff --git a/src/bb/image-io/rt_display.h b/src/bb/image-io/rt_display.h index 71ebcd3e..5a17039e 100644 --- a/src/bb/image-io/rt_display.h +++ b/src/bb/image-io/rt_display.h @@ -10,11 +10,9 @@ #include #endif -#include -#include - #include +#include "opencv_loader.h" #include "rt_common.h" #ifdef __linux__ @@ -123,11 +121,18 @@ extern "C" ION_EXPORT int ion_bb_image_io_gui_display(halide_buffer_t *in, int w in->dim[2].extent = height; } else { if (getenv("DISPLAY")) { + auto& cv(ion::bb::OpenCV::get_instance()); Halide::Runtime::Buffer ibuf(*in); ibuf.copy_to_host(); - cv::Mat img(std::vector{height, width}, CV_8UC3, ibuf.data()); - cv::imshow("img" + std::to_string(idx), img); - cv::waitKey(1); + + auto img = cv.cvCreateMatHeader(height, width, CV_MAKETYPE(CV_8U, 3)); + cv.cvSetData(img, in->host, 3*width*sizeof(uint8_t)); + + auto name = "img" + std::to_string(idx); + cv.cvShowImage(name.c_str(), img); + cv.cvWaitKey(1); + + cv.cvReleaseMat(&img); } else { // This is shimulation mode. Just sleep 1/1000 second. std::this_thread::sleep_for(std::chrono::milliseconds(1)); diff --git a/src/bb/image-io/rt_file.h b/src/bb/image-io/rt_file.h index 6d18ed0e..068e1549 100644 --- a/src/bb/image-io/rt_file.h +++ b/src/bb/image-io/rt_file.h @@ -17,8 +17,7 @@ #include "rt_common.h" #include "httplib.h" -#include -#include +#include "opencv_loader.h" extern "C" int ION_EXPORT ion_bb_image_io_color_data_loader(halide_buffer_t *session_id_buf, halide_buffer_t *url_buf, int32_t width, int32_t height, halide_buffer_t *out) { @@ -37,22 +36,14 @@ extern "C" int ION_EXPORT ion_bb_image_io_color_data_loader(halide_buffer_t *ses } else { const std::string session_id(reinterpret_cast(session_id_buf->host)); const std::string url = reinterpret_cast(url_buf->host); - static std::unordered_map> seqs; + static std::unordered_map>> seqs; if (seqs.count(session_id) == 0) { - seqs[session_id] = std::unique_ptr(new ImageSequence(session_id, url)); + seqs[session_id] = std::unique_ptr>(new ImageSequence(session_id, url)); } - auto frame = seqs[session_id]->get(width, height, cv::IMREAD_COLOR); - // Resize to desired width/height - cv::resize(frame, frame, cv::Size(width, height), 0, 0); + Halide::Runtime::Buffer obuf(*out); + seqs[session_id]->get(width, height, IMREAD_COLOR, obuf); - // Convert to RGB from BGR - cv::cvtColor(frame, frame, cv::COLOR_BGR2RGB); - - // Reshape interleaved to planar - frame = frame.reshape(1, width*height).t(); - - std::memcpy(out->host, frame.data, width * height * 3 * sizeof(uint8_t)); } } catch (const std::exception &e) { std::cerr << e.what() << std::endl; @@ -79,16 +70,12 @@ extern "C" int ION_EXPORT ion_bb_image_io_grayscale_data_loader(halide_buffer_t } else { const std::string session_id(reinterpret_cast(session_id_buf->host)); const std::string url = reinterpret_cast(url_buf->host); - static std::unordered_map> seqs; + static std::unordered_map>> seqs; if (seqs.count(session_id) == 0) { - seqs[session_id] = std::unique_ptr(new ImageSequence(session_id, url)); + seqs[session_id] = std::unique_ptr>(new ImageSequence(session_id, url)); } - auto frame = seqs[session_id]->get(width, height, cv::IMREAD_GRAYSCALE); - - // Normalize value range from 0-255 into 0-dynamic_range - cv::normalize(frame, frame, 0, dynamic_range, cv::NORM_MINMAX, CV_16UC1); - - std::memcpy(out->host, frame.data, width * height * sizeof(uint16_t)); + Halide::Runtime::Buffer obuf(*out); + seqs[session_id]->get(width, height, IMREAD_GRAYSCALE, obuf); } } catch (const std::exception &e) { std::cerr << e.what() << std::endl; @@ -102,18 +89,22 @@ extern "C" int ION_EXPORT ion_bb_image_io_grayscale_data_loader(halide_buffer_t } ION_REGISTER_EXTERN(ion_bb_image_io_grayscale_data_loader); -extern "C" int ION_EXPORT ion_bb_image_io_saver(halide_buffer_t *in, int32_t in_extent_1, int32_t in_extent_2, halide_buffer_t *path, halide_buffer_t *out) { +extern "C" int ION_EXPORT ion_bb_image_io_image_saver(halide_buffer_t *in, int32_t width, int32_t height, halide_buffer_t *path, halide_buffer_t *out) { try { if (in->is_bounds_query()) { in->dim[0].min = 0; in->dim[0].extent = 3; in->dim[1].min = 0; - in->dim[1].extent = in_extent_1; + in->dim[1].extent = width; in->dim[2].min = 0; - in->dim[2].extent = in_extent_2; + in->dim[2].extent = height; } else { - cv::Mat img(std::vector{in_extent_2, in_extent_1}, CV_8UC3, in->host); - cv::imwrite(reinterpret_cast(path->host), img); + + + + Halide::Runtime::Buffer obuf = Halide::Runtime::Buffer::make_interleaved(width, height, 3); + std::memcpy(obuf.data(), in->host, 3* width*height*sizeof(uint8_t)); + Halide::Tools::save_image(obuf, reinterpret_cast(path->host)); } } catch (const std::exception &e) { std::cerr << e.what() << std::endl; @@ -125,7 +116,7 @@ extern "C" int ION_EXPORT ion_bb_image_io_saver(halide_buffer_t *in, int32_t in_ return 0; } -ION_REGISTER_EXTERN(ion_bb_image_io_saver); +ION_REGISTER_EXTERN(ion_bb_image_io_image_saver); namespace { @@ -506,7 +497,7 @@ int ion_bb_image_io_binary_1image_saver( return 0; } - ion::bb::image_io::rawHeader header_info0; + ion::bb::image_io::rawHeader header_info0; ::memcpy(&header_info0, deviceinfo->host, sizeof(ion::bb::image_io::rawHeader)); std::vector header_infos{header_info0}; @@ -531,7 +522,7 @@ ION_REGISTER_EXTERN(ion_bb_image_io_binary_1image_saver); extern "C" ION_EXPORT int ion_bb_image_io_binary_2image_saver( - halide_buffer_t * image0, halide_buffer_t * image1, + halide_buffer_t * image0, halide_buffer_t * image1, halide_buffer_t * deviceinfo0, halide_buffer_t * deviceinfo1, halide_buffer_t * frame_count, bool dispose, int32_t width, int32_t height, int32_t dim, int byte_depth, halide_buffer_t* output_directory_buf, halide_buffer_t * out) diff --git a/src/bb/image-io/rt_v4l2.h b/src/bb/image-io/rt_v4l2.h index 045f0139..af80baa9 100644 --- a/src/bb/image-io/rt_v4l2.h +++ b/src/bb/image-io/rt_v4l2.h @@ -1,6 +1,9 @@ #ifndef ION_BB_IMAGE_IO_RT_V4L2_H #define ION_BB_IMAGE_IO_RT_V4L2_H +// TODO: Remove OpenCV build dependency +#ifdef HAS_OPENCV + #include #include #include @@ -35,6 +38,43 @@ namespace image_io { std::unordered_map> image_cache; +cv::Mat get_image(const std::string &url) { + if (url.empty()) { + return {}; + } + + std::string host_name; + std::string path_name; + std::tie(host_name, path_name) = parse_url(url); + + cv::Mat img; + bool img_loaded = false; + if (host_name.empty() || path_name.empty()) { + // fallback to local file + img = cv::imread(url); + if (!img.empty()) { + img_loaded = true; + } + } else { + httplib::Client cli(host_name.c_str()); + cli.set_follow_location(true); + auto res = cli.Get(path_name.c_str()); + if (res && res->status == 200) { + std::vector data(res->body.size()); + std::memcpy(data.data(), res->body.c_str(), res->body.size()); + img = cv::imdecode(cv::InputArray(data), IMREAD_COLOR); + if (!img.empty()) { + img_loaded = true; + } + } + } + + if (img_loaded) { + return img; + } else { + return {}; + } +} } // namespace image_io } // namespace bb } // namespace ion @@ -578,4 +618,7 @@ extern "C" int ION_EXPORT ion_bb_image_io_camera(int32_t instance_id, int32_t in } ION_REGISTER_EXTERN(ion_bb_image_io_camera) -#endif +#endif // HAS_OPENCV + +#endif // ION_BB_IMAGE_IO_RT_V4L2_H + diff --git a/src/bb/image-io/thirdparty_notice.txt b/src/bb/image-io/thirdparty_notice.txt deleted file mode 100644 index b39cda3d..00000000 --- a/src/bb/image-io/thirdparty_notice.txt +++ /dev/null @@ -1,122 +0,0 @@ -cpp-httplib - -The MIT License (MIT) - -Copyright (c) 2017 yhirose - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----- - -Sole is a lightweight C++11 library to generate universally unique -identificators (UUID) - -Copyright (c) 2015 r-lyeh (https://github.com/r-lyeh) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not -claim that you wrote the original software. If you use this software -in a product, an acknowledgment in the product documentation would be -appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be -misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - ----- - -miniz-cpp - -The MIT License (MIT) - -Copyright (c) 2014-2017 Thomas Fussell - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - ----- - -ghc::filesystem - -The MIT License (MIT) - -Copyright (c) 2018, Steffen Schümann - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ----- - -Sole is a lightweight C++11 library to generate universally unique -identificators (UUID) - -Copyright (c) 2015 r-lyeh (https://github.com/r-lyeh) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any damages -arising from the use of this software. - -Permission is granted to anyone to use this software for any purpose, -including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not -claim that you wrote the original software. If you use this software -in a product, an acknowledgment in the product documentation would be -appreciated but is not required. -2. Altered source versions must be plainly marked as such, and must not be -misrepresented as being the original software. -3. This notice may not be removed or altered from any source distribution. - diff --git a/src/bb/opencv/bb.h b/src/bb/opencv/bb.h index b19c6fcb..9059c316 100644 --- a/src/bb/opencv/bb.h +++ b/src/bb/opencv/bb.h @@ -7,8 +7,6 @@ class MedianBlur : public ion::BuildingBlock { public: GeneratorParam ksize{"ksize", 3}; Input input{"input", UInt(8), 3}; - Input width{"width", 0}; - Input height{"height", 0}; Output output{"output", UInt(8), 3}; void generate() { @@ -16,9 +14,9 @@ class MedianBlur : public ion::BuildingBlock { Func in; in(_) = input(_); in.compute_root(); - std::vector params{in, cast(3), width, height, static_cast(ksize)}; + std::vector params{in, static_cast(ksize)}; Func median_blur; - median_blur.define_extern("median_blur", params, UInt(8), 3); + median_blur.define_extern("ion_bb_opencv_median_blur", params, UInt(8), 3); median_blur.compute_root(); output(c, x, y) = median_blur(c, x, y); } @@ -35,9 +33,9 @@ ION_REGISTER_BUILDING_BLOCK(MedianBlur, opencv_median_blur); class Display : public ion::BuildingBlock { public: GeneratorParam idx{"idx", 0}; + GeneratorParam width{"width", 640}; + GeneratorParam height{"height", 480}; Input input{"input", UInt(8), 3}; - Input width{"width", 0}; - Input height{"height", 0}; Output output{"output"}; void generate() { @@ -45,9 +43,9 @@ class Display : public ion::BuildingBlock { Func in; in(_) = input(_); in.compute_root(); - std::vector params = {in, width, height, static_cast(idx)}; + std::vector params = {in, static_cast(width), static_cast(height), static_cast(idx)}; Func display; - display.define_extern("display", params, Int(32), 0); + display.define_extern("ion_bb_opencv_display", params, Int(32), 0); display.compute_root(); output() = display(); } diff --git a/src/bb/opencv/rt.h b/src/bb/opencv/rt.h index 5bb877cf..ae31e743 100644 --- a/src/bb/opencv/rt.h +++ b/src/bb/opencv/rt.h @@ -2,11 +2,12 @@ #define ION_BB_OPENCV_RT_H #include +#include -#include -#include +#include "dynamic_module.h" -#include "ion/export.h" +#include "log.h" +#include "opencv_loader.h" namespace ion { namespace bb { @@ -21,51 +22,57 @@ class RegisterExtern { } }; + } // image_io } // bb } // ion #define ION_REGISTER_EXTERN(NAME) static auto ion_register_extern_##NAME = ion::bb::opencv::RegisterExtern(#NAME, NAME); -namespace { -int hl2cv_type(halide_type_t hl_type, int channel) { - if (hl_type.code != halide_type_uint) { - return -1; - } - if (hl_type.bits == 8) { - return CV_MAKETYPE(CV_8U, channel); - } else if (hl_type.bits == 16) { - return CV_MAKETYPE(CV_16U, channel); - } else { +extern "C" ION_EXPORT +int ion_bb_opencv_median_blur(halide_buffer_t *in, int ksize, halide_buffer_t *out) { + auto& cv(ion::bb::OpenCV::get_instance()); + if (!cv.is_available()) { + ion::log::error("OpenCV is not available"); return -1; } -} -} // namespace - -extern "C" ION_EXPORT -int median_blur(halide_buffer_t *in, int channel, int width, int height, int ksize, halide_buffer_t *out) { if (in->is_bounds_query()) { - in->dim[0].min = 0; - in->dim[0].extent = channel; - in->dim[1].min = 0; - in->dim[1].extent = width; - in->dim[2].min = 0; - in->dim[2].extent = height; + for (auto i=0; idimensions; ++i) { + in->dim[i].min = out->dim[i].min; + in->dim[i].extent = out->dim[i].extent; + } } else { - int cv_type = hl2cv_type(in->type, channel); + int width = in->dim[1].extent; + int height = in->dim[2].extent; + int cv_type = ion::bb::hl2cv_type(in->type, 3); if (cv_type == -1) { return -1; } - cv::Mat src(std::vector{height, width}, cv_type, in->host); - cv::Mat dst(std::vector{height, width}, cv_type, out->host); - cv::medianBlur(src, dst, ksize); + + auto src = cv.cvCreateMatHeader(height, width, cv_type); + cv.cvSetData(src, in->host, 3*width*sizeof(uint8_t)); + + auto dst = cv.cvCreateMatHeader(height, width, cv_type); + cv.cvSetData(dst, out->host, 3*width*sizeof(uint8_t)); + + cv.cvSmooth(src, dst, CV_MEDIAN, ksize, ksize, 0, 0); + + cv.cvReleaseMat(&src); + cv.cvReleaseMat(&dst); } return 0; } +ION_REGISTER_EXTERN(ion_bb_opencv_median_blur); extern "C" ION_EXPORT -int display(halide_buffer_t *in, int width, int height, int idx, halide_buffer_t *out) { +int ion_bb_opencv_display(halide_buffer_t *in, int width, int height, int idx, halide_buffer_t *out) { + auto& cv(ion::bb::OpenCV::get_instance()); + if (!cv.is_available()) { + ion::log::error("OpenCV is not available"); + return -1; + } + if (in->is_bounds_query()) { in->dim[0].min = 0; in->dim[0].extent = 3; // RGB @@ -74,14 +81,21 @@ int display(halide_buffer_t *in, int width, int height, int idx, halide_buffer_t in->dim[2].min = 0; in->dim[2].extent = height; } else { - cv::Mat img(std::vector{height, width}, CV_8UC3, in->host); - cv::imshow("img" + std::to_string(idx), img); - cv::waitKey(1); + auto img = cv.cvCreateMatHeader(height, width, CV_MAKETYPE(CV_8U, 3)); + cv.cvSetData(img, in->host, 3*width*sizeof(uint8_t)); + + auto name = "img" + std::to_string(idx); + cv.cvShowImage(name.c_str(), img); + cv.cvWaitKey(1); + + cv.cvReleaseMat(&img); } return 0; } +ION_REGISTER_EXTERN(ion_bb_opencv_display); +#undef ION_EXPORT #undef ION_REGISTER_EXTERN #endif // ION_BB_OPENCV_RT_H diff --git a/src/bb/opencv_loader.h b/src/bb/opencv_loader.h new file mode 100644 index 00000000..a5cfb307 --- /dev/null +++ b/src/bb/opencv_loader.h @@ -0,0 +1,198 @@ +#ifndef ION_BB_OPENCV_LOADER_H +#define ION_BB_OPENCV_LOADER_H + +#include "dynamic_module.h" + +namespace { + +#define CV_CN_MAX 512 +#define CV_CN_SHIFT 3 +#define CV_DEPTH_MAX (1 << CV_CN_SHIFT) + +#define CV_8U 0 +#define CV_8S 1 +#define CV_16U 2 +#define CV_16S 3 +#define CV_32S 4 +#define CV_32F 5 +#define CV_64F 6 +#define CV_16F 7 + +#define CV_MAT_DEPTH_MASK (CV_DEPTH_MAX - 1) +#define CV_MAT_DEPTH(flags) ((flags) & CV_MAT_DEPTH_MASK) + +#define CV_MAKETYPE(depth,cn) (CV_MAT_DEPTH(depth) + (((cn)-1) << CV_CN_SHIFT)) +#define CV_MAKE_TYPE CV_MAKETYPE + +#define IMREAD_GRAYSCALE 0 +#define IMREAD_COLOR 1 + + +enum SmoothMethod_c +{ + /** linear convolution with \f$\texttt{size1}\times\texttt{size2}\f$ box kernel (all 1's). If + you want to smooth different pixels with different-size box kernels, you can use the integral + image that is computed using integral */ + CV_BLUR_NO_SCALE =0, + /** linear convolution with \f$\texttt{size1}\times\texttt{size2}\f$ box kernel (all + 1's) with subsequent scaling by \f$1/(\texttt{size1}\cdot\texttt{size2})\f$ */ + CV_BLUR =1, + /** linear convolution with a \f$\texttt{size1}\times\texttt{size2}\f$ Gaussian kernel */ + CV_GAUSSIAN =2, + /** median filter with a \f$\texttt{size1}\times\texttt{size1}\f$ square aperture */ + CV_MEDIAN =3, + /** bilateral filter with a \f$\texttt{size1}\times\texttt{size1}\f$ square aperture, color + sigma= sigma1 and spatial sigma= sigma2. If size1=0, the aperture square side is set to + cvRound(sigma2\*1.5)\*2+1. See cv::bilateralFilter */ + CV_BILATERAL =4 +}; + +} // anonymous + +namespace ion { +namespace bb { + +class OpenCV { + + using CvArr = void; + using CvMat = struct CvMat; + // To fo, remove c api function later, since it is deprecated + using cvCreateMatHeader_t = CvMat*(*)(int rows, int cols, int type); + using cvReleaseMat_t = void(*)(CvMat **mat); + using cvSetData_t = void(*)(CvArr* arr, void* data, int step); + using cvSplit_t = void(*)(const CvArr *src, CvArr *dst0, CvArr *dst1, CvArr *dst2, CvArr *dst3); + using cvPow_t = void(*) (const CvArr *src, CvArr *dst, double power); + using cvRepeat_t = void(*) (const CvArr *src, CvArr *dst); + using cvSmooth_t = void (*)(const CvArr* src, CvArr* dst, int smoothtype, int size1, int size2, double sigma1, double sigma2); + using cvShowImage_t = void(*)(const char* name, const CvArr* image); + using cvCvtColor_t = void(*)(const CvArr *src, CvArr *dst, int code); +// using cvSaveImage_t = void(*)(const char *filename, const CvArr *image, const int *params); +// using cvLoadImageM_t =CvMat*(*)(const char* filename, int iscolor); + using cvWaitKey_t = int(*)(int delay); + + using cvResize_t = void (*)(const CvArr *src, CvArr *dst, int interpolation); + using cvNormalize_t = void (*)(const CvArr* src, CvArr* dst, double alpha, double beta, int normtype, int dtype, const CvArr* mask ); + + public: + + static OpenCV &get_instance() { + static OpenCV instance; + return instance; + } + + OpenCV() : +#ifdef _WIN32 + // TODO: Determine OpenCV version dynamically + opencv_world_("opencv_world455", false) +#else + opencv_core_("opencv_core", false), + opencv_imgproc_("opencv_imgproc", false), + opencv_highgui_("opencv_highgui", false) +#endif + { + if (is_available()) { + init_symbols(); + } else { + + } + } + + void init_symbols() { +#ifdef _WIN32 + #define GET_SYMBOL(LOCAL_VAR, TARGET_SYMBOL) \ + LOCAL_VAR = opencv_world_.get_symbol(TARGET_SYMBOL); \ + if (LOCAL_VAR == nullptr) { \ + throw ::std::runtime_error( \ + TARGET_SYMBOL " is unavailable on opencv_world"); \ + } + + GET_SYMBOL(cvCreateMatHeader, "cvCreateMatHeader"); + GET_SYMBOL(cvReleaseMat, "cvReleaseMat"); + GET_SYMBOL(cvSetData, "cvSetData"); + GET_SYMBOL(cvSplit, "cvSplit"); + GET_SYMBOL(cvPow, "cvPow"); + GET_SYMBOL(cvRepeat, "cvRepeat"); + GET_SYMBOL(cvSmooth, "cvSmooth"); + GET_SYMBOL(cvResize, "cvResize"); + GET_SYMBOL(cvCvtColor, "cvCvtColor"); + GET_SYMBOL(cvShowImage, "cvShowImage"); + GET_SYMBOL(cvWaitKey, "cvWaitKey"); + + #undef GET_SYMBOL +#else + #define GET_SYMBOL(MODULE, LOCAL_VAR, TARGET_SYMBOL) \ + LOCAL_VAR = opencv_##MODULE##_.get_symbol(TARGET_SYMBOL); \ + if (LOCAL_VAR == nullptr) { \ + throw ::std::runtime_error( \ + TARGET_SYMBOL " is unavailable on " #MODULE); \ + } + + GET_SYMBOL(core, cvCreateMatHeader, "cvCreateMatHeader"); + GET_SYMBOL(core, cvReleaseMat, "cvReleaseMat"); + GET_SYMBOL(core, cvSetData, "cvSetData"); + GET_SYMBOL(core, cvSplit, "cvSplit"); + GET_SYMBOL(core, cvPow, "cvPow"); + GET_SYMBOL(core, cvRepeat, "cvRepeat"); + GET_SYMBOL(imgproc, cvSmooth, "cvSmooth"); + GET_SYMBOL(imgproc, cvResize, "cvResize"); + GET_SYMBOL(imgproc, cvNormalize, "cvNormalize"); //obsolete + GET_SYMBOL(imgproc, cvCvtColor, "cvCvtColor"); + GET_SYMBOL(highgui, cvShowImage, "cvShowImage"); + GET_SYMBOL(highgui, cvWaitKey, "cvWaitKey"); + #undef GET_SYMBOL + +#endif + } + + bool is_available(void) const { +#if _WIN32 + return opencv_world_.is_available(); +#else + return opencv_core_.is_available() && opencv_imgproc_.is_available() && opencv_highgui_.is_available(); +#endif + } + +private: +#if _WIN32 + ion::DynamicModule opencv_world_; +#else + ion::DynamicModule opencv_core_; + ion::DynamicModule opencv_imgproc_; + ion::DynamicModule opencv_highgui_; +#endif + +public: + cvCreateMatHeader_t cvCreateMatHeader; + cvReleaseMat_t cvReleaseMat; + cvSetData_t cvSetData; + cvSplit_t cvSplit; + cvPow_t cvPow; + cvRepeat_t cvRepeat; + cvSmooth_t cvSmooth; + cvResize_t cvResize; + cvNormalize_t cvNormalize; + cvShowImage_t cvShowImage; + cvWaitKey_t cvWaitKey; + cvCvtColor_t cvCvtColor; + + + +}; + +int hl2cv_type(halide_type_t hl_type, int channel) { + if (hl_type.code != halide_type_uint) { + return -1; + } + if (hl_type.bits == 8) { + return CV_MAKETYPE(CV_8U, channel); + } else if (hl_type.bits == 16) { + return CV_MAKETYPE(CV_16U, channel); + } else { + return -1; + } +} + +} // bb +} // ion + +#endif diff --git a/src/dynamic_module.h b/src/dynamic_module.h index 4dcc2c06..c60057a1 100644 --- a/src/dynamic_module.h +++ b/src/dynamic_module.h @@ -18,6 +18,8 @@ #define ION_DYNAMIC_MODULE_EXT ".so" #endif +#include "log.h" + namespace ion { class DynamicModule { @@ -29,7 +31,7 @@ class DynamicModule { using Handle = void*; #endif - DynamicModule(const std::string& module_name_or_path) { + DynamicModule(const std::string& module_name_or_path, bool essential = true) { if (module_name_or_path == "") { handle_ = nullptr; return; @@ -43,18 +45,18 @@ class DynamicModule { } // TODO: WIP: test moduel_name_or_path using std::filesystem - #ifdef _WIN32 handle_ = LoadLibraryA(target.c_str()); - if (handle_ == nullptr) { - throw std::runtime_error(getErrorString()); - } #else handle_ = dlopen(target.c_str(), RTLD_NOW); +#endif if (handle_ == nullptr) { - throw std::runtime_error(getErrorString()); + if (essential) { + throw std::runtime_error(getErrorString()); + } else { + log::warn("Not found inessential library {} : {}", target, getErrorString()); + } } -#endif } ~DynamicModule() { @@ -64,6 +66,10 @@ class DynamicModule { } } + bool is_available(void) const { + return handle_ != NULL; + } + template T get_symbol(const std::string &symbol_name) const { #if defined(_WIN32) diff --git a/test/display.cc b/test/display.cc new file mode 100644 index 00000000..3dad305b --- /dev/null +++ b/test/display.cc @@ -0,0 +1,42 @@ +#include "ion/ion.h" + +using namespace ion; + +int main() +{ + try { + int32_t width = 1024, height = 1024; + + ion::Buffer in(std::vector{width, height, 3}); + ion::Buffer r = ion::Buffer::make_scalar(); + + for (int y=0; y