commit 05505a82793ddcc4c89909d81a2c1c1c57034b6a Author: Joao Carreira Date: Tue Jun 18 17:41:08 2024 +0100 feat: add examples diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b0639c7 --- /dev/null +++ b/.clang-format @@ -0,0 +1,156 @@ +--- +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: AcrossEmptyLines +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortEnumsOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +QualifierAlignment: Left +BraceWrapping: + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: true + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeConceptDeclarations: true +BreakBeforeInheritanceComma: false +BreakInheritanceList: AfterColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 120 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DeriveLineEnding: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +EmptyLineBeforeAccessModifier: LogicalBlock +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: "^<.*" + Priority: 2 + - Regex: ".*" + Priority: 3 +IncludeIsMainRegex: "([-_](test|unittest))?$" +IndentCaseLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentRequires: false +IndentWrappedFunctionNames: false +InsertNewlineAtEOF: true +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +LineEnding: LF +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - "c++" + - "C++" + CanonicalDelimiter: "" + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + CanonicalDelimiter: "" + BasedOnStyle: google +ReferenceAlignment: Left +ReflowComments: true +ShortNamespaceLines: 0 +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeCaseColon: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Latest +TabWidth: 4 +UseTab: Never diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..09dd586 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,47 @@ +--- +Checks: "*,-modernize-use-nodiscard,-llvmlibc-*,-fuchsia-trailing-return,-fuchsia*,-altera-*,-cppcoreguidelines-pro-bounds-constant-array-index,-bugprone-implicit-widening-of-multiplication-result" +WarningsAsErrors: "" +HeaderFilterRegex: "" +AnalyzeTemporaryDtors: false +FormatStyle: file +CheckOptions: + - key: readability-braces-around-statements.ShortStatementLines + value: "1" + - key: hicpp-braces-around-statements.ShortStatementLines + value: "1" + - key: bugprone-use-after-move + value: "1" + - key: bugprone-assert-side-effect.AssertMacros + value: assert + - key: bugprone-suspicious-enum-usage.StrictMode + value: "0" + - key: bugprone-unused-return-value.CheckedFunctions + value: "::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty" + - key: cert-dcl59-cpp.HeaderFileExtensions + value: ",h,hh,hpp,hxx" + - key: cert-err09-cpp.CheckThrowTemporaries + value: "1" + - key: cert-err61-cpp.CheckThrowTemporaries + value: "1" + - key: cert-msc32-c.DisallowedSeedTypes + value: "time_t,std::time_t" + - key: cert-msc51-cpp.DisallowedSeedTypes + value: "time_t,std::time_t" + - key: cert-oop11-cpp.IncludeStyle + value: llvm + - key: cppcoreguidelines-no-malloc.Allocations + value: "::malloc;::calloc" + - key: cppcoreguidelines-no-malloc.Deallocations + value: "::free" + - key: cppcoreguidelines-no-malloc.Reallocations + value: "::realloc" + - key: cppcoreguidelines-pro-type-member-init.IgnoreArrays + value: "0" + - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions + value: "0" + - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor + value: "0" + - key: hicpp-special-member-functions.AllowMissingMoveFunctions + value: "0" + - key: hicpp-special-member-functions.AllowSoleDefaultDtor + value: "0" diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..25534c1 --- /dev/null +++ b/.clangd @@ -0,0 +1,3 @@ +CompileFlags: + Add: [-std=c++20] + CompilationDatabase: build/ diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..f406f22 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "C_Cpp.formatting": "default" +} \ No newline at end of file diff --git a/1028.png b/1028.png new file mode 100644 index 0000000..cef2008 Binary files /dev/null and b/1028.png differ diff --git a/1091.png b/1091.png new file mode 100644 index 0000000..97169e7 Binary files /dev/null and b/1091.png differ diff --git a/114.png b/114.png new file mode 100644 index 0000000..eec0658 Binary files /dev/null and b/114.png differ diff --git a/1175.png b/1175.png new file mode 100644 index 0000000..e61668d Binary files /dev/null and b/1175.png differ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..07303ff --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.13) + +# Project +project(onnxruntime_samples C CXX) + +set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_compile_options("-Wall") +add_compile_options("-Wextra") + +find_package(ONNXRUNTIME REQUIRED) +find_package(PNG REQUIRED) + +include_directories(${ONNXRUNTIME_INCLUDE_DIR} ${PNG_PNG_INCLUDE_DIR}) + +add_executable(mnist_example + mnist_example_cxx.cpp +) + +target_link_libraries(mnist_example + /opt/homebrew/lib/libonnxruntime.dylib + ${PNG_LIBRARY_RELEASE} +) + +include_directories(${PNG_INCLUDE_DIR}) + diff --git a/mnist_example_c.cpp b/mnist_example_c.cpp new file mode 100644 index 0000000..61c1b06 --- /dev/null +++ b/mnist_example_c.cpp @@ -0,0 +1,220 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "onnxruntime/cpu_provider_factory.h" +#include "onnxruntime/onnxruntime_c_api.h" +#include "onnxruntime/onnxruntime_cxx_api.h" + +// https://onnxruntime.ai/docs/api/c/namespace_ort.html + +const OrtApi* g_ort = NULL; + +#define ORT_ABORT_ON_ERROR(expr) \ + { \ + OrtStatus* onnx_status = (expr); \ + if (onnx_status != NULL) { \ + const char* msg = g_ort->GetErrorMessage(onnx_status); \ + std::cout << msg << std::endl; \ + g_ort->ReleaseStatus(onnx_status); \ + abort(); \ + } \ + } + +template +static void softmax(T& input) +{ + auto rowmax = *std::max_element(input.begin(), input.end()); + std::vector result(input.size()); + float sum{0.0}; + for (std::size_t i = 0; i != input.size(); ++i) { + sum += result[i] = std::exp(input[i] - rowmax); + } + for (std::size_t i = 0; i != input.size(); ++i) { + input[i] = result[i] / sum; + } +} + +/** + * get_output_data from HWC format + */ +void get_output_data( + const png_byte* input, + std::size_t height, + std::size_t width, + float** output, + std::size_t* output_count) +{ + std::size_t stride = height * width; + *output_count = stride * 1; + float* output_data = (float*)malloc(*output_count * sizeof(float)); + + for (std::size_t i = 0; i != stride; ++i) { + output_data[i] = input[i * 3] == 0 ? 1.0f : 0.0f; + std::string temp = input[i * 3] == 0 ? "**" : "--"; + std::cout << temp; + if ((i + 1) % width == 0) { + std::cout << std::endl; + }; + } + *output = output_data; +} + +/** + * \param out should be freed by caller after use + * \param output_count Array length of the `out` param + */ +auto read_png_file( + const char* input_file, + std::size_t* height, + std::size_t* width, + float** out, + std::size_t* output_count) -> int +{ + 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; + std::size_t input_data_length = PNG_IMAGE_SIZE(image); + if (input_data_length != 28 * 28 * 3) { + std::cout << "input_data_length: " << input_data_length << std::endl; + 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; + } + + get_output_data(buffer, image.height, image.width, out, output_count); + free(buffer); + *width = image.width; + *height = image.height; + return 0; +} + +auto run_inference(OrtSession* session, const ORTCHAR_T* input_file) -> int +{ + std::size_t input_height; + std::size_t input_width; + float* model_input; + std::size_t model_input_ele_count; + + const char* input_file_p = input_file; + + if (read_png_file(input_file_p, &input_height, &input_width, &model_input, &model_input_ele_count) != 0) { + return -1; + } + + if (input_height != 28 || input_width != 28) { + std::cout << "please resize to image to 28x28" << std::endl; + free(model_input); + return -1; + } + + OrtMemoryInfo* memory_info; + ORT_ABORT_ON_ERROR(g_ort->CreateCpuMemoryInfo(OrtArenaAllocator, OrtMemTypeDefault, &memory_info)) + + const std::int64_t input_shape[] = {1, 1, 28, 28}; + const std::size_t input_shape_len = sizeof(input_shape) / sizeof(input_shape[0]); + const std::size_t model_input_len = model_input_ele_count * sizeof(float); + + OrtValue* input_tensor = nullptr; + ORT_ABORT_ON_ERROR(g_ort->CreateTensorWithDataAsOrtValue( + memory_info, + model_input, + model_input_len, + input_shape, + input_shape_len, + ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, + &input_tensor)) + + assert(input_tensor != nullptr); + int is_tensor; + ORT_ABORT_ON_ERROR(g_ort->IsTensor(input_tensor, &is_tensor)); + assert(is_tensor); + g_ort->ReleaseMemoryInfo(memory_info); + const char* input_names[] = {"Input3"}; + const char* output_names[] = {"Plus214_Output_0"}; + + std::array results_{}; + int result_{0}; + const int64_t output_shape[] = {1, 10}; + const std::size_t output_shape_len = sizeof(output_shape) / sizeof(output_shape[0]); + + OrtValue* output_tensor = nullptr; + ORT_ABORT_ON_ERROR(g_ort->CreateTensorWithDataAsOrtValue( + memory_info, + results_.data(), + results_.size() * sizeof(float), + output_shape, + output_shape_len, + ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT, + &output_tensor)); + + ORT_ABORT_ON_ERROR(g_ort->Run( + session, nullptr, input_names, (const OrtValue* const*)&input_tensor, 1, output_names, 1, &output_tensor)); + assert(output_tensor != nullptr); + ORT_ABORT_ON_ERROR(g_ort->IsTensor(output_tensor, &is_tensor)); + assert(is_tensor); + + softmax(results_); + for (std::size_t i = 0; i < results_.size(); i++) { + std::cout << i << " : " << results_[i] << std::endl; + } + + result_ = std::distance(results_.begin(), std::max_element(results_.begin(), results_.end())); + std::cout << "Result : " << result_ << std::endl; + + int ret = 0; + g_ort->ReleaseValue(output_tensor); + g_ort->ReleaseValue(input_tensor); + free(model_input); + return ret; +} + +auto main(int argc, char* argv[]) -> int +{ + if (argc < 2) { + std::cout << "usage: " << std::endl; + return -1; + } + + g_ort = OrtGetApiBase()->GetApi(ORT_API_VERSION); + + ORTCHAR_T* model_path = "../model.onnx"; + + ORTCHAR_T* input_file = argv[1]; + OrtEnv* env{nullptr}; + ORT_ABORT_ON_ERROR(g_ort->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "test", &env)); + OrtSessionOptions* session_options; + ORT_ABORT_ON_ERROR(g_ort->CreateSessionOptions(&session_options)); + + OrtSession* session{nullptr}; + ORT_ABORT_ON_ERROR(g_ort->CreateSession(env, model_path, session_options, &session)); + std::size_t count{0}; + ORT_ABORT_ON_ERROR(g_ort->SessionGetInputCount(session, &count)); + assert(count == 1); + ORT_ABORT_ON_ERROR(g_ort->SessionGetOutputCount(session, &count)); + assert(count == 1); + + auto ret = run_inference(session, input_file); + g_ort->ReleaseSessionOptions(session_options); + g_ort->ReleaseSession(session); + g_ort->ReleaseEnv(env); + if (ret != 0) { + std::cout << "fail." << std::endl; + } + return 0; +} diff --git a/mnist_example_cxx.cpp b/mnist_example_cxx.cpp new file mode 100644 index 0000000..c85ac27 --- /dev/null +++ b/mnist_example_cxx.cpp @@ -0,0 +1,144 @@ +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// https://onnxruntime.ai/docs/api/c/namespace_ort.html +// https://github.com/microsoft/onnxruntime-inference-examples/ + +class ImageData; + +class OnnxMnist +{ +public: + OnnxMnist() noexcept = default; + + auto run(ImageData& image) noexcept -> std::size_t; + + static constexpr const int kWidth{28}; + static constexpr const int kHeight{28}; + +private: + // From session we can get the input and output count + Ort::Session session_{Ort::Env{}, "model.onnx", Ort::SessionOptions{nullptr}}; +}; + +class ImageData +{ +public: + explicit ImageData(std::string_view input_file); + + auto getData() noexcept -> std::span { return data_; } + auto getWidth() const noexcept { return width_; } + auto getHeight() const noexcept { return height_; } + +private: + std::size_t width_{0}; + std::size_t height_{0}; + + std::vector data_; +}; + +template +void softmax(std::span input) +{ + auto row_max = *std::ranges::max_element(input); + std::vector result(input.size()); + float sum{0.0}; + for (std::size_t i = 0; i != input.size(); ++i) { + sum += result[i] = std::exp(input[i] - row_max); + } + for (std::size_t i = 0; i != input.size(); ++i) { + input[i] = result[i] / sum; + } +} + +auto main(int argc, char* argv[]) -> int +{ + if (argc < 2) { + std::cout << "usage: " << std::endl; + return -1; + } + + std::string_view filename{argv[1]}; // NOLINT + ImageData image{filename}; + + if (image.getWidth() != OnnxMnist::kWidth || image.getHeight() != OnnxMnist::kHeight) { + std::cout << "Resize to image to 28x28" << std::endl; + return -1; + } + + OnnxMnist model; + auto result = model.run(image); + std::cout << "Result : " << result << std::endl; + return 0; +} + +auto OnnxMnist::run(ImageData& image) noexcept -> std::size_t +{ + const char* kInputNames[]{"Input3"}; // NOLINT + const char* kOutputNames[]{"Plus214_Output_0"}; // NOLINT + + constexpr std::array kInputShape{1, 1, kWidth, kHeight}; + constexpr std::array kOutputShape{1, 10}; + + const auto memory_info = Ort::MemoryInfo::CreateCpu(OrtDeviceAllocator, OrtMemTypeCPU); + + auto image_data = image.getData(); + Ort::Value const input_tensor = Ort::Value::CreateTensor( + memory_info, image_data.data(), image_data.size(), kInputShape.data(), kInputShape.size()); + + std::array results_{}; + auto output_tensor = Ort::Value::CreateTensor( + memory_info, results_.data(), results_.size(), kOutputShape.data(), kOutputShape.size()); + + const Ort::RunOptions run_options; + session_.Run(run_options, kInputNames, &input_tensor, 1, kOutputNames, &output_tensor, 1); + + softmax(results_); + for (std::size_t i = 0; i < results_.size(); i++) { + std::cout << i << " : " << results_[i] << std::endl; + } + + auto result = std::distance(results_.begin(), std::max_element(results_.begin(), results_.end())); + + return result; +} + +ImageData::ImageData(std::string_view input_file) +{ + // The control structure used by libpng + // Initialize the 'png_image' structure. + png_image image; + memset(&image, 0, (sizeof image)); + image.version = PNG_IMAGE_VERSION; + if (png_image_begin_read_from_file(&image, input_file.data()) == 0) { + return; + } + image.format = PNG_FORMAT_BGR; + + const std::size_t input_data_length = PNG_IMAGE_SIZE(image); // NOLINT + std::vector buffer(input_data_length, 0); + if (png_image_finish_read(&image, nullptr /*background*/, buffer.data(), 0 /*row_stride*/, nullptr /*colormap*/) == + 0) { + return; + } + + width_ = image.width; + height_ = image.height; + + const auto stride = height_ * width_; + data_.resize(height_ * width_); + + for (std::size_t i = 0; i < stride; ++i) { + data_[i] = buffer[i * 3] == 0 ? 1.0F : 0.0F; + } +} diff --git a/model.onnx b/model.onnx new file mode 100644 index 0000000..fc1a3f7 Binary files /dev/null and b/model.onnx differ