Skip to content

Rewrite the fns_candy_style_transfer example so that it doesn't depends on libpng on Windows #163

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Oct 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 17 additions & 17 deletions .pipelines/OneBranch.Official.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,17 @@ extends:
minor: '0'
exclude_commit: true

- script: |
set -e -x
apt-get update
apt-get install -y cmake gcc g++ libpng-dev libjpeg-turbo8-dev
curl -O -L https://github.com/microsoft/onnxruntime/releases/download/v{{ parameters.ortversion }}/onnxruntime-linux-x64-{{ parameters.ortversion }}.tgz
mkdir onnxruntimebin
cd onnxruntimebin
tar --strip=1 -zxvf ../onnxruntime-linux-x64-{{ parameters.ortversion }}.tgz
displayName: Download onnxruntime
workingDirectory: '$(Build.BinariesDirectory)'
- task: Bash@3
displayName: 'Download ONNX Runtime Binaries'
inputs:
targetType: filePath
filePath: ci_build/download_ort_release_and_install_deps.sh
arguments: '-i ${{ parameters.ortversion }}'
workingDirectory: '$(Build.BinariesDirectory)'

- script: |
set -e -x
cmake $(Build.SourcesDirectory)/c_cxx -DCMAKE_BUILD_TYPE=Release -DONNXRUNTIME_ROOTDIR=$(Build.BinariesDirectory)/onnxruntimebin
CFLAGS="-O2 -Wall -Wextra -Werror" CXXFLAGS="-O2 -Wall -Wextra -Werror" cmake $(Build.SourcesDirectory)/c_cxx -DCMAKE_BUILD_TYPE=Release -DONNXRUNTIME_ROOTDIR=$(Build.BinariesDirectory)/onnxruntimebin
make -j$(nproc)
displayName: build
workingDirectory: '$(Build.BinariesDirectory)'
Expand Down Expand Up @@ -130,12 +127,15 @@ extends:
@echo ##vso[task.setvariable variable=vslatest]%vslatest%
@echo ##vso[task.setvariable variable=vsdevcmd]%vsdevcmd%
displayName: 'locate vsdevcmd via vswhere'
- script: |
curl -L -o onnxruntime.zip https://github.com/microsoft/onnxruntime/releases/download/v{{ parameters.ortversion }}/onnxruntime-win-x64-{{ parameters.ortversion }}.zip
7z x onnxruntime.zip
move onnxruntime-win-x64-{{ parameters.ortversion }} onnxruntimebin
workingDirectory: '$(Build.BinariesDirectory)'
displayName: 'Install python modules'

- task: PowerShell@2
displayName: 'Download ONNX Runtime'
inputs:
targetType: filePath
filePath: ci_build/download_ort_release.ps1
arguments: '${{ parameters.ortversion }}'
workingDirectory: '$(Build.BinariesDirectory)'

#TODO: use the vsdevcmd variable
- script: |
call $(vsdevcmd)
Expand Down
34 changes: 17 additions & 17 deletions .pipelines/OneBranch.PullRequest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,17 @@ extends:
minor: '0'
exclude_commit: true

- script: |
set -e -x
apt-get update
apt-get install -y cmake gcc g++ libpng-dev libjpeg-turbo8-dev
curl -O -L https://github.com/microsoft/onnxruntime/releases/download/v{{ parameters.ortversion }}/onnxruntime-linux-x64-{{ parameters.ortversion }}.tgz
mkdir onnxruntimebin
cd onnxruntimebin
tar --strip=1 -zxvf ../onnxruntime-linux-x64-{{ parameters.ortversion }}.tgz
displayName: Download onnxruntime
workingDirectory: '$(Build.BinariesDirectory)'
- task: Bash@3
displayName: 'Download ONNX Runtime Binaries'
inputs:
targetType: filePath
filePath: ci_build/download_ort_release_and_install_deps.sh
arguments: '-i ${{ parameters.ortversion }}'
workingDirectory: '$(Build.BinariesDirectory)'

- script: |
set -e -x
cmake $(Build.SourcesDirectory)/c_cxx -DCMAKE_BUILD_TYPE=Release -DONNXRUNTIME_ROOTDIR=$(Build.BinariesDirectory)/onnxruntimebin
CFLAGS="-O2 -Wall -Wextra -Werror" CXXFLAGS="-O2 -Wall -Wextra -Werror" cmake $(Build.SourcesDirectory)/c_cxx -DCMAKE_BUILD_TYPE=Release -DONNXRUNTIME_ROOTDIR=$(Build.BinariesDirectory)/onnxruntimebin
make -j$(nproc)
displayName: build
workingDirectory: '$(Build.BinariesDirectory)'
Expand Down Expand Up @@ -130,12 +127,15 @@ extends:
@echo ##vso[task.setvariable variable=vslatest]%vslatest%
@echo ##vso[task.setvariable variable=vsdevcmd]%vsdevcmd%
displayName: 'locate vsdevcmd via vswhere'
- script: |
curl -L -o onnxruntime.zip https://github.com/microsoft/onnxruntime/releases/download/v{{ parameters.ortversion }}/onnxruntime-win-x64-{{ parameters.ortversion }}.zip
7z x onnxruntime.zip
move onnxruntime-win-x64-{{ parameters.ortversion }} onnxruntimebin
workingDirectory: '$(Build.BinariesDirectory)'
displayName: 'Download onnxruntime binary'

- task: PowerShell@2
displayName: 'Download ONNX Runtime'
inputs:
targetType: filePath
filePath: ci_build/download_ort_release.ps1
arguments: '${{ parameters.ortversion }}'
workingDirectory: '$(Build.BinariesDirectory)'

#TODO: use the vsdevcmd variable
- script: |
call $(vsdevcmd)
Expand Down
41 changes: 27 additions & 14 deletions c_cxx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ option(onnxruntime_USE_NUPHAR "Build with Nuphar" OFF)
option(onnxruntime_USE_TENSORRT "Build with TensorRT support" OFF)
option(LIBPNG_ROOTDIR "libpng root dir")
option(ONNXRUNTIME_ROOTDIR "onnxruntime root dir")
include(FetchContent)

set(CMAKE_CXX_STANDARD 17)

Expand All @@ -32,23 +33,35 @@ if(NOT ONNXRUNTIME_ROOTDIR)
endif()
endif()

if(WIN32)
add_library(wil INTERFACE)


FetchContent_Declare(
microsoft_wil
URL https://github.com/microsoft/wil/archive/refs/tags/v1.0.220914.1.zip
)
FetchContent_Populate(microsoft_wil)
target_include_directories(wil INTERFACE ${microsoft_wil_SOURCE_DIR}/include)
set(WIL_LIB wil)
endif()

#TODO: we should only need one of them.
include_directories("${ONNXRUNTIME_ROOTDIR}/include" "${ONNXRUNTIME_ROOTDIR}/include/onnxruntime/core/session")
link_directories("${ONNXRUNTIME_ROOTDIR}/lib")

#if JPEG lib is available, we'll use it for image decoding, otherwise we'll use WIC
find_package(JPEG)
if(LIBPNG_ROOTDIR)
set(PNG_FOUND true)
if(WIN32)
set(PNG_LIBRARIES debug libpng16_d optimized libpng16)
else()
set(PNG_LIBRARIES png16)
endif()
set(PNG_INCLUDE_DIRS "${LIBPNG_ROOTDIR}/include")
set(PNG_LIBDIR "${LIBPNG_ROOTDIR}/lib")
else()
find_package(PNG)
# On Linux the samples use libjpeg and libpng for decoding images.
# On Windows they use Windows Image Component(WIC)
if(NOT WIN32)
find_package(JPEG)
if(LIBPNG_ROOTDIR)
set(PNG_FOUND true)
set(PNG_LIBRARIES png16)
set(PNG_INCLUDE_DIRS "${LIBPNG_ROOTDIR}/include")
set(PNG_LIBDIR "${LIBPNG_ROOTDIR}/lib")
else()
find_package(PNG)
endif()
endif()

if(onnxruntime_USE_CUDA)
Expand Down Expand Up @@ -80,7 +93,7 @@ if(WIN32)
add_subdirectory(MNIST)
endif()
add_subdirectory(squeezenet)
if(PNG_FOUND)
if(WIN32 OR PNG_FOUND)
add_subdirectory(fns_candy_style_transfer)
endif()
#missing experimental_onnxruntime_cxx_api.h
Expand Down
9 changes: 7 additions & 2 deletions c_cxx/fns_candy_style_transfer/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

add_executable(fns_candy_style_transfer "fns_candy_style_transfer.c")
add_executable(fns_candy_style_transfer "fns_candy_style_transfer.c" "image_file.h")
if(WIN32)
target_sources(fns_candy_style_transfer PRIVATE image_file_wic.cc)
else()
target_sources(fns_candy_style_transfer PRIVATE image_file_libpng.c)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my understanding is that libpng things (PNG_LIBRARIES, PNG_INCLUDE_DIRS, PNG_LIBDIR) aren't used on windows. perhaps it would be clearer to put them in this block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, You are right.

endif()
target_include_directories(fns_candy_style_transfer PRIVATE ${PROJECT_SOURCE_DIR}/include ${PNG_INCLUDE_DIRS})
target_link_libraries(fns_candy_style_transfer PRIVATE onnxruntime ${PNG_LIBRARIES})
target_link_libraries(fns_candy_style_transfer PRIVATE onnxruntime ${PNG_LIBRARIES} ${WIL_LIB})
if(PNG_LIBDIR)
target_link_directories(fns_candy_style_transfer PRIVATE ${PNG_LIBDIR})
endif()
120 changes: 15 additions & 105 deletions c_cxx/fns_candy_style_transfer/fns_candy_style_transfer.c
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include <assert.h>
#include <png.h>
#include <stdio.h>

#include "onnxruntime_c_api.h"
Expand All @@ -11,6 +10,7 @@
#endif
#include <objbase.h>
#endif
#include "image_file.h"

#ifdef _WIN32
#define tcscmp wcscmp
Expand Down Expand Up @@ -39,10 +39,11 @@ const OrtApi* g_ort = NULL;
* \param output A float array. should be freed by caller after use
* \param output_count Array length of the `output` param
*/
static void hwc_to_chw(const png_byte* input, size_t h, size_t w, float** output, size_t* output_count) {
void hwc_to_chw(const uint8_t* input, size_t h, size_t w, float** output, size_t* output_count) {
size_t stride = h * w;
*output_count = stride * 3;
float* output_data = (float*)malloc(*output_count * sizeof(float));
assert(output_data != NULL);
for (size_t i = 0; i != stride; ++i) {
for (size_t c = 0; c != 3; ++c) {
output_data[c * stride + i] = input[i * 3 + c];
Expand All @@ -58,121 +59,30 @@ static void hwc_to_chw(const png_byte* input, size_t h, size_t w, float** output
* \param w image width
* \param output A byte array. should be freed by caller after use
*/
static void chw_to_hwc(const float* input, size_t h, size_t w, png_bytep* output) {
static void chw_to_hwc(const float* input, size_t h, size_t w, uint8_t** output) {
size_t stride = h * w;
png_bytep output_data = (png_bytep)malloc(stride * 3);
for (int c = 0; c != 3; ++c) {
uint8_t* output_data = (uint8_t*)malloc(stride * 3);
assert(output_data != NULL);
for (size_t c = 0; c != 3; ++c) {
size_t t = c * stride;
for (size_t i = 0; i != stride; ++i) {
float f = input[t + i];
if (f < 0.f || f > 255.0f) f = 0;
output_data[i * 3 + c] = (png_byte)f;
output_data[i * 3 + c] = (uint8_t)f;
}
}
*output = output_data;
}

/**
* \param out should be freed by caller after use
* \param output_count Array length of the `out` param
*/
static int read_png_file(const char* input_file, size_t* height, size_t* width, float** out, size_t* output_count) {
png_image image; /* The control structure used by libpng */
/* Initialize the 'png_image' structure. */
memset(&image, 0, (sizeof image));
image.version = PNG_IMAGE_VERSION;
if (png_image_begin_read_from_file(&image, input_file) == 0) {
return -1;
}
png_bytep buffer;
image.format = PNG_FORMAT_BGR;
size_t input_data_length = PNG_IMAGE_SIZE(image);
if (input_data_length != 720 * 720 * 3) {
printf("input_data_length:%zd\n", input_data_length);
return -1;
}
buffer = (png_bytep)malloc(input_data_length);
memset(buffer, 0, input_data_length);
if (png_image_finish_read(&image, NULL /*background*/, buffer, 0 /*row_stride*/, NULL /*colormap*/) == 0) {
return -1;
}
hwc_to_chw(buffer, image.height, image.width, out, output_count);
free(buffer);
*width = image.width;
*height = image.height;
return 0;
}

/**
* \param tensor should be a float tensor in [N,C,H,W] format
*/
static int write_tensor_to_png_file(OrtValue* tensor, const char* output_file) {
struct OrtTensorTypeAndShapeInfo* shape_info;
ORT_ABORT_ON_ERROR(g_ort->GetTensorTypeAndShape(tensor, &shape_info));
size_t dim_count;
ORT_ABORT_ON_ERROR(g_ort->GetDimensionsCount(shape_info, &dim_count));
if (dim_count != 4) {
printf("output tensor must have 4 dimensions");
return -1;
}
int64_t dims[4];
ORT_ABORT_ON_ERROR(g_ort->GetDimensions(shape_info, dims, sizeof(dims) / sizeof(dims[0])));
if (dims[0] != 1 || dims[1] != 3) {
printf("output tensor shape error");
return -1;
}
float* f;
ORT_ABORT_ON_ERROR(g_ort->GetTensorMutableData(tensor, (void**)&f));
png_bytep model_output_bytes;
png_image image;
memset(&image, 0, (sizeof image));
image.version = PNG_IMAGE_VERSION;
image.format = PNG_FORMAT_BGR;
image.height = (png_uint_32)dims[2];
image.width = (png_uint_32)dims[3];
chw_to_hwc(f, image.height, image.width, &model_output_bytes);
int ret = 0;
if (png_image_write_to_file(&image, output_file, 0 /*convert_to_8bit*/, model_output_bytes, 0 /*row_stride*/,
NULL /*colormap*/) == 0) {
printf("write to '%s' failed:%s\n", output_file, image.message);
ret = -1;
}
free(model_output_bytes);
return ret;
}

static void usage() { printf("usage: <model_path> <input_file> <output_file> [cpu|cuda|dml] \n"); }

#ifdef _WIN32
static char* convert_string(const wchar_t* input) {
size_t src_len = wcslen(input) + 1;
if (src_len > INT_MAX) {
printf("size overflow\n");
abort();
}
const int len = WideCharToMultiByte(CP_ACP, 0, input, (int)src_len, NULL, 0, NULL, NULL);
assert(len > 0);
char* ret = (char*)malloc(len);
assert(ret != NULL);
const int r = WideCharToMultiByte(CP_ACP, 0, input, (int)src_len, ret, len, NULL, NULL);
assert(len == r);
return ret;
}
#endif

int run_inference(OrtSession* session, const ORTCHAR_T* input_file, const ORTCHAR_T* output_file) {
size_t input_height;
size_t input_width;
float* model_input;
size_t model_input_ele_count;
#ifdef _WIN32
const char* output_file_p = convert_string(output_file);
const char* input_file_p = convert_string(input_file);
#else
const char* output_file_p = output_file;
const char* input_file_p = input_file;
#endif
if (read_png_file(input_file_p, &input_height, &input_width, &model_input, &model_input_ele_count) != 0) {

if (read_image_file(input_file, &input_height, &input_width, &model_input, &model_input_ele_count) != 0) {
return -1;
}
if (input_height != 720 || input_width != 720) {
Expand Down Expand Up @@ -204,16 +114,16 @@ int run_inference(OrtSession* session, const ORTCHAR_T* input_file, const ORTCHA
ORT_ABORT_ON_ERROR(g_ort->IsTensor(output_tensor, &is_tensor));
assert(is_tensor);
int ret = 0;
if (write_tensor_to_png_file(output_tensor, output_file_p) != 0) {
float* output_tensor_data = NULL;
ORT_ABORT_ON_ERROR(g_ort->GetTensorMutableData(output_tensor, (void**)&output_tensor_data));
uint8_t* output_image_data = NULL;
chw_to_hwc(output_tensor_data, 720, 720, &output_image_data);
if (write_image_file(output_image_data, 720, 720, output_file) != 0) {
ret = -1;
}
g_ort->ReleaseValue(output_tensor);
g_ort->ReleaseValue(input_tensor);
free(model_input);
#ifdef _WIN32
free(input_file_p);
free(output_file_p);
#endif // _WIN32
return ret;
}

Expand Down
28 changes: 28 additions & 0 deletions c_cxx/fns_candy_style_transfer/image_file.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once
#include "onnxruntime_c_api.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* \param out should be freed by caller after use
* \param output_count Array length of the `out` param
*/
int read_image_file(_In_z_ const ORTCHAR_T* input_file, _Out_ size_t* height, _Out_ size_t* width, _Outptr_ float** out,
_Out_ size_t* output_count);


int write_image_file(_In_ uint8_t* model_output_bytes, unsigned int height,
unsigned int width, _In_z_ const ORTCHAR_T* output_file);

/**
* convert input from HWC format to CHW format
* \param input A single image. The byte array has length of 3*h*w
* \param h image height
* \param w image width
* \param output A float array. should be freed by caller after use
* \param output_count Array length of the `output` param
*/
void hwc_to_chw(const uint8_t* input, size_t h, size_t w, float** output, size_t* output_count);
#ifdef __cplusplus
}
#endif
Loading