diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 88ec5b9c3..966681f02 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -29,6 +29,16 @@ if(USE_OPENCV) add_definitions(-DUSE_OPENCV=1) endif() +option(USE_LIBPNG "use PNG library" OFF) +if(NOT USE_OPENCV) + if(USE_LIBPNG) + find_package(PNG REQUIRED) + if(NOT PNG_FOUND) + message(SEND_ERROR "PNG library not found.") + endif() + add_definitions(-DUSE_LIBPNG=1) + endif() +endif() # Add yaml cpp option(YAML_CPP_BUILD_TESTS "Enable testing" OFF) @@ -49,13 +59,22 @@ file(GLOB SRCS "src/[a-zA-Z]*.cpp") file(GLOB PUBLIC_HEADERS "include/blueoil.hpp") file(GLOB PRIVATE_HEADERS "include/[a-zA-Z]*.hpp") if(NOT USE_OPENCV) - list(FILTER SRCS EXCLUDE REGEX ".*opencv.cpp$") - list(FILTER PRIVATE_HEADERS EXCLUDE REGEX ".*opencv.hpp$") + file(GLOB OPENCV_SRCS "src/*_opencv.cpp") + file(GLOB OPENCV_HEADERS "include/*_opencv.hpp") + list(REMOVE_ITEM SRCS ${OPENCV_SRCS}) + list(REMOVE_ITEM PRIVATE_HEADERS ${OPENCV_HEADERS}) +endif() +if(NOT USE_LIBPNG) + file(GLOB PNG_SRCS "src/*_png.cpp") + file(GLOB PNG_HEADERS "include/*_png.hpp") + list(REMOVE_ITEM SRCS ${PNG_SRCS}) + list(REMOVE_ITEM PRIVATE_HEADERS ${PNG_HEADERS}) endif() if(VERBOSE) message(STATUS "use_opencv: ${USE_OPENCV}") + message(STATUS "use_libpng: ${USE_LIBPNG}") message(STATUS "sources: ${SRCS}") message(STATUS "public_headers: ${PUBLIC_HEADERS}") message(STATUS "private_headers: ${PRIVATE_HEADERS}") @@ -67,6 +86,9 @@ include_directories(include yaml-cpp/include) if(USE_OPENCV) include_directories(${OpenCV_INCLUDE_DIRS}) endif() +if(USE_PNG) + include_directories(${PNG_INCLUDE_DIRS}) +endif() # Add dlk static lib. diff --git a/runtime/README.md b/runtime/README.md index 6a25189d0..c45c5a40b 100644 --- a/runtime/README.md +++ b/runtime/README.md @@ -1,9 +1,16 @@ +# Import model with dlk library + +- Choose a model to try from blueoil/dlk/examples/ + - for example: classification/lmnet_quantize_cifar10 +- make libdlk_x86.a with reference blueoil/dlk/README.md +- put libdlk_x86.a & meta.yaml to blueoil/runtime/examples/ + # Build blueoil static lib ``` $ mkdir build $ cd build # You can set `DLK_LIB_DIR` environment. -$ DLK_LIB_DIR=`pwd`/../examples/dlk_lib/ cmake ../ +$ DLK_LIB_DIR=`pwd`/../examples/ cmake ../ $ make $ make install $ tree output/ @@ -20,7 +27,7 @@ output/ $ mkdir build $ cd build # -DBUILD_SHARED_LIBS=ON -$ DLK_LIB_DIR=`pwd`/../examples/dlk_lib/ cmake -DBUILD_SHARED_LIBS=ON ../ +$ DLK_LIB_DIR=`pwd`/../examples/ cmake -DBUILD_SHARED_LIBS=ON ../ $ make $ make install $ tree output/ @@ -41,59 +48,47 @@ $ cd examples $ cp -R ../build/output/* ./ $ cmake . $ make -$ ./a.out +$ ./run -i cat.npy -c meta.yaml classes: -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 +airplane +automobile +bird +cat +deer +dog +frog +horse +ship +truck task: IMAGE.CLASSIFICATION expected input shape: 1 -128 -128 +32 +32 3 Run Results ! -0.100382 -0.0998879 -0.101126 -0.098727 -0.100418 -0.0999296 -0.100612 -0.0995238 -0.0996848 -0.0997096 +shape:1 10 +0.000105945 6.23502e-05 0.0323531 0.00360625 0.0124029 0.000231775 0.951004 8.7062e-05 9.84179e-05 4.80589e-05 ``` -Currentlly pre/post-process functions are NOT correctly implemented. -The value of Results! 0.100382, 0.0998879 will be changed after correct implementation. - - # Unit tests ``` $ cd build -$ DLK_LIB_DIR=`pwd`/../examples/dlk_lib/ cmake -DBUILD_SHARED_LIBS=ON ../ +$ DLK_LIB_DIR=`pwd`/../examples/ cmake -DBUILD_SHARED_LIBS=ON ../ $ make $ make test Running tests... Test project /blueoil/runtime/build Start 1: blueoil-test-tensor -1/4 Test #1: blueoil-test-tensor .............. Passed 0.00 sec +1/5 Test #1: blueoil-test-tensor .............. Passed 0.00 sec Start 2: blueoil-test-image -2/4 Test #2: blueoil-test-image ............... Passed 0.00 sec - Start 3: blueoil-test-opencv -3/4 Test #3: blueoil-test-opencv .............. Passed 0.04 sec +2/5 Test #2: blueoil-test-image ............... Passed 0.00 sec + Start 3: blueoil-test-npy +3/5 Test #3: blueoil-test-npy ................. Passed 0.00 sec Start 4: blueoil-test-resize -4/4 Test #4: blueoil-test-resize .............. Passed 0.04 sec +4/5 Test #4: blueoil-test-resize .............. Passed 0.00 sec Start 5: blueoil-test-data_processor 5/5 Test #5: blueoil-test-data_processor ...... Passed 0.00 sec diff --git a/runtime/examples/CMakeLists.txt b/runtime/examples/CMakeLists.txt index 9b73f4334..ea15bd5e1 100644 --- a/runtime/examples/CMakeLists.txt +++ b/runtime/examples/CMakeLists.txt @@ -7,10 +7,30 @@ set(CMAKE_CXX_COMPILER g++) set(CMAKE_CXX_FLAGS "-Wall -O3") set(CMAKE_CXX_STANDARD "11") -configure_file(${CMAKE_CURRENT_SOURCE_DIR}/meta.yaml meta.yaml COPYONLY) - link_directories(${CMAKE_CURRENT_SOURCE_DIR}/lib) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) -add_executable(a.out run.cpp) -target_link_libraries(a.out blueoil pthread) +add_executable(run run.cpp) + +if(USE_OPENCV) + find_package(OpenCV REQUIRED) + if(NOT OpenCV_FOUND) + message(SEND_ERROR "OpenCV library not found.") + endif() +endif() +if(USE_LIBPNG) + find_package(PNG REQUIRED) + if(NOT PNG_FOUND) + message(SEND_ERROR "PNG library not found.") + endif() +endif() + +if(USE_OPENCV) + target_link_libraries(run blueoil ${OpenCV_LIBS}) +else() + if(USE_LIBPNG) + target_link_libraries(run blueoil png pthread) + else() + target_link_libraries(run blueoil pthread) + endif() +endif() diff --git a/runtime/examples/cat.npy b/runtime/examples/cat.npy new file mode 100644 index 000000000..ad312a0f8 Binary files /dev/null and b/runtime/examples/cat.npy differ diff --git a/runtime/examples/cat.png b/runtime/examples/cat.png new file mode 100644 index 000000000..9e5e249be Binary files /dev/null and b/runtime/examples/cat.png differ diff --git a/runtime/examples/dlk_lib/libdlk_x86.a b/runtime/examples/dlk_lib/libdlk_x86.a deleted file mode 100644 index 54dbabac7..000000000 Binary files a/runtime/examples/dlk_lib/libdlk_x86.a and /dev/null differ diff --git a/runtime/examples/meta.yaml b/runtime/examples/meta.yaml deleted file mode 100644 index 249e698ac..000000000 --- a/runtime/examples/meta.yaml +++ /dev/null @@ -1,23 +0,0 @@ -CLASSES: -- '0' -- '1' -- '2' -- '3' -- '4' -- '5' -- '6' -- '7' -- '8' -- '9' -DATA_FORMAT: NHWC -IMAGE_SIZE: -- 128 -- 128 -POST_PROCESSOR: null -PRE_PROCESSOR: -- Resize: - size: - - 128 - - 128 -- DivideBy255: null -TASK: IMAGE.CLASSIFICATION diff --git a/runtime/examples/run.cpp b/runtime/examples/run.cpp index 060be5762..259020082 100644 --- a/runtime/examples/run.cpp +++ b/runtime/examples/run.cpp @@ -16,27 +16,34 @@ limitations under the License. #include #include #include +#include #include "blueoil.hpp" -// TODO: delete this func. it is for debug. -blueoil::Tensor RandomImage(int height, int width, int channel) { - blueoil::Tensor t({height, width, channel}); - std::vector& data = t.data(); - unsigned seed = 0; - for (size_t i = 0; i < data.size(); ++i) { - const float f_rand = static_cast (rand_r(&seed)) / static_cast (RAND_MAX) * 255; - data[i] = f_rand; +int main(int argc, char **argv) { + std::string imagefile, meta_yaml; + + for (int i = 1; i < (argc-1); i++) { + char *arg = argv[i]; + char *arg2 = argv[i+1]; + if ((arg[0] == '-') && (std::strlen(arg) == 2) && (arg2[0] != '-')) { + switch (arg[1]) { + case 'i': // image file (ex. raw_image.npy) + imagefile = std::string(arg2); + break; + case 'c': // config file (meta.yaml) + meta_yaml = std::string(arg2); + break; + } } + } - return t; -} - - - -int main() { - std::string meta_yaml = "meta.yaml"; + if (imagefile.empty() || meta_yaml.empty()) { + std::cerr << "Usage: a.out -i -c " << std::endl; + std::cerr << "ex) a.out -i raw_image.npy -c meta.yaml" << std::endl; + std::exit(1); + } blueoil::Predictor predictor = blueoil::Predictor(meta_yaml); @@ -51,14 +58,13 @@ int main() { for (int j : predictor.expected_input_shape) { std::cout << j << "\n"; } - - blueoil::Tensor random_image = RandomImage(256, 256, 3); + blueoil::Tensor image = blueoil::Tensor_loadImage(imagefile); std::cout << "Run" << std::endl; - blueoil::Tensor output = predictor.Run(random_image); + blueoil::Tensor output = predictor.Run(image); std::cout << "Results !" << std::endl; - for (float j : output.data()) { - std::cout << j << std::endl; - } + output.dump(); + + std::exit(0); } diff --git a/runtime/include/blueoil.hpp b/runtime/include/blueoil.hpp index fd1e94b75..5d00ab747 100644 --- a/runtime/include/blueoil.hpp +++ b/runtime/include/blueoil.hpp @@ -69,6 +69,8 @@ class Tensor { bool allclose(const Tensor &tensor, float rtol, float atol) const; }; +Tensor Tensor_loadImage(std::string filename); + // typedef Tensor (*TensorFunction)(Tensor&); typedef std::function Processor; diff --git a/runtime/include/blueoil_image.hpp b/runtime/include/blueoil_image.hpp index c675ab871..930e41b42 100644 --- a/runtime/include/blueoil_image.hpp +++ b/runtime/include/blueoil_image.hpp @@ -16,6 +16,7 @@ limitations under the License. #ifndef RUNTIME_INCLUDE_BLUEOIL_IMAGE_HPP_ #define RUNTIME_INCLUDE_BLUEOIL_IMAGE_HPP_ +#include #include "blueoil.hpp" namespace blueoil { @@ -26,6 +27,8 @@ enum ResizeFilter { RESIZE_FILTER_BI_LINEAR = 2, }; +Tensor LoadImage(const std::string filename); + Tensor Resize(const Tensor& image, const int width, const int height, const enum ResizeFilter filter); diff --git a/runtime/include/blueoil_npy.hpp b/runtime/include/blueoil_npy.hpp new file mode 100644 index 000000000..8526250bb --- /dev/null +++ b/runtime/include/blueoil_npy.hpp @@ -0,0 +1,30 @@ +/* Copyright 2019 The Blueoil Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=============================================================================*/ + +#ifndef RUNTIME_INCLUDE_BLUEOIL_NPY_HPP_ +#define RUNTIME_INCLUDE_BLUEOIL_NPY_HPP_ + +#include +#include "blueoil.hpp" + +namespace blueoil { +namespace npy { + +Tensor Tensor_fromNPYFile(const std::string filename); + +} // namespace npy +} // namespace blueoil + +#endif // RUNTIME_INCLUDE_BLUEOIL_NPY_HPP_ diff --git a/runtime/include/blueoil_png.hpp b/runtime/include/blueoil_png.hpp new file mode 100644 index 000000000..5dbad9286 --- /dev/null +++ b/runtime/include/blueoil_png.hpp @@ -0,0 +1,30 @@ +/* Copyright 2019 The Blueoil Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=============================================================================*/ + +#ifndef RUNTIME_INCLUDE_BLUEOIL_PNG_HPP_ +#define RUNTIME_INCLUDE_BLUEOIL_PNG_HPP_ + +#include +#include "blueoil.hpp" + +namespace blueoil { +namespace png { + +Tensor Tensor_fromPNGFile(const std::string filename); + +} // namespace png +} // namespace blueoil + +#endif // RUNTIME_INCLUDE_BLUEOIL_PNG_HPP_ diff --git a/runtime/src/blueoil.cpp b/runtime/src/blueoil.cpp index 006d0b996..2a68a6024 100644 --- a/runtime/src/blueoil.cpp +++ b/runtime/src/blueoil.cpp @@ -26,6 +26,7 @@ limitations under the License. #include #include "blueoil.hpp" +#include "blueoil_image.hpp" #include "blueoil_data_processor.hpp" #include "yaml-cpp/yaml.h" @@ -242,6 +243,10 @@ bool Tensor::allclose(const Tensor &tensor, float rtol, float atol) const { return true; } +Tensor Tensor_loadImage(std::string filename) { + return blueoil::image::LoadImage(filename); +} + // mapping process node to functions vector. void MappingProcess(const YAML::Node processors_node, std::vector* functions) { diff --git a/runtime/src/blueoil_image.cpp b/runtime/src/blueoil_image.cpp index a02a1f697..7a8977a3f 100644 --- a/runtime/src/blueoil_image.cpp +++ b/runtime/src/blueoil_image.cpp @@ -16,9 +16,17 @@ limitations under the License. #include #include #include +#include #include "blueoil.hpp" #include "blueoil_image.hpp" +#include "blueoil_npy.hpp" +#ifdef USE_OPENCV +#include "blueoil_opencv.hpp" +#endif +#ifdef USE_LIBPNG +#include "blueoil_png.hpp" +#endif namespace blueoil { namespace image { @@ -35,6 +43,31 @@ T clamp(const T x, const T lowerLimit, const T upperLimit) { return x; } + +Tensor LoadImage(const std::string filename) { + blueoil::Tensor tensor({0}); +#ifdef USE_OPENCV + cv::Mat img = cv::imread(filename, 1); // 1:force to RGB format + if (!img.empty()) { + return blueoil::opencv::Tensor_fromCVMat(img); + } +#elif USE_LIBPNG + tensor = blueoil::png::Tensor_fromPNGFile(filename); + if (tensor.shape()[0] > 0) { + return tensor; + } +#endif + tensor = blueoil::npy::Tensor_fromNPYFile(filename); + if (tensor.shape().size() != 3) { + throw std::runtime_error("npy image shape must be 3-dimention"); + } + int channels = tensor.shape()[2]; + if ((channels != 1) && (channels != 3)) { + throw std::runtime_error("npy image channels must be 1(grayscale) or 3(RGB)"); + } + return tensor; +} + /* * Resize Image (Nearest Neighbor) */ diff --git a/runtime/src/blueoil_npy.cpp b/runtime/src/blueoil_npy.cpp new file mode 100644 index 000000000..7f795f71c --- /dev/null +++ b/runtime/src/blueoil_npy.cpp @@ -0,0 +1,243 @@ +/* Copyright 2019 The Blueoil Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=============================================================================*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "blueoil.hpp" +#include "blueoil_npy.hpp" + + +namespace blueoil { +namespace npy { + +struct NPYheader_t { + std::vector shape; + std::string datatype; // |u1(uint8) or +void readNPYdata(std::ifstream &fin, const struct NPYheader_t &nh, + T *imagedata); + +Tensor Tensor_fromNPYFile(const std::string filename) { + std::ifstream fin(filename, std::ios::in | std::ios::binary); + std::stringstream ss; + if (!fin) { + ss << "Can't open file:" << filename; + throw std::runtime_error(ss.str()); + } + NPYheader_t nh = readNPYheader(fin); + + blueoil::Tensor tensor(nh.shape); + readNPYdata(fin, nh, tensor.dataAsArray()); + return tensor; +} + +// " ABC " => "ABC" +static std::string trim(const std::string str) { + const char *trimChara = " \t\r\n"; + auto left = str.find_first_not_of(trimChara); + if (left == std::string::npos) { + return ""; + } + auto right = str.find_last_not_of(trimChara); + return str.substr(left, right - left + 1); +} + +// "{...}" => "..." +std::string extractInner(std::string strdata, + std::string leftSep, std::string rightSep) { + auto braseFirstPos = strdata.find_first_of(leftSep); + auto braseLastPos = strdata.find_last_of(rightSep); + if ((braseFirstPos == std::string::npos) || + (braseLastPos == std::string::npos)) { + return ""; + } + return strdata.substr(braseFirstPos + 1, braseLastPos - braseFirstPos - 1); +} +std::string extractInner(std::string strdata, std::string sep) { + return extractInner(strdata, sep, sep); +} + +// "A,B,(C,D,E)" => "A","B","(C,E,E)" +std::vector jsonCommaSplit(std::string strdata) { + std::vector jsonVector; + std::string::size_type cur = 0, pos = 0; + for (cur = 0 ; cur < strdata.size() && (pos != std::string::npos) ; cur++) { + pos = strdata.find_first_of(",", cur); + if (pos == std::string::npos) { + pos = strdata.size(); + } + auto bCur = strdata.find_first_of("(", cur); + if (bCur != std::string::npos) { + if (bCur < pos) { + auto bCur2 = strdata.find_first_of(")", bCur + 1); + if (bCur2 != std::string::npos) { + auto bCur3 = strdata.find_first_of(",", bCur2 + 1); + pos = bCur3; + } else { + throw std::runtime_error("can't closing bracket"); + } + } + } + if (cur != pos) { + auto value = strdata.substr(cur, pos - cur); + jsonVector.push_back(trim(value)); + } + cur = pos; + } + return jsonVector; +} +// "'A': B" => "A" => "B" +std::pair jsonKeyValueSplit(std::string strdata) { + auto pos = strdata.find_first_of(":", 0); + if (pos == std::string::npos) { + return std::pair("", ""); // failed + } + std::string key = strdata.substr(0, pos); + std::string value = strdata.substr(pos + 1, strdata.size() - pos); + key = trim(key); + value = trim(value); + auto tmp = extractInner(key, "'"); + if (tmp.size() > 0) { + key = tmp; + } + tmp = extractInner(value, "'"); + if (tmp.size() > 0) { + value = tmp; + } + return std::pair(key, value); +} + +// support only flat-1d assoc-array +std::map parseJson(std::string jsondata) { + std::map jsonMap; + std::string innerBracesData = extractInner(jsondata, "{", "}"); + // std::cerr << innerBracesData << std::endl; + std::vector jsonList = jsonCommaSplit(innerBracesData); + for (auto jsonElem : jsonList) { + auto keyvalue = jsonKeyValueSplit(jsonElem); + if (keyvalue.first.size() > 0) { + jsonMap[keyvalue.first] = keyvalue.second; + } + } + return jsonMap; +} + +struct NPYheader_t readNPYheader(std::ifstream &fin) { + char sig[6]; + uint16_t ver; + uint16_t jsonlen; + std::stringstream ss; + NPYheader_t header; + fin.read(sig, 6); + if (std::memcmp(sig, "\x93NUMPY", 6) != 0) { + ss << "wrong npy signature:" << sig; + throw std::runtime_error(ss.str()); + } + fin.read(reinterpret_cast(&ver), sizeof(uint16_t)); + fin.read(reinterpret_cast(&jsonlen), sizeof(uint16_t)); + if ((* reinterpret_cast("\1\0")) != 1) { + // native order = big endian + ver = (ver << 8) | (ver >> 8); + jsonlen = (jsonlen << 8) | (jsonlen >> 8); + } + if (0x80 < (0x0a + jsonlen)) { + ss << "too long json length:" << jsonlen; + throw std::runtime_error(ss.str()); + } + std::string jsondata(jsonlen, '\0'); + if (!fin.read(reinterpret_cast(&(jsondata[0])), jsonlen)) { + ss << "too short file for jsonlen:" << jsonlen; + throw std::runtime_error(ss.str()); + } + // std::cerr << jsondata << std::endl; + // {'descr': '|u1', 'fortran_order': False, 'shape': (46, 70, 3), } + std::map jsonMap = parseJson(jsondata); + for (auto itr = jsonMap.begin(); itr != jsonMap.end(); ++itr) { + std::string key = itr->first, value = itr->second; + if (key == "descr") { + if ((value != "|u1") && (value != " numstrList = jsonCommaSplit(value); + if (numstrList.size() != 3) { + ss << "Wrong shape size:" << numstrList.size(); + throw std::runtime_error(ss.str()); + } + std::vector shape(numstrList.size()); + for (size_t i = 0 ; i < numstrList.size() ; i++) { + shape[i] = std::stoi(numstrList[i]); + } + header.shape = shape; + } else { + ss << "Unknown json key:" << key; + throw std::runtime_error(ss.str()); + } + } + return header; +} + +template +void readNPYdata(std::ifstream &fin, const struct NPYheader_t &nh, + T *data) { + if (data == NULL) { + throw std::runtime_error("data == NULL"); + } + int n = std::accumulate(nh.shape.begin(), nh.shape.end(), 1, std::multiplies()); + // only little-endian support + if (nh.datatype == "|u1") { + for (int i = 0 ; i < n ; ++i) { + int cc = fin.get(); + if (cc < 0) { + throw std::runtime_error("incompleted rgb-data"); + } + data[i] = cc; + } + } else if (nh.datatype == "(fvalue_char)); + } + } else { + throw std::runtime_error("unsupported type:"+nh.datatype); + } +} + +} // namespace npy +} // namespace blueoil diff --git a/runtime/src/blueoil_png.cpp b/runtime/src/blueoil_png.cpp new file mode 100644 index 000000000..476a2a018 --- /dev/null +++ b/runtime/src/blueoil_png.cpp @@ -0,0 +1,93 @@ +/* Copyright 2019 The Blueoil Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=============================================================================*/ + +#include +#include +#include +#include +#include "blueoil.hpp" +#include "blueoil_png.hpp" + + +namespace blueoil { +namespace png { + +Tensor Tensor_fromPNGFile(const std::string filename) { + std::stringstream ss; + png_uint_32 png_width, png_height; + int bpp, color_type, interlace_method; + FILE *fp = fopen(filename.c_str(), "rb"); + if (!fp) { + ss << "can't open file::" << filename; + throw std::runtime_error(ss.str()); + } + png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + if (!png_ptr) { + throw std::runtime_error("failed to png_create_read_struct"); + } + png_infop png_info_ptr = png_create_info_struct(png_ptr); + if (!png_info_ptr) { + png_destroy_read_struct(&png_ptr, NULL, NULL); + throw std::runtime_error("failed to png_create_info_struct"); + } + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL); + fclose(fp); + throw std::runtime_error("failed to png read"); + } + // read header + png_init_io(png_ptr, fp); + png_read_info(png_ptr, png_info_ptr); + + // force any PNG to PNG24(RGB format) + png_set_strip_alpha(png_ptr); + png_set_expand(png_ptr); + png_set_palette_to_rgb(png_ptr); + png_set_expand_gray_1_2_4_to_8(png_ptr); + png_set_gray_to_rgb(png_ptr); + png_read_update_info(png_ptr, png_info_ptr); + png_get_IHDR(png_ptr, png_info_ptr, &png_width, &png_height, &bpp, + &color_type, &interlace_method, NULL, NULL); + if (interlace_method != PNG_INTERLACE_NONE) { + png_set_interlace_handling(png_ptr); + } + assert(bpp == 8); + assert(color_type == 2); // PNG24 + blueoil::Tensor tensor({static_cast(png_height), + static_cast(png_width), 3}); + png_bytepp image_data = (png_bytepp) malloc(3 * png_height * sizeof(png_bytep)); + png_bytep image_data_rows = (png_bytep) malloc(3 * png_width * png_height); + for (png_uint_32 y = 0 ; y < png_height ; y++) { + image_data[y] = image_data_rows + 3 * y * png_width; + } + png_read_image(png_ptr, image_data); + for (png_uint_32 y = 0 ; y < png_height ; y++) { + png_bytep image_row = image_data[y]; + float *tensor_row = tensor.dataAsArray({static_cast(y), 0, 0}); + for (png_uint_32 i = 0 ; i < 3 * png_width ; i++) { + tensor_row[i] = image_row[i]; + } + } + // terminate + free(image_data_rows); + free(image_data); + fclose(fp); + png_destroy_read_struct(&png_ptr, &png_info_ptr, NULL); + return tensor; +} + +} // namespace png +} // namespace blueoil diff --git a/runtime/test/CMakeLists.txt b/runtime/test/CMakeLists.txt index a2523295e..634c76593 100644 --- a/runtime/test/CMakeLists.txt +++ b/runtime/test/CMakeLists.txt @@ -3,6 +3,13 @@ if(USE_OPENCV) if(NOT OpenCV_FOUND) message(SEND_ERROR "OpenCV library not found.") endif() +else() + if(USE_LIBPNG) + find_package(PNG REQUIRED) + if(NOT PNG_FOUND) + message(SEND_ERROR "PNG library not found.") + endif() + endif() endif() macro(blueoil_unittest item) @@ -10,15 +17,24 @@ macro(blueoil_unittest item) if(USE_OPENCV) target_link_libraries(blueoil-test-${item} blueoil ${OpenCV_LIBS}) else() - target_link_libraries(blueoil-test-${item} blueoil "-pthread") + if(USE_LIBPNG) + target_link_libraries(blueoil-test-${item} blueoil png pthread) + else() + target_link_libraries(blueoil-test-${item} blueoil pthread) + endif() endif() add_test(blueoil-test-${item} blueoil-test-${item}) endmacro(blueoil_unittest) blueoil_unittest(tensor) blueoil_unittest(image) +blueoil_unittest(npy) +if(USE_LIBPNG) + blueoil_unittest(png) +endif() if(USE_OPENCV) blueoil_unittest(opencv) + blueoil_unittest(load) endif() blueoil_unittest(resize) blueoil_unittest(data_processor) diff --git a/runtime/test/images/3x3colors-float.npy b/runtime/test/images/3x3colors-float.npy new file mode 100644 index 000000000..8447ddab4 Binary files /dev/null and b/runtime/test/images/3x3colors-float.npy differ diff --git a/runtime/test/images/3x3colors.npy b/runtime/test/images/3x3colors.npy new file mode 100644 index 000000000..5b2c403df Binary files /dev/null and b/runtime/test/images/3x3colors.npy differ diff --git a/runtime/test/images/testinput.png b/runtime/test/images/testinput.png new file mode 100644 index 000000000..5f78f4cc0 Binary files /dev/null and b/runtime/test/images/testinput.png differ diff --git a/runtime/test/test_load.cpp b/runtime/test/test_load.cpp new file mode 100644 index 000000000..51ec48eef --- /dev/null +++ b/runtime/test/test_load.cpp @@ -0,0 +1,77 @@ +/* Copyright 2019 The Blueoil Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=============================================================================*/ + +#include +#include + +#include "blueoil.hpp" +#include "blueoil_image.hpp" +#include "blueoil_opencv.hpp" +#include "test_util.hpp" + +float test_expect[3][8][8] = + { { // Red + {255, 0, 0, 0, 0, 0, 0, 0}, + {0, 255, 0, 0, 0, 0, 0, 0}, + {0, 0, 100, 0, 0, 0, 0, 0}, + {0, 0, 0, 100, 0, 0, 0, 0}, + {0, 0, 0, 0, 100, 0, 0, 0}, + {0, 0, 0, 0, 0, 100, 0, 0}, + {0, 0, 0, 0, 0, 0, 255, 0}, + {0, 0, 0, 0, 0, 0, 0, 255} + }, + { // Green + {0, 0, 0, 0, 0, 0, 0, 255}, + {0, 0, 0, 0, 0, 0, 255, 0}, + {0, 0, 0, 0, 0, 100, 0, 0}, + {0, 0, 0, 0, 100, 0, 0, 0}, + {0, 0, 0, 100, 0, 0, 0, 0}, + {0, 0, 100, 0, 0, 0, 0, 0}, + {0, 255, 0, 0, 0, 0, 0, 0}, + {255, 0, 0, 0, 0, 0, 0, 0} + }, + { // Blue + { 0, 0, 0, 255, 255, 0, 0, 0}, + { 0, 0, 0, 255, 255, 0, 0, 0}, + { 0, 0, 0, 255, 255, 0, 0, 0}, + {255, 255, 255, 100, 100, 255, 255, 255}, + {255, 255, 255, 100, 100, 255, 255, 255}, + { 0, 0, 0, 255, 255, 0, 0, 0}, + { 0, 0, 0, 255, 255, 0, 0, 0}, + { 0, 0, 0, 255, 255, 0, 0, 0} + } }; + +int test_load() { + // CHW (3-channel, height, width) + blueoil::Tensor input = blueoil::Tensor_loadImage("images/testinput.png"); + blueoil::Tensor expect({3, 8, 8}, reinterpret_cast(test_expect)); + expect = blueoil::util::Tensor_CHW_to_HWC(expect); + if (!input.allclose(expect)) { + std::cerr << "test_resize: input != expect" << std::endl; + blueoil::util::Tensor_HWC_to_CHW(input).dump(); + blueoil::util::Tensor_HWC_to_CHW(expect).dump(); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +int main(void) { + int status_code = 0; + status_code = test_load(); + if (status_code != EXIT_FAILURE) { + std::exit(status_code); + } + std::exit(EXIT_SUCCESS); +} diff --git a/runtime/test/test_npy.cpp b/runtime/test/test_npy.cpp new file mode 100644 index 000000000..bc260b39b --- /dev/null +++ b/runtime/test/test_npy.cpp @@ -0,0 +1,67 @@ +/* Copyright 2019 The Blueoil Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=============================================================================*/ + +#include +#include + +#include "blueoil.hpp" +#include "blueoil_npy.hpp" +#include "test_util.hpp" + +float test_expect[3][3][3] = + { { // Red + {255, 255, 255}, + {255, 170, 0}, + { 0, 0, 0}, + }, + { // Green + { 0, 255, 255}, + { 0, 170, 255}, + { 0, 0, 255}, + }, + { // Blue + { 0, 0, 255}, + {255, 170, 0}, + { 0, 255, 255}, + } }; + +int test_npy() { + blueoil::Tensor input = blueoil::npy::Tensor_fromNPYFile("images/3x3colors.npy"); + blueoil::Tensor input_float = blueoil::npy::Tensor_fromNPYFile("images/3x3colors-float.npy"); + blueoil::Tensor expect({3, 3, 3}, reinterpret_cast(test_expect)); + expect = blueoil::util::Tensor_CHW_to_HWC(expect); + if (!input.allequal(expect)) { + std::cerr << "test_npy: input != expect" << std::endl; + blueoil::util::Tensor_HWC_to_CHW(input).dump(); + blueoil::util::Tensor_HWC_to_CHW(expect).dump(); + return EXIT_FAILURE; + } + if (!input_float.allequal(expect)) { + std::cerr << "test_npy: input_float != expect" << std::endl; + blueoil::util::Tensor_HWC_to_CHW(input_float).dump(); + blueoil::util::Tensor_HWC_to_CHW(expect).dump(); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +int main(void) { + int status_code = 0; + status_code = test_npy(); + if (status_code != EXIT_FAILURE) { + std::exit(status_code); + } + std::exit(EXIT_SUCCESS); +} diff --git a/runtime/test/test_png.cpp b/runtime/test/test_png.cpp new file mode 100644 index 000000000..dc0519124 --- /dev/null +++ b/runtime/test/test_png.cpp @@ -0,0 +1,61 @@ +/* Copyright 2019 The Blueoil Authors. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +=============================================================================*/ + +#include +#include + +#include "blueoil.hpp" +#include "blueoil_png.hpp" +#include "test_util.hpp" + +float test_expect[3][3][3] = + { { // Red + {255, 255, 255}, + {255, 170, 0}, + { 0, 0, 0}, + }, + { // Green + { 0, 255, 255}, + { 0, 170, 255}, + { 0, 0, 255}, + }, + { // Blue + { 0, 0, 255}, + {255, 170, 0}, + { 0, 255, 255}, + } }; + +int test_npy() { + // PNG24 using 9 colors + blueoil::Tensor input = blueoil::png::Tensor_fromPNGFile("images/3x3colors.png"); + blueoil::Tensor expect({3, 3, 3}, reinterpret_cast(test_expect)); + expect = blueoil::util::Tensor_CHW_to_HWC(expect); + if (!input.allequal(expect)) { + std::cerr << "test_npy: input != expect" << std::endl; + blueoil::util::Tensor_HWC_to_CHW(input).dump(); + blueoil::util::Tensor_HWC_to_CHW(expect).dump(); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +} + +int main(void) { + int status_code = 0; + status_code = test_npy(); + if (status_code != EXIT_FAILURE) { + std::exit(status_code); + } + std::exit(EXIT_SUCCESS); +} diff --git a/runtime/test/test_resize.cpp b/runtime/test/test_resize.cpp index 6cf780962..bf86b2396 100644 --- a/runtime/test/test_resize.cpp +++ b/runtime/test/test_resize.cpp @@ -115,12 +115,9 @@ int command_resize(int argc, char **argv) { char *outfile = argv[4]; std::cout << "infile:" << infile << " width:" << width << " height:" << height << " outfile:" << outfile << std::endl; - cv::Mat img = cv::imread(infile, 1); // 1:force to RGB format - if (img.data == NULL) { - std::cerr << "can't open image file:" << infile <