From 0a70563b1e1ec41e8e16585ce22276625193aa40 Mon Sep 17 00:00:00 2001 From: "Chaunte W. Lacewell" Date: Wed, 4 Oct 2023 08:44:41 -0700 Subject: [PATCH] V2.6.0 (#165) --- CMakeLists.txt | 35 +- INSTALL.md | 125 +- client/cpp/rapidcsv.h | 3060 ++++++++--------- client/python/setup.py | 4 +- client/python/vdms/queryMessage_pb2.py | 12 +- distributed/CMakeLists.txt | 25 +- docker/base/Dockerfile | 43 +- include/vcl/Image.h | 10 + include/vcl/Video.h | 463 ++- remote_function/README.md | 2 +- remote_function/functions/caption.py | 33 + remote_function/requirements.txt | 8 +- remote_function/udf_server.py | 32 +- src/BlobCommand.cc | 2 +- src/CommunicationManager.cc | 16 +- src/CommunicationManager.h | 4 + src/DescriptorsCommand.cc | 33 +- src/ImageCommand.cc | 52 +- src/ImageLoop.cc | 217 +- src/QueryHandler.cc | 16 +- src/QueryHandler.h | 4 +- src/QueryHandlerBase.cc | 64 + src/QueryHandlerBase.h | 52 + src/QueryHandlerExample.cc | 111 + src/QueryHandlerExample.h | 59 + src/QueryHandlerPMGD.cc | 536 +++ src/QueryHandlerPMGD.h | 70 + src/Server.cc | 177 +- src/Server.h | 14 +- src/VideoCommand.cc | 135 +- src/VideoCommand.h | 3 +- src/VideoLoop.cc | 404 +++ src/VideoLoop.h | 140 + src/defines.h | 1 + src/vcl/DescriptorParams.cc | 96 +- src/vcl/DescriptorParams.h | 154 +- src/vcl/Image.cc | 554 +-- src/vcl/Video.cc | 1154 +++++-- src/vdms.cc | 32 +- tests/cleandbs.sh | 3 +- tests/csv_samples/Descriptor.csv | 12 +- tests/csv_samples/DescriptorSet.csv | 14 +- tests/csv_samples/Image.csv | 22 +- tests/csv_samples/Rectangle.csv | 24 +- tests/csv_samples/Video.csv | 12 +- tests/csv_samples/connection.csv | 8 +- tests/python/TestCommand.py | 25 +- tests/python/TestEngineDescriptors.py | 4 + tests/python/config-aws-tests.json | 4 +- .../remote_function_test/functions/caption.py | 33 + tests/remote_function_test/requirements.txt | 8 +- tests/remote_function_test/udf_server.py | 18 +- tests/run_tests.sh | 6 +- tests/server/QueryHandlerTester.h | 23 +- tests/server/json_queries.cc | 97 +- tests/test_images/large1.jpg | Bin tests/udf_test/functions/caption.py | 36 + tests/udf_test/requirements.txt | 2 +- tests/udf_test/settings.json | 3 +- tests/unit_tests/Image_test.cc | 101 +- tests/unit_tests/Video_test.cc | 580 +++- tests/unit_tests/client_add_entity.cc | 406 +-- tests/unit_tests/client_image.cc | 18 + tests/unit_tests/helpers.cc | 46 + tests/unit_tests/helpers.h | 5 + tests/unit_tests/meta_data.cc | 17 + tests/unit_tests/meta_data_helper.h | 1 + user_defined_operations/README.md | 2 +- user_defined_operations/functions/caption.py | 36 + user_defined_operations/requirements.txt | 2 +- user_defined_operations/settings.json | 3 +- utils/include/stats/SystemStats.h | 3 +- utils/src/stats/SystemStats.cc | 28 +- 73 files changed, 6458 insertions(+), 3096 deletions(-) create mode 100644 remote_function/functions/caption.py create mode 100644 src/QueryHandlerBase.cc create mode 100644 src/QueryHandlerBase.h create mode 100644 src/QueryHandlerExample.cc create mode 100644 src/QueryHandlerExample.h create mode 100644 src/QueryHandlerPMGD.cc create mode 100644 src/QueryHandlerPMGD.h create mode 100644 src/VideoLoop.cc create mode 100644 src/VideoLoop.h create mode 100644 tests/remote_function_test/functions/caption.py mode change 100644 => 100755 tests/test_images/large1.jpg create mode 100644 tests/udf_test/functions/caption.py create mode 100644 user_defined_operations/functions/caption.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 713b4f66..3f0d8161 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,9 @@ cmake_minimum_required (VERSION 3.17) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") set(CMAKE_CXX_STANDARD 17) + IF(CODE_COVERAGE) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O0 -Wall -coverage -fprofile-arcs -ftest-coverage") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -Wall -coverage -fprofile-arcs -ftest-coverage") @@ -12,16 +14,32 @@ project(vdms_application) add_compile_options(-g -fPIC -std=c++17) find_package( OpenCV REQUIRED ) -find_package(Protobuf REQUIRED) +find_package(Protobuf CONFIG REQUIRED) find_package( CURL REQUIRED ) find_package(AWSSDK REQUIRED COMPONENTS core s3) include_directories(${Protobuf_INCLUDE_DIRS}) include_directories(${CMAKE_CURRENT_BINARY_DIR}) -execute_process(COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/utils/src/api_schema/createApiString.py ${CMAKE_CURRENT_SOURCE_DIR}/utils/src/api_schema/api_schema.json ${CMAKE_CURRENT_BINARY_DIR}/APISchema.h) -protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS utils/src/protobuf/partitionerMessages.proto utils/src/protobuf/pmgdMessages.proto utils/src/protobuf/queryMessage.proto) -add_library(vdms_protobuf SHARED ${PROTO_SRCS} ${PROTO_HDRS}) +execute_process(COMMAND python3 + ${CMAKE_CURRENT_SOURCE_DIR}/utils/src/api_schema/createApiString.py + ${CMAKE_CURRENT_SOURCE_DIR}/utils/src/api_schema/api_schema.json + ${CMAKE_CURRENT_BINARY_DIR}/APISchema.h +) +add_library(vdms_protobuf OBJECT + ${CMAKE_CURRENT_SOURCE_DIR}/utils/src/protobuf/partitionerMessages.proto + ${CMAKE_CURRENT_SOURCE_DIR}/utils/src/protobuf/pmgdMessages.proto + ${CMAKE_CURRENT_SOURCE_DIR}/utils/src/protobuf/queryMessage.proto +) +target_link_libraries(vdms_protobuf PUBLIC protobuf::libprotobuf) +set(PROTO_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) +target_include_directories(vdms_protobuf PUBLIC "$") +protobuf_generate( + LANGUAGE cpp + TARGET vdms_protobuf + IMPORT_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/utils/src/protobuf" + PROTOC_OUT_DIR "${PROTO_BINARY_DIR}" +) option(CLIENT "Built client library." OFF) if (CLIENT) @@ -54,7 +72,9 @@ else() src/PMGDIterators.cc src/PMGDQuery.cc src/PMGDQueryHandler.cc - src/QueryHandler.cc + src/QueryHandlerExample.cc + src/QueryHandlerBase.cc + src/QueryHandlerPMGD.cc src/QueryMessage.cc src/RSCommand.cc src/SearchExpression.cc @@ -63,10 +83,9 @@ else() src/VideoCommand.cc src/AutoDeleteNode.cc src/ImageLoop.cc - ${PROTO_SRCS} ${PROTO_HDRS} -) + src/VideoLoop.cc + ) target_link_libraries(dms vcl pmgd pmgd-util protobuf tbb tiledb vdms-utils pthread -lcurl -lzmq ${AWSSDK_LINK_LIBRARIES}) - add_executable(vdms src/vdms.cc) target_link_libraries(vdms dms vdms_protobuf vcl tiledb faiss flinng jsoncpp ${OpenCV_LIBS} ${AWSSDK_LINK_LIBRARIES}) endif () diff --git a/INSTALL.md b/INSTALL.md index 9348eb44..f80c7b0d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -2,20 +2,20 @@ Here is the detailed process of installation of VDMS dependencies. ## Dependencies -To install VDMS, we must install the necessary dependencies via apt, github, and pip. +To install VDMS, we must install the necessary dependencies via apt, github, and pip (Python 3.9+). -### Install Debian Packages -Here we will install the Debian and Python3 packages. +### Install Debian/Ubuntu Packages +Here we will install the Debian/Ubuntu packages. ```bash sudo apt-get update sudo apt-get install -y --no-install-suggests --no-install-recommends \ apt-transport-https autoconf automake bison build-essential bzip2 ca-certificates \ curl ed flex g++-9 gcc-9 git gnupg-agent javacc libarchive-tools libatlas-base-dev \ libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev libc-ares-dev libcurl4-openssl-dev \ - libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev libgtk-3-dev libgtk2.0-dev \ - libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libleveldb-dev liblmdb-dev \ + libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtk-3-dev libgtk2.0-dev \ + libhdf5-dev libjpeg-dev libjsoncpp-dev libleveldb-dev liblmdb-dev \ liblz4-dev libopenblas-dev libopenmpi-dev libpng-dev librdkafka-dev libsnappy-dev libssl-dev \ - libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libtool libzmq3-dev mpich \ + libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libtool libzmq3-dev linux-libc-dev mpich \ openjdk-11-jdk-headless pkg-config procps python3-dev python3-pip software-properties-common \ swig unzip uuid-dev ``` @@ -25,81 +25,110 @@ update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-9 1 update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-9 1 ``` +#### **Install JPEG package** +Please install the JPEG package based on the OS platform being used: +* ***Debian 10+:*** `sudo apt-get install -y libjpeg62-turbo-dev` +* ***Ubuntu 20.04+:*** `sudo apt-get install -y libjpeg8-dev` +
+ ### Install Remaining Dependencies Here we assume `$VDMS_DEP_DIR` is the directory for installing additional dependencies. This directory is user-defined but here we use `/dependencies`. These instructions assume you have full permissions to your system. -If not running as root, add `sudo` where necessary. +***NOTE:*** If running as ***root***, remove `sudo` where applicable. ```bash VDMS_DEP_DIR=/dependencies # Set to any directory BUILD_THREADS="-j`nproc`" mkdir -p $VDMS_DEP_DIR ``` + #### Python3 Packages -Here we will install the necessary Python3 packages Numpy and Protobuf 3.20.3. +Here we will install the necessary Python 3.9+ packages Numpy and Protobuf v24.2. +It is expected that you have Python3.9 or higher installed on your system. +All python calls will use Python3.9+; therefore you may find it convenient to set alias for python. +```bash +alias python=/usr/bin/python3 +``` +***NOTE:*** If multiple versions of Python 3 are present on your system, verify you are using Python3.9 or higher. You can specify the specific verison in above command and also set the following with your specific version: `alias python3=/usr/bin/python3.x`. + You can also install the coverage package if interested in running the Python unit tests. ```bash -PROTOBUF_VERSION="3.20.3" -pip3 install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" "coverage>=7.2.7" +python3 -m pip install --upgrade pip +python3 -m pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" ``` -#### CMAKE v3.26.4 -VDMS requires CMake v3.21+. Here we install CMake v3.26.4. +#### **CMAKE v3.27.2** +VDMS requires CMake v3.21+. Here we install CMake v3.27.2. ```bash -CMAKE_VERSION="v3.26.4" +CMAKE_VERSION="v3.27.2" git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git $VDMS_DEP_DIR/CMake cd $VDMS_DEP_DIR/CMake ./bootstrap make ${BUILD_THREADS} -make install +sudo make install ``` -### gtest -Unfortunately apt doesn't build gtest so you need to do the following: -```bash -cd /usr/src/gtest/ -cmake . -make ${BUILD_THREADS} -mv lib/libgtest* /usr/lib -``` -### Faiss v1.7.3 +#### **Faiss v1.7.3** +Install the Faiss library for similarity search. ```bash FAISS_VERSION="v1.7.3" git clone --branch ${FAISS_VERSION} https://github.com/facebookresearch/faiss.git $VDMS_DEP_DIR/faiss cd $VDMS_DEP_DIR/faiss mkdir build && cd build -cmake -DFAISS_ENABLE_GPU=OFF .. +cmake -DFAISS_ENABLE_GPU=OFF -DPython_EXECUTABLE=/usr/bin/python3 .. make ${BUILD_THREADS} -make install +sudo make install ``` -### FLINNG + +#### **FLINNG** +Install the Filters to Identify Near-Neighbor Groups (FLINNG) library for similarity search. ```bash git clone https://github.com/tonyzhang617/FLINNG.git $VDMS_DEP_DIR/FLINNG cd $VDMS_DEP_DIR/FLINNG mkdir build && cd build cmake .. make ${BUILD_THREADS} -make install +sudo make install ``` -### Protobuf 3.20.3 + +#### **Protobuf v24.2 (4.24.2)** +Install Protobuf (C++ and Python) which requires GoogleTest and Abseil C++ as dependencies. ```bash -PROTOBUF_VERSION="3.20.3" -curl -L -o ${VDMS_DEP_DIR}/${PROTOBUF_VERSION}.tar.gz https://github.com/protocolbuffers/protobuf/archive/refs/tags/v${PROTOBUF_VERSION}.tar.gz -cd ${VDMS_DEP_DIR} && tar -xvf ${PROTOBUF_VERSION}.tar.gz -cd protobuf-${PROTOBUF_VERSION} -./autogen.sh -./configure +PROTOBUF_VERSION="24.2" +git clone -b v${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git $VDMS_DEP_DIR/protobuf + +cd $VDMS_DEP_DIR/protobuf/third_party/googletest +mkdir build && cd build +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local \ + -DBUILD_GMOCK=ON -DCMAKE_CXX_STANDARD=17 .. +make ${BUILD_THREADS} +sudo make install +sudo ldconfig + +cd $VDMS_DEP_DIR/protobuf/third_party/abseil-cpp +mkdir build && cd build +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ \ + -DABSL_BUILD_TESTING=ON -DABSL_ENABLE_INSTALL=ON -DABSL_USE_EXTERNAL_GOOGLETEST=ON \ + -DABSL_FIND_GOOGLETEST=ON -DCMAKE_CXX_STANDARD=17 .. +make ${BUILD_THREADS} +sudo make install + +cd $VDMS_DEP_DIR/protobuf +cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_CXX_STANDARD=17 \ + -Dprotobuf_ABSL_PROVIDER=package -DCMAKE_PREFIX_PATH=/usr/local . make ${BUILD_THREADS} -make install -ldconfig +sudo make install + +python3 -m pip install --no-cache-dir "protobuf==4.${PROTOBUF_VERSION}" ``` -### [OpenCV](https://opencv.org/) 4.5.5 + +#### **[OpenCV](https://opencv.org/) 4.5.5** Below are instructions for installing ***OpenCV v4.5.5***. ```bash OPENCV_VERSION="4.5.5" @@ -108,7 +137,7 @@ cd $VDMS_DEP_DIR/opencv mkdir build && cd build cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. make ${BUILD_THREADS} -make install +sudo make install ``` **Note**: When using videos, and getting the following error: "Unable to stop the stream: Inappropriate ioctl for device", you may need to include more flags when compiling OpenCV. Follow these instructions ([source](https://stackoverflow.com/questions/41200201/opencv-unable-to-stop-the-stream-inappropriate-ioctl-for-device)): @@ -121,20 +150,21 @@ cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF -D CMAKE_BUILD_TYPE=RELEASE -D -D WITH_V4L=ON -D WITH_OPENGL=ON -D WITH_CUBLAS=ON \ -DWITH_QT=OFF -DCUDA_NVCC_FLAGS="-D_FORCE_INLINES" .. make ${BUILD_THREADS} -make install +sudo make install ``` -### Valijson v0.6 -This is a headers-only library, no compilation/installation necessary + +#### **Valijson v0.6** +This is a headers-only library, no compilation/installation necessary. ```bash VALIJSON_VERSION="v0.6" git clone --branch ${VALIJSON_VERSION} https://github.com/tristanpenman/valijson.git $VDMS_DEP_DIR/valijson cd $VDMS_DEP_DIR/valijson -cp -r include/* /usr/local/include/ +sudo cp -r include/* /usr/local/include/ ``` -### [TileDB](https://tiledb.io/) 2.14.1 +#### **[TileDB](https://tiledb.io/) 2.14.1** The directions below will help you install TileDB v2.14.1 from the source. You can also follow the directions listed [here](https://docs.tiledb.io/en/latest/installation.html). ```bash @@ -146,10 +176,12 @@ cd TileDB-${TILEDB_VERSION} mkdir build && cd build ../bootstrap --prefix=/usr/local/ make ${BUILD_THREADS} -make install-tiledb +sudo make install-tiledb ``` -### AWS SDK CPP 1.11.0 + +#### **AWS SDK CPP 1.11.0** +Use the following instructions to install AWS SDK for C++. ```bash AWS_SDK_VERSION="1.11.0" git clone -b ${AWS_SDK_VERSION} --recurse-submodules https://github.com/aws/aws-sdk-cpp ${VDMS_DEP_DIR}/aws-sdk-cpp @@ -157,8 +189,9 @@ mkdir -p ${VDMS_DEP_DIR}/aws-sdk-cpp/build cd ${VDMS_DEP_DIR}/aws-sdk-cpp/build cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DBUILD_ONLY="s3" -DCUSTOM_MEMORY_MANAGEMENT=OFF make ${BUILD_THREADS} -make install +sudo make install ``` +
## Install VDMS This version of VDMS treats PMGD as a submodule so both libraries are compiled at one time. After entering the vdms directory, the command `git submodule update --init --recursive` will pull pmgd into the appropriate directory. Furthermore, Cmake is used to compile all directories. diff --git a/client/cpp/rapidcsv.h b/client/cpp/rapidcsv.h index 878e2c97..2f2d5325 100644 --- a/client/cpp/rapidcsv.h +++ b/client/cpp/rapidcsv.h @@ -1,1531 +1,1531 @@ -/* - * rapidcsv.h - * - * URL: https://github.com/d99kris/rapidcsv - * Version: 8.53 - * - * Copyright (C) 2017-2021 Kristofer Berggren - * All rights reserved. - * - * rapidcsv is distributed under the BSD 3-Clause license, see LICENSE for - * details. - * - */ - -#pragma once - -#include -#include -#include -#ifdef HAS_CODECVT -#include -#include -#endif -#include -#include -#include -#include -#include -#include -#include -#include - -#if defined(_MSC_VER) -#include -typedef SSIZE_T ssize_t; -#endif - -namespace rapidcsv { -#if defined(_MSC_VER) -static const bool sPlatformHasCR = true; -#else -static const bool sPlatformHasCR = false; -#endif - -/** - * @brief Datastructure holding parameters controlling how invalid numbers - * (including empty strings) should be handled. - */ -struct ConverterParams { - /** - * @brief Constructor - * @param pHasDefaultConverter specifies if conversion of non-numerical - * strings shall be converted to a default numerical value, instead of causing - * an exception to be thrown (default). - * @param pDefaultFloat floating-point default value to represent - * invalid numbers. - * @param pDefaultInteger integer default value to represent invalid - * numbers. - */ - explicit ConverterParams( - const bool pHasDefaultConverter = false, - const long double pDefaultFloat = - std::numeric_limits::signaling_NaN(), - const long long pDefaultInteger = 0) - : mHasDefaultConverter(pHasDefaultConverter), - mDefaultFloat(pDefaultFloat), mDefaultInteger(pDefaultInteger) {} - - /** - * @brief specifies if conversion of non-numerical strings shall be - * converted to a default numerical value, instead of causing an exception to - * be thrown (default). - */ - bool mHasDefaultConverter; - - /** - * @brief floating-point default value to represent invalid numbers. - */ - long double mDefaultFloat; - - /** - * @brief integer default value to represent invalid numbers. - */ - long long mDefaultInteger; -}; - -/** - * @brief Exception thrown when attempting to access Document data in a - * datatype which is not supported by the Converter class. - */ -class no_converter : public std::exception { - /** - * @brief Provides details about the exception - * @returns an explanatory string - */ - virtual const char *what() const throw() { - return "unsupported conversion datatype"; - } -}; - -/** - * @brief Class providing conversion to/from numerical datatypes and - * strings. Only intended for rapidcsv internal usage, but exposed externally to - * allow specialization for custom datatype conversions. - */ -template class Converter { -public: - /** - * @brief Constructor - * @param pConverterParams specifies how conversion of non-numerical - * values to numerical datatype shall be handled. - */ - Converter(const ConverterParams &pConverterParams) - : mConverterParams(pConverterParams) {} - - /** - * @brief Converts numerical value to string representation. - * @param pVal numerical value - * @param pStr output string - */ - void ToStr(const T &pVal, std::string &pStr) const { - if (typeid(T) == typeid(int) || typeid(T) == typeid(long) || - typeid(T) == typeid(long long) || typeid(T) == typeid(unsigned) || - typeid(T) == typeid(unsigned long) || - typeid(T) == typeid(unsigned long long) || typeid(T) == typeid(float) || - typeid(T) == typeid(double) || typeid(T) == typeid(long double) || - typeid(T) == typeid(char)) { - std::ostringstream out; - out << pVal; - pStr = out.str(); - } else { - throw no_converter(); - } - } - - /** - * @brief Converts string holding a numerical value to numerical datatype - * representation. - * @param pVal numerical value - * @param pStr output string - */ - void ToVal(const std::string &pStr, T &pVal) const { - try { - if (typeid(T) == typeid(int)) { - pVal = static_cast(std::stoi(pStr)); - return; - } else if (typeid(T) == typeid(long)) { - pVal = static_cast(std::stol(pStr)); - return; - } else if (typeid(T) == typeid(long long)) { - pVal = static_cast(std::stoll(pStr)); - return; - } else if (typeid(T) == typeid(unsigned)) { - pVal = static_cast(std::stoul(pStr)); - return; - } else if (typeid(T) == typeid(unsigned long)) { - pVal = static_cast(std::stoul(pStr)); - return; - } else if (typeid(T) == typeid(unsigned long long)) { - pVal = static_cast(std::stoull(pStr)); - return; - } - } catch (...) { - if (!mConverterParams.mHasDefaultConverter) { - throw; - } else { - pVal = static_cast(mConverterParams.mDefaultInteger); - return; - } - } - - try { - if (typeid(T) == typeid(float)) { - pVal = static_cast(std::stof(pStr)); - return; - } else if (typeid(T) == typeid(double)) { - pVal = static_cast(std::stod(pStr)); - return; - } else if (typeid(T) == typeid(long double)) { - pVal = static_cast(std::stold(pStr)); - return; - } - } catch (...) { - if (!mConverterParams.mHasDefaultConverter) { - throw; - } else { - pVal = static_cast(mConverterParams.mDefaultFloat); - return; - } - } - - if (typeid(T) == typeid(char)) { - pVal = static_cast(pStr[0]); - return; - } else { - throw no_converter(); - } - } - -private: - const ConverterParams &mConverterParams; -}; - -/** - * @brief Specialized implementation handling string to string conversion. - * @param pVal string - * @param pStr string - */ -template <> -inline void Converter::ToStr(const std::string &pVal, - std::string &pStr) const { - pStr = pVal; -} - -/** - * @brief Specialized implementation handling string to string conversion. - * @param pVal string - * @param pStr string - */ -template <> -inline void Converter::ToVal(const std::string &pStr, - std::string &pVal) const { - pVal = pStr; -} - -template -using ConvFunc = std::function; - -/** - * @brief Datastructure holding parameters controlling which row and column - * should be treated as labels. - */ -struct LabelParams { - /** - * @brief Constructor - * @param pColumnNameIdx specifies the zero-based row index of the - * column labels, setting it to -1 prevents column lookup by label name, and - * gives access to all rows as document data. Default: 0 - * @param pRowNameIdx specifies the zero-based column index of the - * row labels, setting it to -1 prevents row lookup by label name, and gives - * access to all columns as document data. Default: -1 - */ - explicit LabelParams(const int pColumnNameIdx = 0, const int pRowNameIdx = -1) - : mColumnNameIdx(pColumnNameIdx), mRowNameIdx(pRowNameIdx) {} - - /** - * @brief specifies the zero-based row index of the column labels. - */ - int mColumnNameIdx; - - /** - * @brief specifies the zero-based column index of the row labels. - */ - int mRowNameIdx; -}; - -/** - * @brief Datastructure holding parameters controlling how the CSV data - * fields are separated. - */ -struct SeparatorParams { - /** - * @brief Constructor - * @param pSeparator specifies the column separator (default - * ','). - * @param pTrim specifies whether to trim leading and - * trailing spaces from cells read (default false). - * @param pHasCR specifies whether a new document (i.e. not - * an existing document read) should use CR/LF instead of only LF (default is - * to use standard behavior of underlying platforms - CR/LF for Win, and LF - * for others). - * @param pQuotedLinebreaks specifies whether to allow line breaks in - * quoted text (default false) - * @param pAutoQuote specifies whether to automatically dequote - * data during read, and add quotes during write (default true). - */ - explicit SeparatorParams(const char pSeparator = ',', - const bool pTrim = false, - const bool pHasCR = sPlatformHasCR, - const bool pQuotedLinebreaks = false, - const bool pAutoQuote = true) - : mSeparator(pSeparator), mTrim(pTrim), mHasCR(pHasCR), - mQuotedLinebreaks(pQuotedLinebreaks), mAutoQuote(pAutoQuote) {} - - /** - * @brief specifies the column separator. - */ - char mSeparator; - - /** - * @brief specifies whether to trim leading and trailing spaces from cells - * read. - */ - bool mTrim; - - /** - * @brief specifies whether new documents should use CR/LF instead of LF. - */ - bool mHasCR; - - /** - * @brief specifies whether to allow line breaks in quoted text. - */ - bool mQuotedLinebreaks; - - /** - * @brief specifies whether to automatically dequote cell data. - */ - bool mAutoQuote; -}; - -/** - * @brief Datastructure holding parameters controlling how special line - * formats should be treated. - */ -struct LineReaderParams { - /** - * @brief Constructor - * @param pSkipCommentLines specifies whether to skip lines prefixed - * with mCommentPrefix. Default: false - * @param pCommentPrefix specifies which prefix character to indicate - * a comment line. Default: # - * @param pSkipEmptyLines specifies whether to skip empty lines. - * Default: false - */ - explicit LineReaderParams(const bool pSkipCommentLines = false, - const char pCommentPrefix = '#', - const bool pSkipEmptyLines = false) - : mSkipCommentLines(pSkipCommentLines), mCommentPrefix(pCommentPrefix), - mSkipEmptyLines(pSkipEmptyLines) {} - - /** - * @brief specifies whether to skip lines prefixed with mCommentPrefix. - */ - bool mSkipCommentLines; - - /** - * @brief specifies which prefix character to indicate a comment line. - */ - char mCommentPrefix; - - /** - * @brief specifies whether to skip empty lines. - */ - bool mSkipEmptyLines; -}; - -/** - * @brief Class representing a CSV document. - */ -class Document { -public: - /** - * @brief Constructor - * @param pPath specifies the path of an existing CSV-file - * to populate the Document data with. - * @param pLabelParams specifies which row and column should be - * treated as labels. - * @param pSeparatorParams specifies which field and row separators - * should be used. - * @param pConverterParams specifies how invalid numbers (including - * empty strings) should be handled. - * @param pLineReaderParams specifies how special line formats should be - * treated. - */ - explicit Document( - const std::string &pPath = std::string(), - const LabelParams &pLabelParams = LabelParams(), - const SeparatorParams &pSeparatorParams = SeparatorParams(), - const ConverterParams &pConverterParams = ConverterParams(), - const LineReaderParams &pLineReaderParams = LineReaderParams()) - : mPath(pPath), mLabelParams(pLabelParams), - mSeparatorParams(pSeparatorParams), mConverterParams(pConverterParams), - mLineReaderParams(pLineReaderParams) { - if (!mPath.empty()) { - ReadCsv(); - } - } - - /** - * @brief Constructor - * @param pStream specifies an input stream to read CSV data - * from. - * @param pLabelParams specifies which row and column should be - * treated as labels. - * @param pSeparatorParams specifies which field and row separators - * should be used. - * @param pConverterParams specifies how invalid numbers (including - * empty strings) should be handled. - * @param pLineReaderParams specifies how special line formats should be - * treated. - */ - explicit Document( - std::istream &pStream, const LabelParams &pLabelParams = LabelParams(), - const SeparatorParams &pSeparatorParams = SeparatorParams(), - const ConverterParams &pConverterParams = ConverterParams(), - const LineReaderParams &pLineReaderParams = LineReaderParams()) - : mPath(), mLabelParams(pLabelParams), mSeparatorParams(pSeparatorParams), - mConverterParams(pConverterParams), - mLineReaderParams(pLineReaderParams) { - ReadCsv(pStream); - } - - /** - * @brief Read Document data from file. - * @param pPath specifies the path of an existing CSV-file - * to populate the Document data with. - * @param pLabelParams specifies which row and column should be - * treated as labels. - * @param pSeparatorParams specifies which field and row separators - * should be used. - * @param pConverterParams specifies how invalid numbers (including - * empty strings) should be handled. - * @param pLineReaderParams specifies how special line formats should be - * treated. - */ - void Load(const std::string &pPath, - const LabelParams &pLabelParams = LabelParams(), - const SeparatorParams &pSeparatorParams = SeparatorParams(), - const ConverterParams &pConverterParams = ConverterParams(), - const LineReaderParams &pLineReaderParams = LineReaderParams()) { - mPath = pPath; - mLabelParams = pLabelParams; - mSeparatorParams = pSeparatorParams; - mConverterParams = pConverterParams; - mLineReaderParams = pLineReaderParams; - ReadCsv(); - } - - /** - * @brief Read Document data from stream. - * @param pStream specifies an input stream to read CSV data - * from. - * @param pLabelParams specifies which row and column should be - * treated as labels. - * @param pSeparatorParams specifies which field and row separators - * should be used. - * @param pConverterParams specifies how invalid numbers (including - * empty strings) should be handled. - * @param pLineReaderParams specifies how special line formats should be - * treated. - */ - void Load(std::istream &pStream, - const LabelParams &pLabelParams = LabelParams(), - const SeparatorParams &pSeparatorParams = SeparatorParams(), - const ConverterParams &pConverterParams = ConverterParams(), - const LineReaderParams &pLineReaderParams = LineReaderParams()) { - mPath = ""; - mLabelParams = pLabelParams; - mSeparatorParams = pSeparatorParams; - mConverterParams = pConverterParams; - mLineReaderParams = pLineReaderParams; - ReadCsv(pStream); - } - - /** - * @brief Write Document data to file. - * @param pPath optionally specifies the path where the - * CSV-file will be created (if not specified, the original path provided when - * creating or loading the Document data will be used). - */ - void Save(const std::string &pPath = std::string()) { - if (!pPath.empty()) { - mPath = pPath; - } - WriteCsv(); - } - - /** - * @brief Write Document data to stream. - * @param pStream specifies an output stream to write the data - * to. - */ - void Save(std::ostream &pStream) { WriteCsv(pStream); } - - /** - * @brief Clears loaded Document data. - * - */ - void Clear() { - mData.clear(); - mColumnNames.clear(); - mRowNames.clear(); -#ifdef HAS_CODECVT - mIsUtf16 = false; - mIsLE = false; -#endif - } - - /** - * @brief Get column index by name. - * @param pColumnName column label name. - * @returns zero-based column index. - */ - ssize_t GetColumnIdx(const std::string &pColumnName) const { - if (mLabelParams.mColumnNameIdx >= 0) { - if (mColumnNames.find(pColumnName) != mColumnNames.end()) { - return mColumnNames.at(pColumnName) - (mLabelParams.mRowNameIdx + 1); - } - } - return -1; - } - - /** - * @brief Get column by index. - * @param pColumnIdx zero-based column index. - * @returns vector of column data. - */ - template - std::vector GetColumn(const size_t pColumnIdx) const { - const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - std::vector column; - Converter converter(mConverterParams); - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { - if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) { - if (columnIdx < static_cast(itRow->size())) { - T val; - converter.ToVal(itRow->at(columnIdx), val); - column.push_back(val); - } else { - const std::string errStr = - "requested column index " + - std::to_string(columnIdx - (mLabelParams.mRowNameIdx + 1)) + - " >= " + - std::to_string(itRow->size() - (mLabelParams.mRowNameIdx + 1)) + - " (number of columns on row index " + - std::to_string(std::distance(mData.begin(), itRow) - - (mLabelParams.mColumnNameIdx + 1)) + - ")"; - throw std::out_of_range(errStr); - } - } - } - return column; - } - - /** - * @brief Get column by index. - * @param pColumnIdx zero-based column index. - * @param pToVal conversion function. - * @returns vector of column data. - */ - template - std::vector GetColumn(const size_t pColumnIdx, ConvFunc pToVal) const { - const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - std::vector column; - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { - if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) { - T val; - pToVal(itRow->at(columnIdx), val); - column.push_back(val); - } - } - return column; - } - - /** - * @brief Get column by name. - * @param pColumnName column label name. - * @returns vector of column data. - */ - template - std::vector GetColumn(const std::string &pColumnName) const { - const ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - return GetColumn(columnIdx); - } - - /** - * @brief Get column by name. - * @param pColumnName column label name. - * @param pToVal conversion function. - * @returns vector of column data. - */ - template - std::vector GetColumn(const std::string &pColumnName, - ConvFunc pToVal) const { - const ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - return GetColumn(columnIdx, pToVal); - } - - /** - * @brief Set column by index. - * @param pColumnIdx zero-based column index. - * @param pColumn vector of column data. - */ - template - void SetColumn(const size_t pColumnIdx, const std::vector &pColumn) { - const size_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - - while (pColumn.size() + (mLabelParams.mColumnNameIdx + 1) > - GetDataRowCount()) { - std::vector row; - row.resize(GetDataColumnCount()); - mData.push_back(row); - } - - if ((columnIdx + 1) > GetDataColumnCount()) { - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { - itRow->resize(columnIdx + 1 + (mLabelParams.mRowNameIdx + 1)); - } - } - - Converter converter(mConverterParams); - for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow) { - std::string str; - converter.ToStr(*itRow, str); - mData - .at(std::distance(pColumn.begin(), itRow) + - (mLabelParams.mColumnNameIdx + 1)) - .at(columnIdx) = str; - } - } - - /** - * @brief Set column by name. - * @param pColumnName column label name. - * @param pColumn vector of column data. - */ - template - void SetColumn(const std::string &pColumnName, - const std::vector &pColumn) { - const ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - SetColumn(columnIdx, pColumn); - } - - /** - * @brief Remove column by index. - * @param pColumnIdx zero-based column index. - */ - void RemoveColumn(const size_t pColumnIdx) { - const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { - itRow->erase(itRow->begin() + columnIdx); - } - } - - /** - * @brief Remove column by name. - * @param pColumnName column label name. - */ - void RemoveColumn(const std::string &pColumnName) { - ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - - RemoveColumn(columnIdx); - } - - /** - * @brief Insert column at specified index. - * @param pColumnIdx zero-based column index. - * @param pColumn vector of column data (optional argument). - * @param pColumnName column label name (optional argument). - */ - template - void InsertColumn(const size_t pColumnIdx, - const std::vector &pColumn = std::vector(), - const std::string &pColumnName = std::string()) { - const size_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - - std::vector column; - if (pColumn.empty()) { - column.resize(GetDataRowCount()); - } else { - column.resize(pColumn.size() + (mLabelParams.mColumnNameIdx + 1)); - Converter converter(mConverterParams); - for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow) { - std::string str; - converter.ToStr(*itRow, str); - const size_t rowIdx = std::distance(pColumn.begin(), itRow) + - (mLabelParams.mColumnNameIdx + 1); - column.at(rowIdx) = str; - } - } - - while (column.size() > GetDataRowCount()) { - std::vector row; - const size_t columnCount = - std::max(static_cast(mLabelParams.mColumnNameIdx + 1), - GetDataColumnCount()); - row.resize(columnCount); - mData.push_back(row); - } - - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { - const size_t rowIdx = std::distance(mData.begin(), itRow); - itRow->insert(itRow->begin() + columnIdx, column.at(rowIdx)); - } - - if (!pColumnName.empty()) { - SetColumnName(pColumnIdx, pColumnName); - } - } - - /** - * @brief Get number of data columns (excluding label columns). - * @returns column count. - */ - size_t GetColumnCount() const { - const ssize_t count = - static_cast((mData.size() > 0) ? mData.at(0).size() : 0) - - (mLabelParams.mRowNameIdx + 1); - return (count >= 0) ? count : 0; - } - - /** - * @brief Get row index by name. - * @param pRowName row label name. - * @returns zero-based row index. - */ - ssize_t GetRowIdx(const std::string &pRowName) const { - if (mLabelParams.mRowNameIdx >= 0) { - if (mRowNames.find(pRowName) != mRowNames.end()) { - return mRowNames.at(pRowName) - (mLabelParams.mColumnNameIdx + 1); - } - } - return -1; - } - - /** - * @brief Get row by index. - * @param pRowIdx zero-based row index. - * @returns vector of row data. - */ - template std::vector GetRow(const size_t pRowIdx) const { - const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - std::vector row; - Converter converter(mConverterParams); - for (auto itCol = mData.at(rowIdx).begin(); itCol != mData.at(rowIdx).end(); - ++itCol) { - if (std::distance(mData.at(rowIdx).begin(), itCol) > - mLabelParams.mRowNameIdx) { - T val; - converter.ToVal(*itCol, val); - row.push_back(val); - } - } - return row; - } - - /** - * @brief Get row by index. - * @param pRowIdx zero-based row index. - * @param pToVal conversion function. - * @returns vector of row data. - */ - template - std::vector GetRow(const size_t pRowIdx, ConvFunc pToVal) const { - const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - std::vector row; - Converter converter(mConverterParams); - for (auto itCol = mData.at(rowIdx).begin(); itCol != mData.at(rowIdx).end(); - ++itCol) { - if (std::distance(mData.at(rowIdx).begin(), itCol) > - mLabelParams.mRowNameIdx) { - T val; - pToVal(*itCol, val); - row.push_back(val); - } - } - return row; - } - - /** - * @brief Get row by name. - * @param pRowName row label name. - * @returns vector of row data. - */ - template - std::vector GetRow(const std::string &pRowName) const { - ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - return GetRow(rowIdx); - } - - /** - * @brief Get row by name. - * @param pRowName row label name. - * @param pToVal conversion function. - * @returns vector of row data. - */ - template - std::vector GetRow(const std::string &pRowName, ConvFunc pToVal) const { - ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - return GetRow(rowIdx, pToVal); - } - - /** - * @brief Set row by index. - * @param pRowIdx zero-based row index. - * @param pRow vector of row data. - */ - template - void SetRow(const size_t pRowIdx, const std::vector &pRow) { - const size_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - - while ((rowIdx + 1) > GetDataRowCount()) { - std::vector row; - row.resize(GetDataColumnCount()); - mData.push_back(row); - } - - if (pRow.size() > GetDataColumnCount()) { - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { - itRow->resize(pRow.size() + (mLabelParams.mRowNameIdx + 1)); - } - } - - Converter converter(mConverterParams); - for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol) { - std::string str; - converter.ToStr(*itCol, str); - mData.at(rowIdx).at(std::distance(pRow.begin(), itCol) + - (mLabelParams.mRowNameIdx + 1)) = str; - } - } - - /** - * @brief Set row by name. - * @param pRowName row label name. - * @param pRow vector of row data. - */ - template - void SetRow(const std::string &pRowName, const std::vector &pRow) { - ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - return SetRow(rowIdx, pRow); - } - - /** - * @brief Remove row by index. - * @param pRowIdx zero-based row index. - */ - void RemoveRow(const size_t pRowIdx) { - const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - mData.erase(mData.begin() + rowIdx); - } - - /** - * @brief Remove row by name. - * @param pRowName row label name. - */ - void RemoveRow(const std::string &pRowName) { - ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - - RemoveRow(rowIdx); - } - - /** - * @brief Insert row at specified index. - * @param pRowIdx zero-based row index. - * @param pRow vector of row data (optional argument). - * @param pRowName row label name (optional argument). - */ - template - void InsertRow(const size_t pRowIdx, - const std::vector &pRow = std::vector(), - const std::string &pRowName = std::string()) { - const size_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - - std::vector row; - if (pRow.empty()) { - row.resize(GetDataColumnCount()); - } else { - row.resize(pRow.size() + (mLabelParams.mRowNameIdx + 1)); - Converter converter(mConverterParams); - for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol) { - std::string str; - converter.ToStr(*itCol, str); - row.at(std::distance(pRow.begin(), itCol) + - (mLabelParams.mRowNameIdx + 1)) = str; - } - } - - while (rowIdx > GetDataRowCount()) { - std::vector tempRow; - tempRow.resize(GetDataColumnCount()); - mData.push_back(tempRow); - } - - mData.insert(mData.begin() + rowIdx, row); - - if (!pRowName.empty()) { - SetRowName(pRowIdx, pRowName); - } - } - - /** - * @brief Get number of data rows (excluding label rows). - * @returns row count. - */ - size_t GetRowCount() const { - const ssize_t count = - static_cast(mData.size()) - (mLabelParams.mColumnNameIdx + 1); - return (count >= 0) ? count : 0; - } - - /** - * @brief Get cell by index. - * @param pColumnIdx zero-based column index. - * @param pRowIdx zero-based row index. - * @returns cell data. - */ - template - T GetCell(const size_t pColumnIdx, const size_t pRowIdx) const { - const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - - T val; - Converter converter(mConverterParams); - converter.ToVal(mData.at(rowIdx).at(columnIdx), val); - return val; - } - - /** - * @brief Get cell by index. - * @param pColumnIdx zero-based column index. - * @param pRowIdx zero-based row index. - * @param pToVal conversion function. - * @returns cell data. - */ - template - T GetCell(const size_t pColumnIdx, const size_t pRowIdx, - ConvFunc pToVal) const { - const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - - T val; - pToVal(mData.at(rowIdx).at(columnIdx), val); - return val; - } - - /** - * @brief Get cell by name. - * @param pColumnName column label name. - * @param pRowName row label name. - * @returns cell data. - */ - template - T GetCell(const std::string &pColumnName, const std::string &pRowName) const { - const ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - - return GetCell(columnIdx, rowIdx); - } - - /** - * @brief Get cell by name. - * @param pColumnName column label name. - * @param pRowName row label name. - * @param pToVal conversion function. - * @returns cell data. - */ - template - T GetCell(const std::string &pColumnName, const std::string &pRowName, - ConvFunc pToVal) const { - const ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - - return GetCell(columnIdx, rowIdx, pToVal); - } - - /** - * @brief Get cell by column name and row index. - * @param pColumnName column label name. - * @param pRowIdx zero-based row index. - * @returns cell data. - */ - template - T GetCell(const std::string &pColumnName, const size_t pRowIdx) const { - const ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - - return GetCell(columnIdx, pRowIdx); - } - - /** - * @brief Get cell by column name and row index. - * @param pColumnName column label name. - * @param pRowIdx zero-based row index. - * @param pToVal conversion function. - * @returns cell data. - */ - template - T GetCell(const std::string &pColumnName, const size_t pRowIdx, - ConvFunc pToVal) const { - const ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - - return GetCell(columnIdx, pRowIdx, pToVal); - } - - /** - * @brief Get cell by column index and row name. - * @param pColumnIdx zero-based column index. - * @param pRowName row label name. - * @returns cell data. - */ - template - T GetCell(const size_t pColumnIdx, const std::string &pRowName) const { - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - - return GetCell(pColumnIdx, rowIdx); - } - - /** - * @brief Get cell by column index and row name. - * @param pColumnIdx zero-based column index. - * @param pRowName row label name. - * @param pToVal conversion function. - * @returns cell data. - */ - template - T GetCell(const size_t pColumnIdx, const std::string &pRowName, - ConvFunc pToVal) const { - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - - return GetCell(pColumnIdx, rowIdx, pToVal); - } - - /** - * @brief Set cell by index. - * @param pRowIdx zero-based row index. - * @param pColumnIdx zero-based column index. - * @param pCell cell data. - */ - template - void SetCell(const size_t pColumnIdx, const size_t pRowIdx, const T &pCell) { - const size_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - const size_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - - while ((rowIdx + 1) > GetDataRowCount()) { - std::vector row; - row.resize(GetDataColumnCount()); - mData.push_back(row); - } - - if ((columnIdx + 1) > GetDataColumnCount()) { - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { - itRow->resize(columnIdx + 1); - } - } - - std::string str; - Converter converter(mConverterParams); - converter.ToStr(pCell, str); - mData.at(rowIdx).at(columnIdx) = str; - } - - /** - * @brief Set cell by name. - * @param pColumnName column label name. - * @param pRowName row label name. - * @param pCell cell data. - */ - template - void SetCell(const std::string &pColumnName, const std::string &pRowName, - const T &pCell) { - const ssize_t columnIdx = GetColumnIdx(pColumnName); - if (columnIdx < 0) { - throw std::out_of_range("column not found: " + pColumnName); - } - - const ssize_t rowIdx = GetRowIdx(pRowName); - if (rowIdx < 0) { - throw std::out_of_range("row not found: " + pRowName); - } - - SetCell(columnIdx, rowIdx, pCell); - } - - /** - * @brief Get column name - * @param pColumnIdx zero-based column index. - * @returns column name. - */ - std::string GetColumnName(const ssize_t pColumnIdx) { - const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - if (mLabelParams.mColumnNameIdx < 0) { - throw std::out_of_range("column name row index < 0: " + - std::to_string(mLabelParams.mColumnNameIdx)); - } - - return mData.at(mLabelParams.mColumnNameIdx).at(columnIdx); - } - - /** - * @brief Set column name - * @param pColumnIdx zero-based column index. - * @param pColumnName column name. - */ - void SetColumnName(size_t pColumnIdx, const std::string &pColumnName) { - const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); - mColumnNames[pColumnName] = columnIdx; - if (mLabelParams.mColumnNameIdx < 0) { - throw std::out_of_range("column name row index < 0: " + - std::to_string(mLabelParams.mColumnNameIdx)); - } - - // increase table size if necessary: - const int rowIdx = mLabelParams.mColumnNameIdx; - if (rowIdx >= static_cast(mData.size())) { - mData.resize(rowIdx + 1); - } - auto &row = mData[rowIdx]; - if (columnIdx >= static_cast(row.size())) { - row.resize(columnIdx + 1); - } - - mData.at(mLabelParams.mColumnNameIdx).at(columnIdx) = pColumnName; - } - - /** - * @brief Get column names - * @returns vector of column names. - */ - std::vector GetColumnNames() { - if (mLabelParams.mColumnNameIdx >= 0) { - return std::vector( - mData.at(mLabelParams.mColumnNameIdx).begin() + - (mLabelParams.mRowNameIdx + 1), - mData.at(mLabelParams.mColumnNameIdx).end()); - } - - return std::vector(); - } - - /** - * @brief Get row name - * @param pRowIdx zero-based column index. - * @returns row name. - */ - std::string GetRowName(const ssize_t pRowIdx) { - const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - if (mLabelParams.mRowNameIdx < 0) { - throw std::out_of_range("row name column index < 0: " + - std::to_string(mLabelParams.mRowNameIdx)); - } - - return mData.at(rowIdx).at(mLabelParams.mRowNameIdx); - } - - /** - * @brief Set row name - * @param pRowIdx zero-based row index. - * @param pRowName row name. - */ - void SetRowName(size_t pRowIdx, const std::string &pRowName) { - const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); - mRowNames[pRowName] = rowIdx; - if (mLabelParams.mRowNameIdx < 0) { - throw std::out_of_range("row name column index < 0: " + - std::to_string(mLabelParams.mRowNameIdx)); - } - - // increase table size if necessary: - if (rowIdx >= static_cast(mData.size())) { - mData.resize(rowIdx + 1); - } - auto &row = mData[rowIdx]; - if (mLabelParams.mRowNameIdx >= static_cast(row.size())) { - row.resize(mLabelParams.mRowNameIdx + 1); - } - - mData.at(rowIdx).at(mLabelParams.mRowNameIdx) = pRowName; - } - - /** - * @brief Get row names - * @returns vector of row names. - */ - std::vector GetRowNames() { - std::vector rownames; - if (mLabelParams.mRowNameIdx >= 0) { - for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { - if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) { - rownames.push_back(itRow->at(mLabelParams.mRowNameIdx)); - } - } - } - return rownames; - } - -private: - void ReadCsv() { - std::ifstream stream; - stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); - stream.open(mPath, std::ios::binary); - ReadCsv(stream); - } - - void ReadCsv(std::istream &pStream) { - Clear(); - pStream.seekg(0, std::ios::end); - std::streamsize length = pStream.tellg(); - pStream.seekg(0, std::ios::beg); - -#ifdef HAS_CODECVT - std::vector bom2b(2, '\0'); - if (length >= 2) { - pStream.read(bom2b.data(), 2); - pStream.seekg(0, std::ios::beg); - } - - static const std::vector bomU16le = {'\xff', '\xfe'}; - static const std::vector bomU16be = {'\xfe', '\xff'}; - if ((bom2b == bomU16le) || (bom2b == bomU16be)) { - mIsUtf16 = true; - mIsLE = (bom2b == bomU16le); - - std::wifstream wstream; - wstream.exceptions(std::wifstream::failbit | std::wifstream::badbit); - wstream.open(mPath, std::ios::binary); - if (mIsLE) { - wstream.imbue( - std::locale(wstream.getloc(), - new std::codecvt_utf16( - std::consume_header | - std::little_endian)>)); - } else { - wstream.imbue(std::locale( - wstream.getloc(), - new std::codecvt_utf16)); - } - std::wstringstream wss; - wss << wstream.rdbuf(); - std::string utf8 = ToString(wss.str()); - std::stringstream ss(utf8); - ParseCsv(ss, utf8.size()); - } else -#endif - { - // check for UTF-8 Byte order mark and skip it when found - if (length >= 3) { - std::vector bom3b(3, '\0'); - pStream.read(bom3b.data(), 3); - static const std::vector bomU8 = {'\xef', '\xbb', '\xbf'}; - if (bom3b != bomU8) { - // file does not start with a UTF-8 Byte order mark - pStream.seekg(0, std::ios::beg); - } else { - // file did start with a UTF-8 Byte order mark, simply skip it - length -= 3; - } - } - - ParseCsv(pStream, length); - } - } - - void ParseCsv(std::istream &pStream, std::streamsize p_FileLength) { - const std::streamsize bufLength = 64 * 1024; - std::vector buffer(bufLength); - std::vector row; - std::string cell; - bool quoted = false; - int cr = 0; - int lf = 0; - - while (p_FileLength > 0) { - std::streamsize readLength = - std::min(p_FileLength, bufLength); - pStream.read(buffer.data(), readLength); - for (int i = 0; i < readLength; ++i) { - if (buffer[i] == '"') { - if (cell.empty() || cell[0] == '"') { - quoted = !quoted; - } - cell += buffer[i]; - } else if (buffer[i] == mSeparatorParams.mSeparator) { - if (!quoted) { - row.push_back(Unquote(Trim(cell))); - cell.clear(); - } else { - cell += buffer[i]; - } - } else if (buffer[i] == '\r') { - if (mSeparatorParams.mQuotedLinebreaks && quoted) { - cell += buffer[i]; - } else { - ++cr; - } - } else if (buffer[i] == '\n') { - if (mSeparatorParams.mQuotedLinebreaks && quoted) { - cell += buffer[i]; - } else { - ++lf; - if (mLineReaderParams.mSkipEmptyLines && row.empty() && - cell.empty()) { - // skip empty line - } else { - row.push_back(Unquote(Trim(cell))); - - if (mLineReaderParams.mSkipCommentLines && !row.at(0).empty() && - (row.at(0)[0] == mLineReaderParams.mCommentPrefix)) { - // skip comment line - } else { - mData.push_back(row); - } - - cell.clear(); - row.clear(); - quoted = false; - } - } - } else { - cell += buffer[i]; - } - } - p_FileLength -= readLength; - } - - // Handle last line without linebreak - if (!cell.empty() || !row.empty()) { - row.push_back(Unquote(Trim(cell))); - cell.clear(); - mData.push_back(row); - row.clear(); - } - - // Assume CR/LF if at least half the linebreaks have CR - mSeparatorParams.mHasCR = (cr > (lf / 2)); - - // Set up column labels - if ((mLabelParams.mColumnNameIdx >= 0) && - (static_cast(mData.size()) > mLabelParams.mColumnNameIdx)) { - int i = 0; - for (auto &columnName : mData[mLabelParams.mColumnNameIdx]) { - mColumnNames[columnName] = i++; - } - } - - // Set up row labels - if ((mLabelParams.mRowNameIdx >= 0) && - (static_cast(mData.size()) > - (mLabelParams.mColumnNameIdx + 1))) { - int i = 0; - for (auto &dataRow : mData) { - if (static_cast(dataRow.size()) > mLabelParams.mRowNameIdx) { - mRowNames[dataRow[mLabelParams.mRowNameIdx]] = i++; - } - } - } - } - - void WriteCsv() const { -#ifdef HAS_CODECVT - if (mIsUtf16) { - std::stringstream ss; - WriteCsv(ss); - std::string utf8 = ss.str(); - std::wstring wstr = ToWString(utf8); - - std::wofstream wstream; - wstream.exceptions(std::wofstream::failbit | std::wofstream::badbit); - wstream.open(mPath, std::ios::binary | std::ios::trunc); - - if (mIsLE) { - wstream.imbue( - std::locale(wstream.getloc(), - new std::codecvt_utf16( - std::little_endian)>)); - } else { - wstream.imbue(std::locale(wstream.getloc(), - new std::codecvt_utf16)); - } - - wstream << static_cast(0xfeff); - wstream << wstr; - } else -#endif - { - std::ofstream stream; - stream.exceptions(std::ofstream::failbit | std::ofstream::badbit); - stream.open(mPath, std::ios::binary | std::ios::trunc); - WriteCsv(stream); - } - } - - void WriteCsv(std::ostream &pStream) const { - for (auto itr = mData.begin(); itr != mData.end(); ++itr) { - for (auto itc = itr->begin(); itc != itr->end(); ++itc) { - if (mSeparatorParams.mAutoQuote && - ((itc->find(mSeparatorParams.mSeparator) != std::string::npos) || - (itc->find(' ') != std::string::npos))) { - // escape quotes in string - std::string str = *itc; - ReplaceString(str, "\"", "\"\""); - - pStream << "\"" << str << "\""; - } else { - pStream << *itc; - } - - if (std::distance(itc, itr->end()) > 1) { - pStream << mSeparatorParams.mSeparator; - } - } - pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n"); - } - } - - size_t GetDataRowCount() const { return mData.size(); } - - size_t GetDataColumnCount() const { - return (mData.size() > 0) ? mData.at(0).size() : 0; - } - - std::string Trim(const std::string &pStr) { - if (mSeparatorParams.mTrim) { - std::string str = pStr; - - // ltrim - str.erase(str.begin(), std::find_if(str.begin(), str.end(), - [](int ch) { return !isspace(ch); })); - - // rtrim - str.erase(std::find_if(str.rbegin(), str.rend(), - [](int ch) { return !isspace(ch); }) - .base(), - str.end()); - - return str; - } else { - return pStr; - } - } - - std::string Unquote(const std::string &pStr) { - if (mSeparatorParams.mAutoQuote && (pStr.size() >= 2) && - (pStr.front() == '"') && (pStr.back() == '"')) { - // remove start/end quotes - std::string str = pStr.substr(1, pStr.size() - 2); - - // unescape quotes in string - ReplaceString(str, "\"\"", "\""); - - return str; - } else { - return pStr; - } - } - -#ifdef HAS_CODECVT -#if defined(_MSC_VER) -#pragma warning(disable : 4996) -#endif - static std::string ToString(const std::wstring &pWStr) { - return std::wstring_convert, wchar_t>{}.to_bytes( - pWStr); - } - - static std::wstring ToWString(const std::string &pStr) { - return std::wstring_convert, wchar_t>{} - .from_bytes(pStr); - } -#if defined(_MSC_VER) -#pragma warning(default : 4996) -#endif -#endif - - static void ReplaceString(std::string &pStr, const std::string &pSearch, - const std::string &pReplace) { - size_t pos = 0; - - while ((pos = pStr.find(pSearch, pos)) != std::string::npos) { - pStr.replace(pos, pSearch.size(), pReplace); - pos += pReplace.size(); - } - } - -private: - std::string mPath; - LabelParams mLabelParams; - SeparatorParams mSeparatorParams; - ConverterParams mConverterParams; - LineReaderParams mLineReaderParams; - std::vector> mData; - std::map mColumnNames; - std::map mRowNames; -#ifdef HAS_CODECVT - bool mIsUtf16 = false; - bool mIsLE = false; -#endif -}; +/* + * rapidcsv.h + * + * URL: https://github.com/d99kris/rapidcsv + * Version: 8.53 + * + * Copyright (C) 2017-2021 Kristofer Berggren + * All rights reserved. + * + * rapidcsv is distributed under the BSD 3-Clause license, see LICENSE for + * details. + * + */ + +#pragma once + +#include +#include +#include +#ifdef HAS_CODECVT +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif + +namespace rapidcsv { +#if defined(_MSC_VER) +static const bool sPlatformHasCR = true; +#else +static const bool sPlatformHasCR = false; +#endif + +/** + * @brief Datastructure holding parameters controlling how invalid numbers + * (including empty strings) should be handled. + */ +struct ConverterParams { + /** + * @brief Constructor + * @param pHasDefaultConverter specifies if conversion of non-numerical + * strings shall be converted to a default numerical value, instead of causing + * an exception to be thrown (default). + * @param pDefaultFloat floating-point default value to represent + * invalid numbers. + * @param pDefaultInteger integer default value to represent invalid + * numbers. + */ + explicit ConverterParams( + const bool pHasDefaultConverter = false, + const long double pDefaultFloat = + std::numeric_limits::signaling_NaN(), + const long long pDefaultInteger = 0) + : mHasDefaultConverter(pHasDefaultConverter), + mDefaultFloat(pDefaultFloat), mDefaultInteger(pDefaultInteger) {} + + /** + * @brief specifies if conversion of non-numerical strings shall be + * converted to a default numerical value, instead of causing an exception to + * be thrown (default). + */ + bool mHasDefaultConverter; + + /** + * @brief floating-point default value to represent invalid numbers. + */ + long double mDefaultFloat; + + /** + * @brief integer default value to represent invalid numbers. + */ + long long mDefaultInteger; +}; + +/** + * @brief Exception thrown when attempting to access Document data in a + * datatype which is not supported by the Converter class. + */ +class no_converter : public std::exception { + /** + * @brief Provides details about the exception + * @returns an explanatory string + */ + virtual const char *what() const throw() { + return "unsupported conversion datatype"; + } +}; + +/** + * @brief Class providing conversion to/from numerical datatypes and + * strings. Only intended for rapidcsv internal usage, but exposed externally to + * allow specialization for custom datatype conversions. + */ +template class Converter { +public: + /** + * @brief Constructor + * @param pConverterParams specifies how conversion of non-numerical + * values to numerical datatype shall be handled. + */ + Converter(const ConverterParams &pConverterParams) + : mConverterParams(pConverterParams) {} + + /** + * @brief Converts numerical value to string representation. + * @param pVal numerical value + * @param pStr output string + */ + void ToStr(const T &pVal, std::string &pStr) const { + if (typeid(T) == typeid(int) || typeid(T) == typeid(long) || + typeid(T) == typeid(long long) || typeid(T) == typeid(unsigned) || + typeid(T) == typeid(unsigned long) || + typeid(T) == typeid(unsigned long long) || typeid(T) == typeid(float) || + typeid(T) == typeid(double) || typeid(T) == typeid(long double) || + typeid(T) == typeid(char)) { + std::ostringstream out; + out << pVal; + pStr = out.str(); + } else { + throw no_converter(); + } + } + + /** + * @brief Converts string holding a numerical value to numerical datatype + * representation. + * @param pVal numerical value + * @param pStr output string + */ + void ToVal(const std::string &pStr, T &pVal) const { + try { + if (typeid(T) == typeid(int)) { + pVal = static_cast(std::stoi(pStr)); + return; + } else if (typeid(T) == typeid(long)) { + pVal = static_cast(std::stol(pStr)); + return; + } else if (typeid(T) == typeid(long long)) { + pVal = static_cast(std::stoll(pStr)); + return; + } else if (typeid(T) == typeid(unsigned)) { + pVal = static_cast(std::stoul(pStr)); + return; + } else if (typeid(T) == typeid(unsigned long)) { + pVal = static_cast(std::stoul(pStr)); + return; + } else if (typeid(T) == typeid(unsigned long long)) { + pVal = static_cast(std::stoull(pStr)); + return; + } + } catch (...) { + if (!mConverterParams.mHasDefaultConverter) { + throw; + } else { + pVal = static_cast(mConverterParams.mDefaultInteger); + return; + } + } + + try { + if (typeid(T) == typeid(float)) { + pVal = static_cast(std::stof(pStr)); + return; + } else if (typeid(T) == typeid(double)) { + pVal = static_cast(std::stod(pStr)); + return; + } else if (typeid(T) == typeid(long double)) { + pVal = static_cast(std::stold(pStr)); + return; + } + } catch (...) { + if (!mConverterParams.mHasDefaultConverter) { + throw; + } else { + pVal = static_cast(mConverterParams.mDefaultFloat); + return; + } + } + + if (typeid(T) == typeid(char)) { + pVal = static_cast(pStr[0]); + return; + } else { + throw no_converter(); + } + } + +private: + const ConverterParams &mConverterParams; +}; + +/** + * @brief Specialized implementation handling string to string conversion. + * @param pVal string + * @param pStr string + */ +template <> +inline void Converter::ToStr(const std::string &pVal, + std::string &pStr) const { + pStr = pVal; +} + +/** + * @brief Specialized implementation handling string to string conversion. + * @param pVal string + * @param pStr string + */ +template <> +inline void Converter::ToVal(const std::string &pStr, + std::string &pVal) const { + pVal = pStr; +} + +template +using ConvFunc = std::function; + +/** + * @brief Datastructure holding parameters controlling which row and column + * should be treated as labels. + */ +struct LabelParams { + /** + * @brief Constructor + * @param pColumnNameIdx specifies the zero-based row index of the + * column labels, setting it to -1 prevents column lookup by label name, and + * gives access to all rows as document data. Default: 0 + * @param pRowNameIdx specifies the zero-based column index of the + * row labels, setting it to -1 prevents row lookup by label name, and gives + * access to all columns as document data. Default: -1 + */ + explicit LabelParams(const int pColumnNameIdx = 0, const int pRowNameIdx = -1) + : mColumnNameIdx(pColumnNameIdx), mRowNameIdx(pRowNameIdx) {} + + /** + * @brief specifies the zero-based row index of the column labels. + */ + int mColumnNameIdx; + + /** + * @brief specifies the zero-based column index of the row labels. + */ + int mRowNameIdx; +}; + +/** + * @brief Datastructure holding parameters controlling how the CSV data + * fields are separated. + */ +struct SeparatorParams { + /** + * @brief Constructor + * @param pSeparator specifies the column separator (default + * ','). + * @param pTrim specifies whether to trim leading and + * trailing spaces from cells read (default false). + * @param pHasCR specifies whether a new document (i.e. not + * an existing document read) should use CR/LF instead of only LF (default is + * to use standard behavior of underlying platforms - CR/LF for Win, and LF + * for others). + * @param pQuotedLinebreaks specifies whether to allow line breaks in + * quoted text (default false) + * @param pAutoQuote specifies whether to automatically dequote + * data during read, and add quotes during write (default true). + */ + explicit SeparatorParams(const char pSeparator = ',', + const bool pTrim = false, + const bool pHasCR = sPlatformHasCR, + const bool pQuotedLinebreaks = false, + const bool pAutoQuote = true) + : mSeparator(pSeparator), mTrim(pTrim), mHasCR(pHasCR), + mQuotedLinebreaks(pQuotedLinebreaks), mAutoQuote(pAutoQuote) {} + + /** + * @brief specifies the column separator. + */ + char mSeparator; + + /** + * @brief specifies whether to trim leading and trailing spaces from cells + * read. + */ + bool mTrim; + + /** + * @brief specifies whether new documents should use CR/LF instead of LF. + */ + bool mHasCR; + + /** + * @brief specifies whether to allow line breaks in quoted text. + */ + bool mQuotedLinebreaks; + + /** + * @brief specifies whether to automatically dequote cell data. + */ + bool mAutoQuote; +}; + +/** + * @brief Datastructure holding parameters controlling how special line + * formats should be treated. + */ +struct LineReaderParams { + /** + * @brief Constructor + * @param pSkipCommentLines specifies whether to skip lines prefixed + * with mCommentPrefix. Default: false + * @param pCommentPrefix specifies which prefix character to indicate + * a comment line. Default: # + * @param pSkipEmptyLines specifies whether to skip empty lines. + * Default: false + */ + explicit LineReaderParams(const bool pSkipCommentLines = false, + const char pCommentPrefix = '#', + const bool pSkipEmptyLines = false) + : mSkipCommentLines(pSkipCommentLines), mCommentPrefix(pCommentPrefix), + mSkipEmptyLines(pSkipEmptyLines) {} + + /** + * @brief specifies whether to skip lines prefixed with mCommentPrefix. + */ + bool mSkipCommentLines; + + /** + * @brief specifies which prefix character to indicate a comment line. + */ + char mCommentPrefix; + + /** + * @brief specifies whether to skip empty lines. + */ + bool mSkipEmptyLines; +}; + +/** + * @brief Class representing a CSV document. + */ +class Document { +public: + /** + * @brief Constructor + * @param pPath specifies the path of an existing CSV-file + * to populate the Document data with. + * @param pLabelParams specifies which row and column should be + * treated as labels. + * @param pSeparatorParams specifies which field and row separators + * should be used. + * @param pConverterParams specifies how invalid numbers (including + * empty strings) should be handled. + * @param pLineReaderParams specifies how special line formats should be + * treated. + */ + explicit Document( + const std::string &pPath = std::string(), + const LabelParams &pLabelParams = LabelParams(), + const SeparatorParams &pSeparatorParams = SeparatorParams(), + const ConverterParams &pConverterParams = ConverterParams(), + const LineReaderParams &pLineReaderParams = LineReaderParams()) + : mPath(pPath), mLabelParams(pLabelParams), + mSeparatorParams(pSeparatorParams), mConverterParams(pConverterParams), + mLineReaderParams(pLineReaderParams) { + if (!mPath.empty()) { + ReadCsv(); + } + } + + /** + * @brief Constructor + * @param pStream specifies an input stream to read CSV data + * from. + * @param pLabelParams specifies which row and column should be + * treated as labels. + * @param pSeparatorParams specifies which field and row separators + * should be used. + * @param pConverterParams specifies how invalid numbers (including + * empty strings) should be handled. + * @param pLineReaderParams specifies how special line formats should be + * treated. + */ + explicit Document( + std::istream &pStream, const LabelParams &pLabelParams = LabelParams(), + const SeparatorParams &pSeparatorParams = SeparatorParams(), + const ConverterParams &pConverterParams = ConverterParams(), + const LineReaderParams &pLineReaderParams = LineReaderParams()) + : mPath(), mLabelParams(pLabelParams), mSeparatorParams(pSeparatorParams), + mConverterParams(pConverterParams), + mLineReaderParams(pLineReaderParams) { + ReadCsv(pStream); + } + + /** + * @brief Read Document data from file. + * @param pPath specifies the path of an existing CSV-file + * to populate the Document data with. + * @param pLabelParams specifies which row and column should be + * treated as labels. + * @param pSeparatorParams specifies which field and row separators + * should be used. + * @param pConverterParams specifies how invalid numbers (including + * empty strings) should be handled. + * @param pLineReaderParams specifies how special line formats should be + * treated. + */ + void Load(const std::string &pPath, + const LabelParams &pLabelParams = LabelParams(), + const SeparatorParams &pSeparatorParams = SeparatorParams(), + const ConverterParams &pConverterParams = ConverterParams(), + const LineReaderParams &pLineReaderParams = LineReaderParams()) { + mPath = pPath; + mLabelParams = pLabelParams; + mSeparatorParams = pSeparatorParams; + mConverterParams = pConverterParams; + mLineReaderParams = pLineReaderParams; + ReadCsv(); + } + + /** + * @brief Read Document data from stream. + * @param pStream specifies an input stream to read CSV data + * from. + * @param pLabelParams specifies which row and column should be + * treated as labels. + * @param pSeparatorParams specifies which field and row separators + * should be used. + * @param pConverterParams specifies how invalid numbers (including + * empty strings) should be handled. + * @param pLineReaderParams specifies how special line formats should be + * treated. + */ + void Load(std::istream &pStream, + const LabelParams &pLabelParams = LabelParams(), + const SeparatorParams &pSeparatorParams = SeparatorParams(), + const ConverterParams &pConverterParams = ConverterParams(), + const LineReaderParams &pLineReaderParams = LineReaderParams()) { + mPath = ""; + mLabelParams = pLabelParams; + mSeparatorParams = pSeparatorParams; + mConverterParams = pConverterParams; + mLineReaderParams = pLineReaderParams; + ReadCsv(pStream); + } + + /** + * @brief Write Document data to file. + * @param pPath optionally specifies the path where the + * CSV-file will be created (if not specified, the original path provided when + * creating or loading the Document data will be used). + */ + void Save(const std::string &pPath = std::string()) { + if (!pPath.empty()) { + mPath = pPath; + } + WriteCsv(); + } + + /** + * @brief Write Document data to stream. + * @param pStream specifies an output stream to write the data + * to. + */ + void Save(std::ostream &pStream) { WriteCsv(pStream); } + + /** + * @brief Clears loaded Document data. + * + */ + void Clear() { + mData.clear(); + mColumnNames.clear(); + mRowNames.clear(); +#ifdef HAS_CODECVT + mIsUtf16 = false; + mIsLE = false; +#endif + } + + /** + * @brief Get column index by name. + * @param pColumnName column label name. + * @returns zero-based column index. + */ + ssize_t GetColumnIdx(const std::string &pColumnName) const { + if (mLabelParams.mColumnNameIdx >= 0) { + if (mColumnNames.find(pColumnName) != mColumnNames.end()) { + return mColumnNames.at(pColumnName) - (mLabelParams.mRowNameIdx + 1); + } + } + return -1; + } + + /** + * @brief Get column by index. + * @param pColumnIdx zero-based column index. + * @returns vector of column data. + */ + template + std::vector GetColumn(const size_t pColumnIdx) const { + const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + std::vector column; + Converter converter(mConverterParams); + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) { + if (columnIdx < static_cast(itRow->size())) { + T val; + converter.ToVal(itRow->at(columnIdx), val); + column.push_back(val); + } else { + const std::string errStr = + "requested column index " + + std::to_string(columnIdx - (mLabelParams.mRowNameIdx + 1)) + + " >= " + + std::to_string(itRow->size() - (mLabelParams.mRowNameIdx + 1)) + + " (number of columns on row index " + + std::to_string(std::distance(mData.begin(), itRow) - + (mLabelParams.mColumnNameIdx + 1)) + + ")"; + throw std::out_of_range(errStr); + } + } + } + return column; + } + + /** + * @brief Get column by index. + * @param pColumnIdx zero-based column index. + * @param pToVal conversion function. + * @returns vector of column data. + */ + template + std::vector GetColumn(const size_t pColumnIdx, ConvFunc pToVal) const { + const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + std::vector column; + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) { + T val; + pToVal(itRow->at(columnIdx), val); + column.push_back(val); + } + } + return column; + } + + /** + * @brief Get column by name. + * @param pColumnName column label name. + * @returns vector of column data. + */ + template + std::vector GetColumn(const std::string &pColumnName) const { + const ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + return GetColumn(columnIdx); + } + + /** + * @brief Get column by name. + * @param pColumnName column label name. + * @param pToVal conversion function. + * @returns vector of column data. + */ + template + std::vector GetColumn(const std::string &pColumnName, + ConvFunc pToVal) const { + const ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + return GetColumn(columnIdx, pToVal); + } + + /** + * @brief Set column by index. + * @param pColumnIdx zero-based column index. + * @param pColumn vector of column data. + */ + template + void SetColumn(const size_t pColumnIdx, const std::vector &pColumn) { + const size_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + + while (pColumn.size() + (mLabelParams.mColumnNameIdx + 1) > + GetDataRowCount()) { + std::vector row; + row.resize(GetDataColumnCount()); + mData.push_back(row); + } + + if ((columnIdx + 1) > GetDataColumnCount()) { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + itRow->resize(columnIdx + 1 + (mLabelParams.mRowNameIdx + 1)); + } + } + + Converter converter(mConverterParams); + for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow) { + std::string str; + converter.ToStr(*itRow, str); + mData + .at(std::distance(pColumn.begin(), itRow) + + (mLabelParams.mColumnNameIdx + 1)) + .at(columnIdx) = str; + } + } + + /** + * @brief Set column by name. + * @param pColumnName column label name. + * @param pColumn vector of column data. + */ + template + void SetColumn(const std::string &pColumnName, + const std::vector &pColumn) { + const ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + SetColumn(columnIdx, pColumn); + } + + /** + * @brief Remove column by index. + * @param pColumnIdx zero-based column index. + */ + void RemoveColumn(const size_t pColumnIdx) { + const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + itRow->erase(itRow->begin() + columnIdx); + } + } + + /** + * @brief Remove column by name. + * @param pColumnName column label name. + */ + void RemoveColumn(const std::string &pColumnName) { + ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + + RemoveColumn(columnIdx); + } + + /** + * @brief Insert column at specified index. + * @param pColumnIdx zero-based column index. + * @param pColumn vector of column data (optional argument). + * @param pColumnName column label name (optional argument). + */ + template + void InsertColumn(const size_t pColumnIdx, + const std::vector &pColumn = std::vector(), + const std::string &pColumnName = std::string()) { + const size_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + + std::vector column; + if (pColumn.empty()) { + column.resize(GetDataRowCount()); + } else { + column.resize(pColumn.size() + (mLabelParams.mColumnNameIdx + 1)); + Converter converter(mConverterParams); + for (auto itRow = pColumn.begin(); itRow != pColumn.end(); ++itRow) { + std::string str; + converter.ToStr(*itRow, str); + const size_t rowIdx = std::distance(pColumn.begin(), itRow) + + (mLabelParams.mColumnNameIdx + 1); + column.at(rowIdx) = str; + } + } + + while (column.size() > GetDataRowCount()) { + std::vector row; + const size_t columnCount = + std::max(static_cast(mLabelParams.mColumnNameIdx + 1), + GetDataColumnCount()); + row.resize(columnCount); + mData.push_back(row); + } + + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + const size_t rowIdx = std::distance(mData.begin(), itRow); + itRow->insert(itRow->begin() + columnIdx, column.at(rowIdx)); + } + + if (!pColumnName.empty()) { + SetColumnName(pColumnIdx, pColumnName); + } + } + + /** + * @brief Get number of data columns (excluding label columns). + * @returns column count. + */ + size_t GetColumnCount() const { + const ssize_t count = + static_cast((mData.size() > 0) ? mData.at(0).size() : 0) - + (mLabelParams.mRowNameIdx + 1); + return (count >= 0) ? count : 0; + } + + /** + * @brief Get row index by name. + * @param pRowName row label name. + * @returns zero-based row index. + */ + ssize_t GetRowIdx(const std::string &pRowName) const { + if (mLabelParams.mRowNameIdx >= 0) { + if (mRowNames.find(pRowName) != mRowNames.end()) { + return mRowNames.at(pRowName) - (mLabelParams.mColumnNameIdx + 1); + } + } + return -1; + } + + /** + * @brief Get row by index. + * @param pRowIdx zero-based row index. + * @returns vector of row data. + */ + template std::vector GetRow(const size_t pRowIdx) const { + const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + std::vector row; + Converter converter(mConverterParams); + for (auto itCol = mData.at(rowIdx).begin(); itCol != mData.at(rowIdx).end(); + ++itCol) { + if (std::distance(mData.at(rowIdx).begin(), itCol) > + mLabelParams.mRowNameIdx) { + T val; + converter.ToVal(*itCol, val); + row.push_back(val); + } + } + return row; + } + + /** + * @brief Get row by index. + * @param pRowIdx zero-based row index. + * @param pToVal conversion function. + * @returns vector of row data. + */ + template + std::vector GetRow(const size_t pRowIdx, ConvFunc pToVal) const { + const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + std::vector row; + Converter converter(mConverterParams); + for (auto itCol = mData.at(rowIdx).begin(); itCol != mData.at(rowIdx).end(); + ++itCol) { + if (std::distance(mData.at(rowIdx).begin(), itCol) > + mLabelParams.mRowNameIdx) { + T val; + pToVal(*itCol, val); + row.push_back(val); + } + } + return row; + } + + /** + * @brief Get row by name. + * @param pRowName row label name. + * @returns vector of row data. + */ + template + std::vector GetRow(const std::string &pRowName) const { + ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + return GetRow(rowIdx); + } + + /** + * @brief Get row by name. + * @param pRowName row label name. + * @param pToVal conversion function. + * @returns vector of row data. + */ + template + std::vector GetRow(const std::string &pRowName, ConvFunc pToVal) const { + ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + return GetRow(rowIdx, pToVal); + } + + /** + * @brief Set row by index. + * @param pRowIdx zero-based row index. + * @param pRow vector of row data. + */ + template + void SetRow(const size_t pRowIdx, const std::vector &pRow) { + const size_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + + while ((rowIdx + 1) > GetDataRowCount()) { + std::vector row; + row.resize(GetDataColumnCount()); + mData.push_back(row); + } + + if (pRow.size() > GetDataColumnCount()) { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + itRow->resize(pRow.size() + (mLabelParams.mRowNameIdx + 1)); + } + } + + Converter converter(mConverterParams); + for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol) { + std::string str; + converter.ToStr(*itCol, str); + mData.at(rowIdx).at(std::distance(pRow.begin(), itCol) + + (mLabelParams.mRowNameIdx + 1)) = str; + } + } + + /** + * @brief Set row by name. + * @param pRowName row label name. + * @param pRow vector of row data. + */ + template + void SetRow(const std::string &pRowName, const std::vector &pRow) { + ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + return SetRow(rowIdx, pRow); + } + + /** + * @brief Remove row by index. + * @param pRowIdx zero-based row index. + */ + void RemoveRow(const size_t pRowIdx) { + const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + mData.erase(mData.begin() + rowIdx); + } + + /** + * @brief Remove row by name. + * @param pRowName row label name. + */ + void RemoveRow(const std::string &pRowName) { + ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + + RemoveRow(rowIdx); + } + + /** + * @brief Insert row at specified index. + * @param pRowIdx zero-based row index. + * @param pRow vector of row data (optional argument). + * @param pRowName row label name (optional argument). + */ + template + void InsertRow(const size_t pRowIdx, + const std::vector &pRow = std::vector(), + const std::string &pRowName = std::string()) { + const size_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + + std::vector row; + if (pRow.empty()) { + row.resize(GetDataColumnCount()); + } else { + row.resize(pRow.size() + (mLabelParams.mRowNameIdx + 1)); + Converter converter(mConverterParams); + for (auto itCol = pRow.begin(); itCol != pRow.end(); ++itCol) { + std::string str; + converter.ToStr(*itCol, str); + row.at(std::distance(pRow.begin(), itCol) + + (mLabelParams.mRowNameIdx + 1)) = str; + } + } + + while (rowIdx > GetDataRowCount()) { + std::vector tempRow; + tempRow.resize(GetDataColumnCount()); + mData.push_back(tempRow); + } + + mData.insert(mData.begin() + rowIdx, row); + + if (!pRowName.empty()) { + SetRowName(pRowIdx, pRowName); + } + } + + /** + * @brief Get number of data rows (excluding label rows). + * @returns row count. + */ + size_t GetRowCount() const { + const ssize_t count = + static_cast(mData.size()) - (mLabelParams.mColumnNameIdx + 1); + return (count >= 0) ? count : 0; + } + + /** + * @brief Get cell by index. + * @param pColumnIdx zero-based column index. + * @param pRowIdx zero-based row index. + * @returns cell data. + */ + template + T GetCell(const size_t pColumnIdx, const size_t pRowIdx) const { + const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + + T val; + Converter converter(mConverterParams); + converter.ToVal(mData.at(rowIdx).at(columnIdx), val); + return val; + } + + /** + * @brief Get cell by index. + * @param pColumnIdx zero-based column index. + * @param pRowIdx zero-based row index. + * @param pToVal conversion function. + * @returns cell data. + */ + template + T GetCell(const size_t pColumnIdx, const size_t pRowIdx, + ConvFunc pToVal) const { + const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + + T val; + pToVal(mData.at(rowIdx).at(columnIdx), val); + return val; + } + + /** + * @brief Get cell by name. + * @param pColumnName column label name. + * @param pRowName row label name. + * @returns cell data. + */ + template + T GetCell(const std::string &pColumnName, const std::string &pRowName) const { + const ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + + const ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + + return GetCell(columnIdx, rowIdx); + } + + /** + * @brief Get cell by name. + * @param pColumnName column label name. + * @param pRowName row label name. + * @param pToVal conversion function. + * @returns cell data. + */ + template + T GetCell(const std::string &pColumnName, const std::string &pRowName, + ConvFunc pToVal) const { + const ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + + const ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + + return GetCell(columnIdx, rowIdx, pToVal); + } + + /** + * @brief Get cell by column name and row index. + * @param pColumnName column label name. + * @param pRowIdx zero-based row index. + * @returns cell data. + */ + template + T GetCell(const std::string &pColumnName, const size_t pRowIdx) const { + const ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + + return GetCell(columnIdx, pRowIdx); + } + + /** + * @brief Get cell by column name and row index. + * @param pColumnName column label name. + * @param pRowIdx zero-based row index. + * @param pToVal conversion function. + * @returns cell data. + */ + template + T GetCell(const std::string &pColumnName, const size_t pRowIdx, + ConvFunc pToVal) const { + const ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + + return GetCell(columnIdx, pRowIdx, pToVal); + } + + /** + * @brief Get cell by column index and row name. + * @param pColumnIdx zero-based column index. + * @param pRowName row label name. + * @returns cell data. + */ + template + T GetCell(const size_t pColumnIdx, const std::string &pRowName) const { + const ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + + return GetCell(pColumnIdx, rowIdx); + } + + /** + * @brief Get cell by column index and row name. + * @param pColumnIdx zero-based column index. + * @param pRowName row label name. + * @param pToVal conversion function. + * @returns cell data. + */ + template + T GetCell(const size_t pColumnIdx, const std::string &pRowName, + ConvFunc pToVal) const { + const ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + + return GetCell(pColumnIdx, rowIdx, pToVal); + } + + /** + * @brief Set cell by index. + * @param pRowIdx zero-based row index. + * @param pColumnIdx zero-based column index. + * @param pCell cell data. + */ + template + void SetCell(const size_t pColumnIdx, const size_t pRowIdx, const T &pCell) { + const size_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + const size_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + + while ((rowIdx + 1) > GetDataRowCount()) { + std::vector row; + row.resize(GetDataColumnCount()); + mData.push_back(row); + } + + if ((columnIdx + 1) > GetDataColumnCount()) { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + itRow->resize(columnIdx + 1); + } + } + + std::string str; + Converter converter(mConverterParams); + converter.ToStr(pCell, str); + mData.at(rowIdx).at(columnIdx) = str; + } + + /** + * @brief Set cell by name. + * @param pColumnName column label name. + * @param pRowName row label name. + * @param pCell cell data. + */ + template + void SetCell(const std::string &pColumnName, const std::string &pRowName, + const T &pCell) { + const ssize_t columnIdx = GetColumnIdx(pColumnName); + if (columnIdx < 0) { + throw std::out_of_range("column not found: " + pColumnName); + } + + const ssize_t rowIdx = GetRowIdx(pRowName); + if (rowIdx < 0) { + throw std::out_of_range("row not found: " + pRowName); + } + + SetCell(columnIdx, rowIdx, pCell); + } + + /** + * @brief Get column name + * @param pColumnIdx zero-based column index. + * @returns column name. + */ + std::string GetColumnName(const ssize_t pColumnIdx) { + const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + if (mLabelParams.mColumnNameIdx < 0) { + throw std::out_of_range("column name row index < 0: " + + std::to_string(mLabelParams.mColumnNameIdx)); + } + + return mData.at(mLabelParams.mColumnNameIdx).at(columnIdx); + } + + /** + * @brief Set column name + * @param pColumnIdx zero-based column index. + * @param pColumnName column name. + */ + void SetColumnName(size_t pColumnIdx, const std::string &pColumnName) { + const ssize_t columnIdx = pColumnIdx + (mLabelParams.mRowNameIdx + 1); + mColumnNames[pColumnName] = columnIdx; + if (mLabelParams.mColumnNameIdx < 0) { + throw std::out_of_range("column name row index < 0: " + + std::to_string(mLabelParams.mColumnNameIdx)); + } + + // increase table size if necessary: + const int rowIdx = mLabelParams.mColumnNameIdx; + if (rowIdx >= static_cast(mData.size())) { + mData.resize(rowIdx + 1); + } + auto &row = mData[rowIdx]; + if (columnIdx >= static_cast(row.size())) { + row.resize(columnIdx + 1); + } + + mData.at(mLabelParams.mColumnNameIdx).at(columnIdx) = pColumnName; + } + + /** + * @brief Get column names + * @returns vector of column names. + */ + std::vector GetColumnNames() { + if (mLabelParams.mColumnNameIdx >= 0) { + return std::vector( + mData.at(mLabelParams.mColumnNameIdx).begin() + + (mLabelParams.mRowNameIdx + 1), + mData.at(mLabelParams.mColumnNameIdx).end()); + } + + return std::vector(); + } + + /** + * @brief Get row name + * @param pRowIdx zero-based column index. + * @returns row name. + */ + std::string GetRowName(const ssize_t pRowIdx) { + const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + if (mLabelParams.mRowNameIdx < 0) { + throw std::out_of_range("row name column index < 0: " + + std::to_string(mLabelParams.mRowNameIdx)); + } + + return mData.at(rowIdx).at(mLabelParams.mRowNameIdx); + } + + /** + * @brief Set row name + * @param pRowIdx zero-based row index. + * @param pRowName row name. + */ + void SetRowName(size_t pRowIdx, const std::string &pRowName) { + const ssize_t rowIdx = pRowIdx + (mLabelParams.mColumnNameIdx + 1); + mRowNames[pRowName] = rowIdx; + if (mLabelParams.mRowNameIdx < 0) { + throw std::out_of_range("row name column index < 0: " + + std::to_string(mLabelParams.mRowNameIdx)); + } + + // increase table size if necessary: + if (rowIdx >= static_cast(mData.size())) { + mData.resize(rowIdx + 1); + } + auto &row = mData[rowIdx]; + if (mLabelParams.mRowNameIdx >= static_cast(row.size())) { + row.resize(mLabelParams.mRowNameIdx + 1); + } + + mData.at(rowIdx).at(mLabelParams.mRowNameIdx) = pRowName; + } + + /** + * @brief Get row names + * @returns vector of row names. + */ + std::vector GetRowNames() { + std::vector rownames; + if (mLabelParams.mRowNameIdx >= 0) { + for (auto itRow = mData.begin(); itRow != mData.end(); ++itRow) { + if (std::distance(mData.begin(), itRow) > mLabelParams.mColumnNameIdx) { + rownames.push_back(itRow->at(mLabelParams.mRowNameIdx)); + } + } + } + return rownames; + } + +private: + void ReadCsv() { + std::ifstream stream; + stream.exceptions(std::ifstream::failbit | std::ifstream::badbit); + stream.open(mPath, std::ios::binary); + ReadCsv(stream); + } + + void ReadCsv(std::istream &pStream) { + Clear(); + pStream.seekg(0, std::ios::end); + std::streamsize length = pStream.tellg(); + pStream.seekg(0, std::ios::beg); + +#ifdef HAS_CODECVT + std::vector bom2b(2, '\0'); + if (length >= 2) { + pStream.read(bom2b.data(), 2); + pStream.seekg(0, std::ios::beg); + } + + static const std::vector bomU16le = {'\xff', '\xfe'}; + static const std::vector bomU16be = {'\xfe', '\xff'}; + if ((bom2b == bomU16le) || (bom2b == bomU16be)) { + mIsUtf16 = true; + mIsLE = (bom2b == bomU16le); + + std::wifstream wstream; + wstream.exceptions(std::wifstream::failbit | std::wifstream::badbit); + wstream.open(mPath, std::ios::binary); + if (mIsLE) { + wstream.imbue( + std::locale(wstream.getloc(), + new std::codecvt_utf16( + std::consume_header | + std::little_endian)>)); + } else { + wstream.imbue(std::locale( + wstream.getloc(), + new std::codecvt_utf16)); + } + std::wstringstream wss; + wss << wstream.rdbuf(); + std::string utf8 = ToString(wss.str()); + std::stringstream ss(utf8); + ParseCsv(ss, utf8.size()); + } else +#endif + { + // check for UTF-8 Byte order mark and skip it when found + if (length >= 3) { + std::vector bom3b(3, '\0'); + pStream.read(bom3b.data(), 3); + static const std::vector bomU8 = {'\xef', '\xbb', '\xbf'}; + if (bom3b != bomU8) { + // file does not start with a UTF-8 Byte order mark + pStream.seekg(0, std::ios::beg); + } else { + // file did start with a UTF-8 Byte order mark, simply skip it + length -= 3; + } + } + + ParseCsv(pStream, length); + } + } + + void ParseCsv(std::istream &pStream, std::streamsize p_FileLength) { + const std::streamsize bufLength = 64 * 1024; + std::vector buffer(bufLength); + std::vector row; + std::string cell; + bool quoted = false; + int cr = 0; + int lf = 0; + + while (p_FileLength > 0) { + std::streamsize readLength = + std::min(p_FileLength, bufLength); + pStream.read(buffer.data(), readLength); + for (int i = 0; i < readLength; ++i) { + if (buffer[i] == '"') { + if (cell.empty() || cell[0] == '"') { + quoted = !quoted; + } + cell += buffer[i]; + } else if (buffer[i] == mSeparatorParams.mSeparator) { + if (!quoted) { + row.push_back(Unquote(Trim(cell))); + cell.clear(); + } else { + cell += buffer[i]; + } + } else if (buffer[i] == '\r') { + if (mSeparatorParams.mQuotedLinebreaks && quoted) { + cell += buffer[i]; + } else { + ++cr; + } + } else if (buffer[i] == '\n') { + if (mSeparatorParams.mQuotedLinebreaks && quoted) { + cell += buffer[i]; + } else { + ++lf; + if (mLineReaderParams.mSkipEmptyLines && row.empty() && + cell.empty()) { + // skip empty line + } else { + row.push_back(Unquote(Trim(cell))); + + if (mLineReaderParams.mSkipCommentLines && !row.at(0).empty() && + (row.at(0)[0] == mLineReaderParams.mCommentPrefix)) { + // skip comment line + } else { + mData.push_back(row); + } + + cell.clear(); + row.clear(); + quoted = false; + } + } + } else { + cell += buffer[i]; + } + } + p_FileLength -= readLength; + } + + // Handle last line without linebreak + if (!cell.empty() || !row.empty()) { + row.push_back(Unquote(Trim(cell))); + cell.clear(); + mData.push_back(row); + row.clear(); + } + + // Assume CR/LF if at least half the linebreaks have CR + mSeparatorParams.mHasCR = (cr > (lf / 2)); + + // Set up column labels + if ((mLabelParams.mColumnNameIdx >= 0) && + (static_cast(mData.size()) > mLabelParams.mColumnNameIdx)) { + int i = 0; + for (auto &columnName : mData[mLabelParams.mColumnNameIdx]) { + mColumnNames[columnName] = i++; + } + } + + // Set up row labels + if ((mLabelParams.mRowNameIdx >= 0) && + (static_cast(mData.size()) > + (mLabelParams.mColumnNameIdx + 1))) { + int i = 0; + for (auto &dataRow : mData) { + if (static_cast(dataRow.size()) > mLabelParams.mRowNameIdx) { + mRowNames[dataRow[mLabelParams.mRowNameIdx]] = i++; + } + } + } + } + + void WriteCsv() const { +#ifdef HAS_CODECVT + if (mIsUtf16) { + std::stringstream ss; + WriteCsv(ss); + std::string utf8 = ss.str(); + std::wstring wstr = ToWString(utf8); + + std::wofstream wstream; + wstream.exceptions(std::wofstream::failbit | std::wofstream::badbit); + wstream.open(mPath, std::ios::binary | std::ios::trunc); + + if (mIsLE) { + wstream.imbue( + std::locale(wstream.getloc(), + new std::codecvt_utf16( + std::little_endian)>)); + } else { + wstream.imbue(std::locale(wstream.getloc(), + new std::codecvt_utf16)); + } + + wstream << static_cast(0xfeff); + wstream << wstr; + } else +#endif + { + std::ofstream stream; + stream.exceptions(std::ofstream::failbit | std::ofstream::badbit); + stream.open(mPath, std::ios::binary | std::ios::trunc); + WriteCsv(stream); + } + } + + void WriteCsv(std::ostream &pStream) const { + for (auto itr = mData.begin(); itr != mData.end(); ++itr) { + for (auto itc = itr->begin(); itc != itr->end(); ++itc) { + if (mSeparatorParams.mAutoQuote && + ((itc->find(mSeparatorParams.mSeparator) != std::string::npos) || + (itc->find(' ') != std::string::npos))) { + // escape quotes in string + std::string str = *itc; + ReplaceString(str, "\"", "\"\""); + + pStream << "\"" << str << "\""; + } else { + pStream << *itc; + } + + if (std::distance(itc, itr->end()) > 1) { + pStream << mSeparatorParams.mSeparator; + } + } + pStream << (mSeparatorParams.mHasCR ? "\r\n" : "\n"); + } + } + + size_t GetDataRowCount() const { return mData.size(); } + + size_t GetDataColumnCount() const { + return (mData.size() > 0) ? mData.at(0).size() : 0; + } + + std::string Trim(const std::string &pStr) { + if (mSeparatorParams.mTrim) { + std::string str = pStr; + + // ltrim + str.erase(str.begin(), std::find_if(str.begin(), str.end(), + [](int ch) { return !isspace(ch); })); + + // rtrim + str.erase(std::find_if(str.rbegin(), str.rend(), + [](int ch) { return !isspace(ch); }) + .base(), + str.end()); + + return str; + } else { + return pStr; + } + } + + std::string Unquote(const std::string &pStr) { + if (mSeparatorParams.mAutoQuote && (pStr.size() >= 2) && + (pStr.front() == '"') && (pStr.back() == '"')) { + // remove start/end quotes + std::string str = pStr.substr(1, pStr.size() - 2); + + // unescape quotes in string + ReplaceString(str, "\"\"", "\""); + + return str; + } else { + return pStr; + } + } + +#ifdef HAS_CODECVT +#if defined(_MSC_VER) +#pragma warning(disable : 4996) +#endif + static std::string ToString(const std::wstring &pWStr) { + return std::wstring_convert, wchar_t>{}.to_bytes( + pWStr); + } + + static std::wstring ToWString(const std::string &pStr) { + return std::wstring_convert, wchar_t>{} + .from_bytes(pStr); + } +#if defined(_MSC_VER) +#pragma warning(default : 4996) +#endif +#endif + + static void ReplaceString(std::string &pStr, const std::string &pSearch, + const std::string &pReplace) { + size_t pos = 0; + + while ((pos = pStr.find(pSearch, pos)) != std::string::npos) { + pStr.replace(pos, pSearch.size(), pReplace); + pos += pReplace.size(); + } + } + +private: + std::string mPath; + LabelParams mLabelParams; + SeparatorParams mSeparatorParams; + ConverterParams mConverterParams; + LineReaderParams mLineReaderParams; + std::vector> mData; + std::map mColumnNames; + std::map mRowNames; +#ifdef HAS_CODECVT + bool mIsUtf16 = false; + bool mIsLE = false; +#endif +}; } // namespace rapidcsv \ No newline at end of file diff --git a/client/python/setup.py b/client/python/setup.py index 453d849a..ef627cd4 100644 --- a/client/python/setup.py +++ b/client/python/setup.py @@ -5,11 +5,11 @@ setuptools.setup( name="vdms", - version="0.0.18", + version="0.0.19", author="Chaunté W. Lacewell", author_email="chaunte.w.lacewell@intel.com", description="VDMS Client Module", - install_requires=["protobuf==3.20.3"], + install_requires=["protobuf==4.24.2"], long_description=long_description, long_description_content_type="text/markdown", url="https://github.com/IntelLabs/vdms", diff --git a/client/python/vdms/queryMessage_pb2.py b/client/python/vdms/queryMessage_pb2.py index f751c403..4bd962f5 100644 --- a/client/python/vdms/queryMessage_pb2.py +++ b/client/python/vdms/queryMessage_pb2.py @@ -2,10 +2,10 @@ # Generated by the protocol buffer compiler. DO NOT EDIT! # source: queryMessage.proto """Generated protocol buffer code.""" -from google.protobuf.internal import builder as _builder from google.protobuf import descriptor as _descriptor from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database +from google.protobuf.internal import builder as _builder # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -15,11 +15,11 @@ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x12queryMessage.proto\x12\x0eVDMS.protobufs\"+\n\x0cqueryMessage\x12\x0c\n\x04json\x18\x01 \x01(\t\x12\r\n\x05\x62lobs\x18\x02 \x03(\x0c\x62\x06proto3') -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'queryMessage_pb2', globals()) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'queryMessage_pb2', _globals) if _descriptor._USE_C_DESCRIPTORS == False: - DESCRIPTOR._options = None - _QUERYMESSAGE._serialized_start=38 - _QUERYMESSAGE._serialized_end=81 + _globals['_QUERYMESSAGE']._serialized_start=38 + _globals['_QUERYMESSAGE']._serialized_end=81 # @@protoc_insertion_point(module_scope) diff --git a/distributed/CMakeLists.txt b/distributed/CMakeLists.txt index 5b196c87..6ee16298 100644 --- a/distributed/CMakeLists.txt +++ b/distributed/CMakeLists.txt @@ -3,24 +3,21 @@ project(kaka_test VERSION 0.1.0 LANGUAGES "CXX") add_compile_options(-g -fPIC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -O0 -Wall -coverage -fprofile-arcs -ftest-coverage") -find_package(Protobuf REQUIRED) + +find_package(Protobuf CONFIG REQUIRED) + include_directories(${CMAKE_CURRENT_BINARY_DIR}) -# protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ../utils/src/protobuf/partitionerMessages.proto ../utils/src/protobuf/pmgdMessages.proto ../utils/src/protobuf/queryMessage.proto) -# add_library(vdms_protobuf SHARED ${PROTO_SRCS} ${PROTO_HDRS}) -include_directories(../client/cpp ../utils/include librdkafka/src -/usr/include/jsoncpp/ . .. ) +include_directories(../client/cpp ../utils/include librdkafka/src /usr/include/jsoncpp/ . ..) link_directories( /usr/lib /usr/local/lib /usr/lib/x86_64-linux-gnu/ . ) -add_executable(meta_data kafka_test.cpp ) -target_link_libraries( meta_data jsoncpp protobuf -vdms-utils vdms-client vdms_protobuf rdkafka rdkafka++ pthread glog) +add_executable(meta_data kafka_test.cpp) +target_link_libraries(meta_data jsoncpp protobuf vdms-utils vdms-client vdms_protobuf rdkafka rdkafka++ pthread glog) + +add_executable(image_data mutli_modal.cpp) +target_link_libraries(image_data jsoncpp protobuf vdms-utils vdms-client vdms_protobuf rdkafka rdkafka++ pthread glog) -add_executable(image_data mutli_modal.cpp ) -target_link_libraries(image_data jsoncpp protobuf -vdms-utils vdms-client vdms_protobuf rdkafka rdkafka++ pthread glog) -add_executable(multi-modal adaptive_platform.cpp ) -target_link_libraries(multi-modal jsoncpp protobuf -vdms-utils vdms-client vdms_protobuf rdkafka rdkafka++ pthread glog) +add_executable(multi-modal adaptive_platform.cpp) +target_link_libraries(multi-modal jsoncpp protobuf vdms-utils vdms-client vdms_protobuf rdkafka rdkafka++ pthread glog) set(CPACK_PROJECT_NAME ${PROJECT_NAME}) diff --git a/docker/base/Dockerfile b/docker/base/Dockerfile index 31d4d83b..ba4a1101 100644 --- a/docker/base/Dockerfile +++ b/docker/base/Dockerfile @@ -10,11 +10,11 @@ FROM debian:${BASE_VERSION} ARG BUILD_THREADS # Install Packages -RUN apt-get update && apt-get install -y --no-install-suggests --no-install-recommends \ +RUN apt-get update -y && apt-get upgrade -y && apt-get install -y --no-install-suggests --no-install-recommends \ apt-transport-https autoconf automake bison build-essential bzip2 ca-certificates \ curl ed flex g++-9 gcc-9 git gnupg-agent javacc libarchive-tools libatlas-base-dev \ libavcodec-dev libavformat-dev libboost-all-dev libbz2-dev libc-ares-dev libcurl4-openssl-dev \ - libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtest-dev libgtk-3-dev libgtk2.0-dev \ + libdc1394-22-dev libgflags-dev libgoogle-glog-dev libgtk-3-dev libgtk2.0-dev \ libhdf5-dev libjpeg-dev libjpeg62-turbo-dev libjsoncpp-dev libleveldb-dev liblmdb-dev \ liblz4-dev libopenblas-dev libopenmpi-dev libpng-dev librdkafka-dev libsnappy-dev libssl-dev \ libswscale-dev libtbb-dev libtbb2 libtiff-dev libtiff5-dev libtool libzmq3-dev linux-libc-dev mpich \ @@ -26,8 +26,8 @@ RUN apt-get update && apt-get install -y --no-install-suggests --no-install-reco ln -s /usr/bin/python3 /usr/bin/python # Pull and Install Dependencies -ENV CMAKE_VERSION="v3.26.4" \ - PROTOBUF_VERSION="3.20.3" \ +ENV CMAKE_VERSION="v3.27.2" \ + PROTOBUF_VERSION="24.2" \ OPENCV_VERSION="4.5.5" \ FAISS_VERSION="v1.7.3" \ VALIJSON_VERSION="v0.6" \ @@ -35,23 +35,32 @@ ENV CMAKE_VERSION="v3.26.4" \ TILEDB_VERSION="2.14.1" WORKDIR /dependencies -RUN pip install --no-cache-dir "numpy>=1.25.1" "protobuf==${PROTOBUF_VERSION}" "coverage>=7.2.7" && \ +RUN pip install --no-cache-dir "numpy>=1.25.1" "coverage>=7.2.7" && \ git clone --branch ${CMAKE_VERSION} https://github.com/Kitware/CMake.git && \ - cd CMake && ./bootstrap && make ${BUILD_THREADS} && make install && \ - cd /usr/src/gtest && cmake . && make ${BUILD_THREADS} && mv lib/libgtest* /usr/lib/ && \ - git clone --branch ${FAISS_VERSION} https://github.com/facebookresearch/faiss.git /dependencies/faiss && \ - cd /dependencies/faiss && mkdir build && cd build && cmake -DFAISS_ENABLE_GPU=OFF .. && \ - make ${BUILD_THREADS} && make install && cd /dependencies/ && \ + cd CMake && ./bootstrap && make ${BUILD_THREADS} && make install && cd /dependencies/ && \ + git clone --branch ${FAISS_VERSION} https://github.com/facebookresearch/faiss.git && \ + cd /dependencies/faiss && mkdir build && cd build && \ + cmake -DFAISS_ENABLE_GPU=OFF -DPython_EXECUTABLE=/usr/bin/python3 .. && \ + make ${BUILD_THREADS} && make install && cd /dependencies/ && \ git clone https://github.com/tonyzhang617/FLINNG.git && \ - cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && make ${BUILD_THREADS} && make install && cd /dependencies && \ - curl -L -o /dependencies/${PROTOBUF_VERSION}.tar.gz \ - https://github.com/protocolbuffers/protobuf/archive/refs/tags/v${PROTOBUF_VERSION}.tar.gz && \ - cd /dependencies/ && tar -xvf ${PROTOBUF_VERSION}.tar.gz && \ - cd protobuf-${PROTOBUF_VERSION} && ./autogen.sh && ./configure && make -j$(nproc) && \ - make install && ldconfig && cd /dependencies && \ + cd /dependencies/FLINNG && mkdir build && cd build && cmake .. && \ + make ${BUILD_THREADS} && make install && cd /dependencies && \ + git clone -b v${PROTOBUF_VERSION} --recursive https://github.com/protocolbuffers/protobuf.git && \ + cd /dependencies/protobuf/third_party/googletest && mkdir build && cd build/ && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_GMOCK=ON -DCMAKE_CXX_STANDARD=17 .. && \ + make ${BUILD_THREADS} && make install && ldconfig && \ + cd ../../abseil-cpp && mkdir build && cd build && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_PREFIX_PATH=/usr/local/ -DCMAKE_INSTALL_PREFIX=/usr/local/ -DABSL_BUILD_TESTING=ON \ + -DABSL_ENABLE_INSTALL=ON -DABSL_USE_EXTERNAL_GOOGLETEST=ON -DABSL_FIND_GOOGLETEST=ON -DCMAKE_CXX_STANDARD=17 .. && \ + make ${BUILD_THREADS} && make install && \ + cd /dependencies/protobuf && \ + cmake -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_CXX_STANDARD=17 \ + -Dprotobuf_ABSL_PROVIDER=package -DCMAKE_PREFIX_PATH=/usr/local . && \ + make ${BUILD_THREADS} && make install && \ + python3 -m pip install --no-cache-dir "protobuf==4.${PROTOBUF_VERSION}" && cd /dependencies && \ git clone --branch ${OPENCV_VERSION} https://github.com/opencv/opencv.git && \ cd opencv && mkdir build && cd build && cmake -D BUILD_PERF_TESTS=OFF -D BUILD_TESTS=OFF .. && \ - make ${BUILD_THREADS} && make install && cd /dependencies/ && \ + make ${BUILD_THREADS} && make install && cd /dependencies/ && \ git clone --branch ${VALIJSON_VERSION} https://github.com/tristanpenman/valijson.git && \ cd valijson && cp -r include/* /usr/local/include/ && cd /dependencies && \ curl -L -o /dependencies/${TILEDB_VERSION}.tar.gz \ diff --git a/include/vcl/Image.h b/include/vcl/Image.h index a2a0febc..a872975c 100644 --- a/include/vcl/Image.h +++ b/include/vcl/Image.h @@ -268,6 +268,11 @@ class Image { */ Json::Value get_remoteOp_params(); + /** + * @return The error message if the query fails. Null if query is a success. + */ + std::string get_query_error_response(); + /* *********************** */ /* SET FUNCTIONS */ /* *********************** */ @@ -311,6 +316,8 @@ class Image { void set_connection(RemoteConnection *remote); + void set_query_error_response(std::string error_msg); + /* *********************** */ /* IMAGE INTERACTIONS */ /* *********************** */ @@ -486,6 +493,9 @@ class Image { // Full path to image std::string _image_id; + // Query Error response + std::string _query_error_response = ""; + // Image data (OpenCV Mat or TDBImage) cv::Mat _cv_img; TDBImage *_tdb; diff --git a/include/vcl/Video.h b/include/vcl/Video.h index 4544a264..2e0cb851 100644 --- a/include/vcl/Video.h +++ b/include/vcl/Video.h @@ -5,7 +5,7 @@ * * The MIT License * - * @copyright Copyright (c) 2017 Intel Corporation + * @copyright Copyright (c) 2023 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -43,19 +43,20 @@ #include "KeyFrame.h" #include "vcl/Image.h" +#include "../utils/include/stats/SystemStats.h" #include "Exception.h" #include "utils.h" namespace VCL { -typedef cv::Rect Rectangle; // spcifiy an ROI inside a video +typedef cv::Rect Rectangle; // specify an ROI inside a video class Video { public: enum Codec { NOCODEC = 0, MJPG, XVID, H263, H264, AVC1 }; - // enum class Storage { LOCAL = 0, AWS = 1 }; + std::string NOERRORSTRING = ""; struct VideoSize { unsigned width; @@ -65,9 +66,17 @@ class Video { enum Unit { FRAMES = 0, SECONDS = 1 }; - enum OperationType { READ, WRITE, RESIZE, CROP, THRESHOLD, INTERVAL }; - - enum OperationResult { PASS, CONTINUE, BREAK }; + enum OperationType { + READ, + WRITE, + RESIZE, + CROP, + THRESHOLD, + INTERVAL, + SYNCREMOTEOPERATION, + REMOTEOPERATION, + USEROPERATION + }; RemoteConnection *_remote; // Remote connection (if one exists) @@ -137,24 +146,27 @@ class Video { /** * Gets the size of the Video in pixels (height * width * channels) - * + * @param performOp Specify if operations should be performed first. Default + * is true. * @return The size of the Video in pixels */ - VideoSize get_size(); + VideoSize get_size(bool performOp = true); /** * Gets the dimensions (height and width) of the Video - * + * @param performOp Specify if operations should be performed first. Default + * is true. * @return The height and width of the Video as an OpenCV Size object */ - cv::Size get_frame_size(); + cv::Size get_frame_size(bool performOp = true); /** * Gets number of frames in the video - * + * @param performOp Specify if operations should be performed first. Default + * is true. * @return Number of frames in the video */ - long get_frame_count(); + long get_frame_count(bool performOp = true); /** * Gets frames per second. @@ -169,10 +181,11 @@ class Video { * If key frame information is stored for this video, both this * function and key_frames() performs partial decoding on the video * to exploit key frame information. - * + * @param performOp Specify if operations should be performed first. Default + * is true. * @return cv::Mat with the specified frame */ - cv::Mat get_frame(unsigned frame_num); + cv::Mat get_frame(unsigned frame_num, bool performOp = true); /** * Gets mutiple frames from the video @@ -186,9 +199,13 @@ class Video { * Before calling this method, the store method must be called, * as OpenCV only offers interfaces from encoding/decoding * from/to files. - * + * @param container Video container format type, eg. mp4, in which the + * video should be encoded in + * @param vcl_codec The VCL codec, eg H264, in which the video is to be + * encoded in */ - std::vector get_encoded(); + std::vector get_encoded(std::string container, + VCL::Video::Codec vcl_codec); /** * Invokes key-frame generation on the video, if the video is encoded @@ -200,6 +217,33 @@ class Video { */ const KeyFrameList &get_key_frame_list(); + /** + * Gets the Codec as a fourcc array. + * @param _codec The VCL codec that is to be converted to fourcc + */ + int get_video_fourcc(VCL::Video::Codec _codec); + + /** + * @return The error message if the query fails. Null if query is a success. + */ + std::string get_query_error_response(); + + /** + * @return The number of enqueued operations not executed yet + */ + int get_enqueued_operation_count(); + + /** + * @return The parameters sent by client for the remote operation + */ + Json::Value get_remoteOp_params(); + + /** + * @return The location of the temporary video file on which operations have + * been perfromed + */ + std::string get_operated_video_id(); + /* *********************** */ /* SET FUNCTIONS */ /* *********************** */ @@ -242,6 +286,29 @@ class Video { */ void set_connection(RemoteConnection *remote); + /** + * Sets the _query_error_response message when an exception occurs + * + * @param error_msg Error message to be sent to the client. + */ + void set_query_error_response(std::string error_msg); + + /** + * Sets the remote parameters that a remote operation will require + * + * @param options encapsulated parameters for a specific remote operation. + * @param url remote API url + */ + void set_remoteOp_params(Json::Value options, std::string url); + + /** + * Sets the location of the temporary video file on which operations have + * been perfromed + * + * @param filename location of the temporary video file + */ + void set_operated_video_id(std::string filename); + /* *********************** */ /* Video INTERACTIONS */ /* *********************** */ @@ -292,6 +359,29 @@ class Video { */ void interval(Unit u, int start, int stop, int step = 1); + /** + * Performs a synchronous remote operation on the video. + * + * @param url Remote url + * @param options operation options + */ + void syncremoteOperation(std::string url, Json::Value options); + + /** + * Performs a asynchronous remote operation on the video. + * + * @param url Remote url + * @param options operation options + */ + void remoteOperation(std::string url, Json::Value options); + + /** + * Performs a user defined operation on the video. + * + * @param options operation options + */ + void userOperation(Json::Value options); + /** * Writes the Video to the system at the given location and in * the given format @@ -315,18 +405,14 @@ class Video { void delete_video(); /** - * Read a frame from the video file. - * To improve the performance, if we read multiple frames, we should - * read from the smallest index to the largest index. - * - * @param index The index of the frame within the video. - * @return The pointer to the frame if it succeeds and NULL if it fails + * Initiates execution of the enqueued operation. Called by the VideoLoop. + * @param isRemote If the operation to be executed is a remote operation. + * Default is false. */ - VCL::Image *read_frame(int index); + int execute_operations(bool isRemote = false); private: class Operation; - class Read; // Forward declaration of VideoTest class, that is used for the unit // test to accesss private methods of this class @@ -336,9 +422,13 @@ class Video { // It is called _video_id to keep it consistent with VCL::Image std::string _video_id; - bool _flag_stored; // Flag to avoid unnecessary read/write + // Full path to the temporary video file on which operations are performed. + std::string _operated_video_id; - std::shared_ptr _video_read; + // Query Error response + std::string _query_error_response = ""; + + bool _flag_stored; // Flag to avoid unnecessary read/write VideoSize _size; @@ -358,6 +448,9 @@ class Video { Storage _storage = Storage::LOCAL; + // Remote operation parameters sent by the client + Json::Value remoteOp_params; + /* *********************** */ /* OPERATION */ /* *********************** */ @@ -372,129 +465,30 @@ class Video { * () operator */ class Operation { - protected: - // Pointer to the video object to be handled - Video *_video; public: - Operation(Video *video) : _video(video) {} - /** * Implemented by the specific operation, performs what * the operation is supposed to do - * This function should be executed for every frame * - * @param index The index of frame to be processed - * @return PASS the frame should be passed to the next operation object - * CONTINUE Abort the current frame operation - * BREAK Abort the whole video operation + * @param video A pointer to the current Video object + * @param frame The frame on which the operation will be performed + * @param args Any additional parameters required by the operation */ - virtual OperationResult operator()(int index) = 0; + virtual void operator()(Video *video, cv::Mat &frame, + std::string args = "") = 0; virtual OperationType get_type() = 0; /** - * This function is called after the video operation, to tell the - * Operation object to release the resources and update video metadata. - * - */ - virtual void finalize() {} - }; - - /* *********************** */ - /* READ OPERATION */ - /* *********************** */ - - /** - * Extends Operation, reads Video from the file system - */ - class Read : public Operation, public std::enable_shared_from_this { - - // The currently opened video file - cv::VideoCapture _inputVideo; - // The cached frames - std::vector _frames; - // The range of cached frames - int _frame_index_starting, _frame_index_ending; - // The path of the currently opened video file - std::string _video_id; - - Video::Codec read_codec(char *fourcc); - - // Open the video file and initialize VideoCapture handler - void open(); - - // Reopen the VideoCapture handler, this happens if - // * the video file changes - // * we want to read the frames all over again - void reopen(); - - public: - /** - * Reads an Video from the file system (based on specified path) - * - */ - Read(Video *video) - : Operation(video), _frame_index_starting(0), _frame_index_ending(0), - _video_id(video->_video_id){ - - }; - - OperationResult operator()(int index); - - void finalize(); - - OperationType get_type() { return READ; }; - - // Reset or close the VideoCapture handler - void reset(); - - /** - * Read a frame from the video file. - * To improve the performance, if we read multiple frames, we should - * read from the smallest index to the largest index. - * - * @param index The index of the frame within the video. - * @return The pointer to the frame if it succeeds and NULL if it fails - */ - VCL::Image *read_frame(int index); - - ~Read(); - }; - - /* *********************** */ - /* WRITE OPERATION */ - /* *********************** */ - /** - * Extends Operation, writes to the file system in the specified - * format - */ - class Write : public Operation { - private: - cv::VideoWriter _outputVideo; - std::string _outname; - Video::Codec _codec; - int _frame_count; - int _last_write; - - int get_fourcc(); - - public: - Write(Video *video, std::string outname, Video::Codec codec) - : Operation(video), _outname(outname), _codec(codec), _frame_count(0), - _last_write(-1){}; - - /** - * Writes an Video to the file system. + * Implemented by the Resize and Crop operations. + * Used to set the size of the video writer object. * */ - OperationResult operator()(int index); - - OperationType get_type() { return WRITE; }; - - void finalize(); - - ~Write(); + virtual cv::Size get_video_size() { + cv::Size size; + return size; + }; }; /* *********************** */ @@ -514,17 +508,20 @@ class Video { * * @param size Struct that contains w and h */ - Resize(Video *video, const cv::Size &size) - : Operation(video), _size(size){}; + Resize(const cv::Size &size) : _size(size){}; /** * Resizes an Video to the given dimensions * - * @param video A pointer to the current Video object + * @param video A pointer to the current Video object + * @param frame The frame on which the operation will be performed + * @param args Any additional parameters required by the operation */ - OperationResult operator()(int index); + void operator()(Video *video, cv::Mat &frame, std::string args = NULL); OperationType get_type() { return RESIZE; }; + + cv::Size get_video_size() { return _size; } }; /* *********************** */ @@ -537,9 +534,6 @@ class Video { int _stop; int _step; Video::Unit _u; - bool _fps_updated; - - void update_fps(); public: /** @@ -550,17 +544,17 @@ class Video { * @param stop Last frame * @param step Number of frames to be skipped in between. */ - Interval(Video *video, Video::Unit u, const int start, const int stop, - int step) - : Operation(video), _u(u), _start(start), _stop(stop), _step(step), - _fps_updated(false){}; + Interval(Video::Unit u, const int start, const int stop, int step) + : _u(u), _start(start), _stop(stop), _step(step){}; /** * Resizes an Video to the given dimensions * - * @param video A pointer to the current Video object + * @param video A pointer to the current Video object + * @param frame The frame on which the operation will be performed + * @param args Any additional parameters required by the operation */ - OperationResult operator()(int index); + void operator()(Video *video, cv::Mat &frame, std::string args = NULL); OperationType get_type() { return INTERVAL; }; }; @@ -584,16 +578,20 @@ class Video { * @param rect Contains dimensions and coordinates of * desired area */ - Crop(Video *video, const Rectangle &rect) : Operation(video), _rect(rect){}; + Crop(const Rectangle &rect) : _rect(rect){}; /** * Crops the Video to the given area * - * @param video A pointer to the current Video object + * @param video A pointer to the current Video object + * @param frame The frame on which the operation will be performed + * @param args Any additional parameters required by the operation */ - OperationResult operator()(int index); + void operator()(Video *video, cv::Mat &frame, std::string args = NULL); OperationType get_type() { return CROP; }; + + cv::Size get_video_size() { return _rect.size(); } }; /* *********************** */ @@ -615,19 +613,116 @@ class Video { * * @param value Minimum value pixels should be */ - Threshold(Video *video, const int value) - : Operation(video), _threshold(value){}; + Threshold(const int value) : _threshold(value){}; /** * Performs the thresholding operation * - * @param img A pointer to the current Video object + * @param video A pointer to the current Video object + * @param frame The frame on which the operation will be performed + * @param args Any additional parameters required by the operation */ - OperationResult operator()(int index); + void operator()(Video *video, cv::Mat &frame, std::string args = NULL); OperationType get_type() { return THRESHOLD; }; }; + /* *********************** */ + /* SYNCREMOTE OPERATION */ + /* *********************** */ + /** Extends Operation, performs a synchronous remote operation + */ + class SyncRemoteOperation : public Operation { + private: + std::string _url; + Json::Value _options; + + public: + /** + * + * Constructor, sets the remote url and client options + * + * @param url remote server url + * @param options client parameters for the operation + */ + SyncRemoteOperation(std::string url, Json::Value options) + : _url(url), _options(options){}; + + /** + * Performs the remote operation + * + * @param video A pointer to the current Video object + * @param frame The frame on which the operation will be performed + * @param args Any additional parameters required by the operation + */ + void operator()(Video *video, cv::Mat &frame, std::string args = NULL); + + OperationType get_type() { return SYNCREMOTEOPERATION; }; + }; + + /* *********************** */ + /* REMOTE OPERATION */ + /* *********************** */ + /** Extends Operation, performs an asynchronous remote operation + */ + class RemoteOperation : public Operation { + private: + std::string _url; + Json::Value _options; + + public: + /** + * + * Constructor, sets the remote url and client options + * + * @param url remote server url + * @param options client parameters for the operation + */ + RemoteOperation(std::string url, Json::Value options) + : _url(url), _options(options){}; + + /** + * Performs the remote operation + * + * @param video A pointer to the current Video object + * @param frame The frame on which the operation will be performed + * @param args Any additional parameters required by the operation + */ + void operator()(Video *video, cv::Mat &frame, std::string args = NULL); + + OperationType get_type() { return REMOTEOPERATION; }; + }; + + /* *********************** */ + /* USER DEFINED OPERATION */ + /* *********************** */ + /** Extends Operation, performs a udf + */ + class UserOperation : public Operation { + private: + Json::Value _options; + + public: + /** + * + * Constructor, sets the client options + * + * @param options client parameters for the operation + */ + UserOperation(Json::Value options) : _options(options){}; + + /** + * Performs the remote operation + * + * @param video A pointer to the current Video object + * @param frame The frame on which the operation will be performed + * @param args Any additional parameters required by the operation + */ + void operator()(Video *video, cv::Mat &frame, std::string args = NULL); + + OperationType get_type() { return USEROPERATION; }; + }; + protected: /* *********************** */ /* UTILITIES */ @@ -638,18 +733,68 @@ class Video { * * @return true if video was read, false otherwise */ - // bool is_read(void); + bool is_read(void); + + /** + * Sets video attributes such as frame count, height, width + * @param vname path to the video file + */ + void initialize_video_attributes(std::string vname); /** * Performs the set of operations that have been requested * on the Video + * @param is_store Is the function called to perform a write to the data + * store + * @param store_id File name to be used for the video stored in the data + * store */ - void perform_operations(); + void perform_operations(bool is_store = false, std::string store_id = ""); + + /** + * Checks if sufficient memory is available to perform the + * Video operation + * @param VideoSize struct containing the width, height, and frame + * count of the video + */ + bool check_sufficient_memory(const struct VideoSize &size); /** * Swaps members of two Video objects, to be used by assignment * operator. + * @param rhs The video from which the attributes are to be swapped with. */ void swap(Video &rhs) noexcept; + + /** + * Get the format of the video file. + * @param video_id Path of the video file + */ + std::string get_video_format(char *video_id); + + /** + * Set size of the video writer object + * @param op_count Current operation number + */ + void set_video_writer_size(int op_count); + + /** + * Store a video to the data store + * @param id Input video path + * @param store_id path to the file location where the video should be stored + * @param fname path to the temporary file location + */ + void store_video_no_operation(std::string id, std::string store_id, + std::string fname); + + /** + * Perform operations in a frame-by-frame manner on the video. + * @param id source video file path + * @param op_count index of the current operation being executed + * @param fname path to the temporary file location + */ + int perform_single_frame_operations(std::string id, int op_count, + std::string fname); }; -} // namespace VCL + +} // namespace VCL \ No newline at end of file diff --git a/remote_function/README.md b/remote_function/README.md index 9a4b7273..dbee2719 100644 --- a/remote_function/README.md +++ b/remote_function/README.md @@ -1,5 +1,5 @@ # Remote Operations in VDMS -This submodule is required to execute VDMS operation on a remote server using Flask APIs (Support only available for images). Although shipped with VDMS, this submodule can be run independently and interacts with VDMS using http APIs. +This submodule is required to execute VDMS operation on a remote server using Flask APIs. Although shipped with VDMS, this submodule can be run independently and interacts with VDMS using http APIs. ## Requirements - Python 3 or higher diff --git a/remote_function/functions/caption.py b/remote_function/functions/caption.py new file mode 100644 index 00000000..d086b1e1 --- /dev/null +++ b/remote_function/functions/caption.py @@ -0,0 +1,33 @@ +import cv2 +import numpy as np +from datetime import datetime +from collections import deque +import skvideo.io +import imutils +import uuid + + +def run(ipfilename, format, options): + opfilename = "tmpfile" + uuid.uuid1().hex + "." + str(format) + print(opfilename) + vs = cv2.VideoCapture(ipfilename) + + video = skvideo.io.FFmpegWriter(opfilename, {"-pix_fmt": "bgr24"}) + print(options) + i = 0 + while True: + (grabbed, frame) = vs.read() + if not grabbed: + print("[INFO] no frame read from stream - exiting") + video.close() + # sys.exit(0) + break + + label = options["text"] + cv2.putText( + frame, label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2 + ) + + video.writeFrame(frame) + + return opfilename diff --git a/remote_function/requirements.txt b/remote_function/requirements.txt index 89b80f95..03c7d0e0 100644 --- a/remote_function/requirements.txt +++ b/remote_function/requirements.txt @@ -1,5 +1,5 @@ opencv-python==4.5.5.64 -flask -numpy -sk-video -imutils \ No newline at end of file +flask==2.3.3 +numpy==1.26.0 +sk-video==1.1.10 +imutils==0.5.4 \ No newline at end of file diff --git a/remote_function/udf_server.py b/remote_function/udf_server.py index 922d4e32..a476557f 100644 --- a/remote_function/udf_server.py +++ b/remote_function/udf_server.py @@ -8,6 +8,7 @@ from collections import defaultdict, deque import skvideo.io import imutils +import uuid for entry in os.scandir("functions"): if entry.is_file(): @@ -40,7 +41,7 @@ def image_api(): format = json_data["format"] if "format" in json_data else "jpg" - tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + tmpfile = "tmpfile" + uuid.uuid1().hex + "." + str(format) image_data.save(tmpfile) @@ -52,6 +53,35 @@ def image_api(): return return_string +@app.route("/video", methods=["POST"]) +def video_api(): + json_data = json.loads(request.form["jsonData"]) + video_data = request.files["videoData"] + format = json_data["format"] + + tmpfile = "tmpfile" + uuid.uuid1().hex + "." + str(format) + video_data.save(tmpfile) + + udf = globals()[json_data["id"]] + response_file = udf.run(tmpfile, format, json_data) + + os.remove(tmpfile) + + @after_this_request + def remove_tempfile(response): + try: + os.remove(response_file) + except Exception as e: + print("File cannot be deleted or not present") + return response + + try: + return send_file(response_file, as_attachment=True, download_name=response_file) + except Exception as e: + print(str(e)) + return "Error in file read" + + @app.errorhandler(400) def handle_bad_request(e): response = e.get_response() diff --git a/src/BlobCommand.cc b/src/BlobCommand.cc index b810444a..eae93672 100644 --- a/src/BlobCommand.cc +++ b/src/BlobCommand.cc @@ -204,4 +204,4 @@ Json::Value FindBlob::construct_responses(Json::Value &responses, ret[_cmd_name].swap(findBlob); return ret; -} \ No newline at end of file +} diff --git a/src/CommunicationManager.cc b/src/CommunicationManager.cc index 96adba84..865b775d 100644 --- a/src/CommunicationManager.cc +++ b/src/CommunicationManager.cc @@ -30,7 +30,8 @@ */ #include "CommunicationManager.h" -#include "QueryHandler.h" +#include "QueryHandlerExample.h" +#include "QueryHandlerPMGD.h" #include "VDMSConfig.h" @@ -41,6 +42,9 @@ CommunicationManager::CommunicationManager() { _num_threads = VDMSConfig::instance()->get_int_value( "max_simultaneous_clients", MAX_CONNECTED_CLIENTS); + _q_handler = VDMSConfig::instance()->get_string_value("query_handler", + DEFAULT_QUERY_HANDLER); + if (_num_threads > MAX_CONNECTED_CLIENTS) _num_threads = MAX_CONNECTED_CLIENTS; @@ -65,9 +69,15 @@ void CommunicationManager::process_queue() { auto c_it = _conn_list.insert(_conn_list.begin(), c); _conn_list_lock.unlock(); - QueryHandler qh; + if (_q_handler == "pmgd") { + QueryHandlerPMGD qh; + qh.process_connection(c); + } else if (_q_handler == "example") { + QueryHandlerExample qh; + qh.process_connection(c); + } + printf("Connection received...\n"); - qh.process_connection(c); std::unique_lock conn_list_lock(_conn_list_lock); _conn_list.erase(c_it); diff --git a/src/CommunicationManager.h b/src/CommunicationManager.h index 32300b17..fd9668e6 100644 --- a/src/CommunicationManager.h +++ b/src/CommunicationManager.h @@ -44,6 +44,10 @@ namespace VDMS { class CommunicationManager { static const int MAX_CONNECTED_CLIENTS = 500; + std::string DEFAULT_QUERY_HANDLER = + "pmgd"; // TODO need to move this someplace central between server and + // comm manager + std::string _q_handler; // For the thread pool std::mutex _mlock; diff --git a/src/DescriptorsCommand.cc b/src/DescriptorsCommand.cc index 95b367df..9f0aa755 100644 --- a/src/DescriptorsCommand.cc +++ b/src/DescriptorsCommand.cc @@ -110,9 +110,9 @@ bool DescriptorsCommand::check_blob_size(const std::string &blob, AddDescriptorSet::AddDescriptorSet() : DescriptorsCommand("AddDescriptorSet") { _storage_sets = VDMSConfig::instance()->get_path_descriptors(); _flinng_num_rows = 3; // set based on the default values of Flinng - _flinng_cells_per_row = 4096; - _flinng_num_hash_tables = 512; - _flinng_hashes_per_table = 14; + _flinng_cells_per_row = 1000; + _flinng_num_hash_tables = 10; + _flinng_hashes_per_table = 12; _flinng_sub_hash_bits = 2; _flinng_cut_off = 6; @@ -135,18 +135,21 @@ int AddDescriptorSet::construct_protobuf(PMGDQuery &query, props[VDMS_DESC_SET_NAME_PROP] = cmd["name"].asString(); props[VDMS_DESC_SET_DIM_PROP] = cmd["dimensions"].asInt(); props[VDMS_DESC_SET_PATH_PROP] = desc_set_path; - if (cmd.isMember("flinng_num_rows")) - _flinng_num_rows = cmd["flinng_num_rows"].asInt(); - if (cmd.isMember("flinng_cells_per_row")) - _flinng_cells_per_row = cmd["flinng_cells_per_row"].asInt(); - if (cmd.isMember("flinng_num_hash_tables")) - _flinng_num_hash_tables = cmd["flinng_num_hash_tables"].asInt(); - if (cmd.isMember("flinng_hashes_per_table")) - _flinng_hashes_per_table = cmd["flinng_hashes_per_table"].asInt(); - if (cmd.isMember("flinng_sub_hash_bits")) - _flinng_sub_hash_bits = cmd["flinng_sub_hash_bits"].asInt(); - if (cmd.isMember("flinng_cut_off")) - _flinng_cut_off = cmd["flinng_cut_off"].asInt(); + props[VDMS_DESC_SET_ENGIN_PROP] = cmd["engine"].asString(); + if (props[VDMS_DESC_SET_ENGIN_PROP] == "Flinng") { + if (cmd.isMember("flinng_num_rows")) + _flinng_num_rows = cmd["flinng_num_rows"].asInt(); + if (cmd.isMember("flinng_cells_per_row")) + _flinng_cells_per_row = cmd["flinng_cells_per_row"].asInt(); + if (cmd.isMember("flinng_num_hash_tables")) + _flinng_num_hash_tables = cmd["flinng_num_hash_tables"].asInt(); + if (cmd.isMember("flinng_hashes_per_table")) + _flinng_hashes_per_table = cmd["flinng_hashes_per_table"].asInt(); + if (cmd.isMember("flinng_sub_hash_bits")) + _flinng_sub_hash_bits = cmd["flinng_sub_hash_bits"].asInt(); + if (cmd.isMember("flinng_cut_off")) + _flinng_cut_off = cmd["flinng_cut_off"].asInt(); + } Json::Value constraints; constraints[VDMS_DESC_SET_NAME_PROP].append("=="); diff --git a/src/ImageCommand.cc b/src/ImageCommand.cc index 757b3841..cfbdb8b5 100644 --- a/src/ImageCommand.cc +++ b/src/ImageCommand.cc @@ -36,7 +36,6 @@ #include "defines.h" #include "ImageLoop.h" -#include "stats/SystemStats.h" using namespace VDMS; @@ -62,45 +61,18 @@ int ImageCommand::enqueue_operations(VCL::Image &img, const Json::Value &ops, } else if (type == "rotate") { img.rotate(get_value(op, "angle"), get_value(op, "resize")); } else if (type == "syncremoteOp") { - VCL::Image *tmp_image = new VCL::Image(img, true); - - try { + img.syncremoteOperation(get_value(op, "url"), + get_value(op, "options")); + } else if (type == "remoteOp") { + if (is_addition) { img.syncremoteOperation(get_value(op, "url"), get_value(op, "options")); - } catch (const std::exception &e) { - img.deep_copy_cv(tmp_image->get_cvmat(true)); - std::cerr << e.what() << '\n'; - return -1; - } - delete tmp_image; - } else if (type == "remoteOp") { - VCL::Image *tmp_image = new VCL::Image(img, true); - - try { - if (is_addition) { - img.syncremoteOperation(get_value(op, "url"), - get_value(op, "options")); - } else { - img.remoteOperation(get_value(op, "url"), - get_value(op, "options")); - } - } catch (const std::exception &e) { - img.deep_copy_cv(tmp_image->get_cvmat(true)); - std::cerr << e.what() << '\n'; - return -1; + } else { + img.remoteOperation(get_value(op, "url"), + get_value(op, "options")); } - delete tmp_image; } else if (type == "userOp") { - VCL::Image *tmp_image = new VCL::Image(img, true); - - try { - img.userOperation(get_value(op, "options")); - } catch (const std::exception &e) { - img.deep_copy_cv(tmp_image->get_cvmat(true)); - std::cerr << e.what() << '\n'; - return -1; - } - delete tmp_image; + img.userOperation(get_value(op, "options")); } else if (type == "custom") { VCL::Image *tmp_image = new VCL::Image(img, true); try { @@ -285,7 +257,6 @@ Json::Value FindImage::construct_responses(Json::Value &responses, int operation_flags = 0; bool has_operations = false; std::string no_op_def_image; - SystemStats systemStats; Json::Value ret; @@ -421,6 +392,13 @@ Json::Value FindImage::construct_responses(Json::Value &responses, std::map imageMap = eventloop.get_image_map(); std::map::iterator iter = imageMap.begin(); + if (iter->second->get_query_error_response() != "") { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = iter->second->get_query_error_response(); + return error(return_error); + } + while (iter != imageMap.end()) { std::vector img_enc = iter->second->get_encoded_image_async(formats[iter->first]); diff --git a/src/ImageLoop.cc b/src/ImageLoop.cc index 8e8a9a47..04472d4b 100644 --- a/src/ImageLoop.cc +++ b/src/ImageLoop.cc @@ -101,10 +101,26 @@ void ImageLoop::operationThread() noexcept { for (int i = img->get_op_completed(); i < enqueued_operations; i++) { int response = img->execute_operation(); - if (response != 0) { + if (response == -1) { + // Remote operation encountered. Enqueue to remote thread r_enqueue(img); flag = 1; break; + } else if (response == -2) { + // Exception thrown. Terminate eventloop. + auto const result = imageMap.insert( + std::pair(img->get_image_id(), img)); + if (not result.second) { + result.first->second = img; + } + _remote_running = false; + flag = 0; + m_writeBuffer.clear(); + r_writeBuffer.clear(); + m_running = false; + r_running = false; + break; + } else { auto const result = imageMap.insert( std::pair(img->get_image_id(), img)); @@ -214,107 +230,140 @@ void ImageLoop::execute_remote_operations( int rindex = 0; std::vector redoBuffer; std::vector pendingImages; - while (start_index != readBuffer.size()) { - CURLM *multi_handle; - CURLMsg *msg = NULL; - CURL *eh = NULL; - CURLcode return_code; - int still_running = 0, i = 0, msgs_left = 0; - int http_status_code; - char *szUrl; + try { + while (start_index != readBuffer.size()) { + CURLM *multi_handle; + CURLMsg *msg = NULL; + CURL *eh = NULL; + CURLcode return_code; + int still_running = 0, i = 0, msgs_left = 0; + int http_status_code; + char *szUrl; + + multi_handle = curl_multi_init(); + + auto start = readBuffer.begin() + start_index; + auto end = readBuffer.begin() + end_index; + + std::vector tempBuffer(start, end); + + for (VCL::Image *img : tempBuffer) { + CURL *curl = get_easy_handle(img, responseBuffer[rindex]); + rindex++; + curl_multi_add_handle(multi_handle, curl); + } - multi_handle = curl_multi_init(); + do { + CURLMcode mc = curl_multi_perform(multi_handle, &still_running); + if (still_running) + mc = curl_multi_wait(multi_handle, NULL, 0, 1000, NULL); - auto start = readBuffer.begin() + start_index; - auto end = readBuffer.begin() + end_index; + if (mc) { + break; + } + } while (still_running); + + while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) { + if (msg->msg == CURLMSG_DONE) { + eh = msg->easy_handle; + + return_code = msg->data.result; + + szUrl = NULL; + long rsize = 0; + + curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &http_status_code); + curl_easy_getinfo(eh, CURLINFO_EFFECTIVE_URL, &szUrl); + curl_easy_getinfo(eh, CURLINFO_REQUEST_SIZE, &rsize); + + if (http_status_code != 200) { + // Throw specific exceptions if error codes received as response. + if (http_status_code == 0) { + throw VCLException(ObjectEmpty, "Remote server is not running."); + } + if (http_status_code == 400) { + throw VCLException(ObjectEmpty, + "Invalid Request to the Remote Server."); + } else if (http_status_code == 404) { + throw VCLException(ObjectEmpty, + "Invalid URL Request. Please check the URL."); + } else if (http_status_code == 500) { + throw VCLException(ObjectEmpty, + "Exception occurred at the remote server. " + "Please check your query."); + } else if (http_status_code == 503) { + throw VCLException(ObjectEmpty, "Unable to reach remote server"); + } else { + throw VCLException(ObjectEmpty, "Remote Server error."); + } + } - std::vector tempBuffer(start, end); + curl_multi_remove_handle(multi_handle, eh); + curl_easy_cleanup(eh); + } else { + fprintf(stderr, "error: after curl_multi_info_read(), CURLMsg=%d\n", + msg->msg); + } + } - for (VCL::Image *img : tempBuffer) { - CURL *curl = get_easy_handle(img, responseBuffer[rindex]); - rindex++; - curl_multi_add_handle(multi_handle, curl); + tempBuffer.clear(); + start_index = end_index; + end_index = readBuffer.size() > (end_index + step) ? (end_index + step) + : readBuffer.size(); } - - do { - CURLMcode mc = curl_multi_perform(multi_handle, &still_running); - if (still_running) - mc = curl_multi_wait(multi_handle, NULL, 0, 1000, NULL); - - if (mc) { - break; + rindex = -1; + for (VCL::Image *img : readBuffer) { + rindex++; + if (std::find(redoBuffer.begin(), redoBuffer.end(), + img->get_image_id().data()) != redoBuffer.end()) { + pendingImages.push_back(img); + continue; } - } while (still_running); - while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) { - if (msg->msg == CURLMSG_DONE) { - eh = msg->easy_handle; - - return_code = msg->data.result; - - // Get HTTP status code - szUrl = NULL; - long rsize = 0; - - curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &http_status_code); - curl_easy_getinfo(eh, CURLINFO_EFFECTIVE_URL, &szUrl); - curl_easy_getinfo(eh, CURLINFO_REQUEST_SIZE, &rsize); - - if (http_status_code != 200) { - std::string delimiter = "="; - - char *p = std::strtok(szUrl, delimiter.data()); - p = std::strtok(NULL, delimiter.data()); + int rthresh = 0; + auto t_start = std::chrono::high_resolution_clock::now(); + bool rflag = false; + while (responseBuffer[rindex].size() == 0) { + continue; + } + cv::Mat dmat = write_image(responseBuffer[rindex]); + if (dmat.empty()) { + pendingImages.push_back(img); + } - std::string id(p); - redoBuffer.push_back(id); - } + img->shallow_copy_cv(dmat); + img->update_op_completed(); - curl_multi_remove_handle(multi_handle, eh); - curl_easy_cleanup(eh); - } else { - fprintf(stderr, "error: after curl_multi_info_read(), CURLMsg=%d\n", - msg->msg); + auto const result = imageMap.insert( + std::pair(img->get_image_id(), img)); + if (not result.second) { + result.first->second = img; + } + if (rindex == readBuffer.size() - 1 && pendingImages.size() == 0) { + _remote_running = false; } - } - tempBuffer.clear(); - start_index = end_index; - end_index = readBuffer.size() > (end_index + step) ? (end_index + step) - : readBuffer.size(); - } - rindex = -1; - for (VCL::Image *img : readBuffer) { - rindex++; - if (std::find(redoBuffer.begin(), redoBuffer.end(), - img->get_image_id().data()) != redoBuffer.end()) { - pendingImages.push_back(img); - continue; - } - int rthresh = 0; - auto t_start = std::chrono::high_resolution_clock::now(); - bool rflag = false; - while (responseBuffer[rindex].size() == 0) { - continue; - } - cv::Mat dmat = write_image(responseBuffer[rindex]); - if (dmat.empty()) { - pendingImages.push_back(img); + enqueue(img); } - img->shallow_copy_cv(dmat); - img->update_op_completed(); + readBuffer.clear(); + std::swap(readBuffer, pendingImages); + } catch (VCL::Exception e) { + VCL::Image *img = readBuffer[0]; + img->set_query_error_response(e.msg); auto const result = imageMap.insert( std::pair(img->get_image_id(), img)); if (not result.second) { result.first->second = img; } - if (rindex == readBuffer.size() - 1 && pendingImages.size() == 0) { - _remote_running = false; - } - enqueue(img); + readBuffer.clear(); + print_exception(e); + _remote_running = false; + m_writeBuffer.clear(); + r_writeBuffer.clear(); + m_running = false; + r_running = false; + return; } - readBuffer.clear(); - std::swap(readBuffer, pendingImages); } void ImageLoop::remoteOperationThread() noexcept { diff --git a/src/QueryHandler.cc b/src/QueryHandler.cc index 8a05bf91..34d15073 100644 --- a/src/QueryHandler.cc +++ b/src/QueryHandler.cc @@ -430,12 +430,12 @@ void QueryHandler::process_query(protobufs::queryMessage &proto_query, error_msg << "Internal Server Error: Json Exception: " << e.what() << std::endl; exception_handler(); - } catch (google::protobuf::FatalException &e) { - // Need to be carefull with this, may lead to memory leak. - // Protoubuf is not exception safe. - error_msg << "Internal Server Error: Protobuf Exception: " << e.what() - << std::endl; - exception_handler(); + // } catch (google::protobuf::FatalException &e) { + // // Need to be carefull with this, may lead to memory leak. + // // Protoubuf is not exception safe. + // error_msg << "Internal Server Error: Protobuf Exception: " << e.what() + // << std::endl; + // exception_handler(); } catch (const std::invalid_argument &e) { error_msg << "FATAL: Invalid argument: " << e.what() << std::endl; exception_handler(); @@ -448,7 +448,7 @@ void QueryHandler::process_query(protobufs::queryMessage &proto_query, } } -void QueryHandler::regualar_run_autoreplicate( +void QueryHandler::regular_run_autoreplicate( ReplicationConfig &replicate_settings) { std::string command = "bsdtar cvfz "; std::string name; @@ -547,7 +547,7 @@ void QueryHandler::reset_autodelete_init_flag() { _autodelete_init = false; } void QueryHandler::set_autodelete_init_flag() { _autodelete_init = true; } -void QueryHandler::regualar_run_autodelete() { +void QueryHandler::regular_run_autodelete() { std::string *json_string = new std::string( "[{\"DeleteExpired\": {\"results\": {\"list\": [\"_expiration\"]}}}]"); protobufs::queryMessage response; diff --git a/src/QueryHandler.h b/src/QueryHandler.h index c4ac440b..61ada1fd 100644 --- a/src/QueryHandler.h +++ b/src/QueryHandler.h @@ -86,10 +86,10 @@ class QueryHandler { void process_connection(comm::Connection *c); void reset_autodelete_init_flag(); void set_autodelete_init_flag(); - void regualar_run_autodelete(); + void regular_run_autodelete(); void build_autodelete_queue(); void set_autoreplicate_init_flag(); void reset_autoreplicate_init_flag(); - void regualar_run_autoreplicate(ReplicationConfig &); + void regular_run_autoreplicate(ReplicationConfig &); }; } // namespace VDMS diff --git a/src/QueryHandlerBase.cc b/src/QueryHandlerBase.cc new file mode 100644 index 00000000..3e9b31e5 --- /dev/null +++ b/src/QueryHandlerBase.cc @@ -0,0 +1,64 @@ +// +// Created by ifadams on 7/19/2023. +// + +#include "QueryHandlerBase.h" +#include "ImageCommand.h" +#include "VideoCommand.h" + +using namespace VDMS; + +valijson::Schema *QueryHandlerBase::_schema = new valijson::Schema; + +QueryHandlerBase::QueryHandlerBase() + : _validator(valijson::Validator::kWeakTypes) +#ifdef CHRONO_TIMING + , + ch_tx_total("ch_tx_total"), ch_tx_query("ch_tx_query"), + ch_tx_send("ch_tx_send") +#endif +{ +} + +// TODO create a better mechanism to cleanup queries that +// includes feature vectors and user-defined blobs +// For now, we do it for videos/images as a starting point. +void QueryHandlerBase::cleanup_query(const std::vector &images, + const std::vector &videos) { + for (auto &img_path : images) { + VCL::Image img(img_path); + img.delete_image(); + } + + for (auto &vid_path : videos) { + VCL::Video vid(vid_path); + vid.delete_video(); + } +} + +void QueryHandlerBase::process_connection(comm::Connection *c) { + QueryMessage msgs(c); + + try { + while (true) { + protobufs::queryMessage response; + protobufs::queryMessage query = msgs.get_query(); + CHRONO_TIC(ch_tx_total); + + CHRONO_TIC(ch_tx_query); + process_query(query, response); + CHRONO_TAC(ch_tx_query); + + CHRONO_TIC(ch_tx_send); + msgs.send_response(response); + CHRONO_TAC(ch_tx_send); + + CHRONO_TAC(ch_tx_total); + CHRONO_PRINT_LAST_MS(ch_tx_total); + CHRONO_PRINT_LAST_MS(ch_tx_query); + CHRONO_PRINT_LAST_MS(ch_tx_send); + } + } catch (comm::ExceptionComm e) { + print_exception(e); + } +} \ No newline at end of file diff --git a/src/QueryHandlerBase.h b/src/QueryHandlerBase.h new file mode 100644 index 00000000..fe144418 --- /dev/null +++ b/src/QueryHandlerBase.h @@ -0,0 +1,52 @@ +// +// Created by ifadams on 7/19/2023. +// + +#ifndef VDMS_QUERYHANDLERBASE_H +#define VDMS_QUERYHANDLERBASE_H + +#include "QueryMessage.h" // Protobuff implementation +#include +#include +//#include "Server.h" +#include "chrono/Chrono.h" + +// Json parsing files +#include +#include +#include + +namespace VDMS { + +class QueryHandlerBase { + +protected: + // valijson + valijson::Validator _validator; + static valijson::Schema *_schema; + +#ifdef CHRONO_TIMING + ChronoCpu ch_tx_total; + ChronoCpu ch_tx_query; + ChronoCpu ch_tx_send; +#endif + + void virtual cleanup_query(const std::vector &images, + const std::vector &videos); + + // process query is the core logic of any derived handler + // it takes in a protobuf serialized JSON that can be indexed/mapped + // into using CPP JSON (see query handler example) + // any json can be serialized and used as response that is handled + // by communication logic elsewhere. + void virtual process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response) = 0; + +public: + QueryHandlerBase(); + + void virtual process_connection(comm::Connection *c); +}; +} // namespace VDMS + +#endif // VDMS_QUERYHANDLERBASE_H diff --git a/src/QueryHandlerExample.cc b/src/QueryHandlerExample.cc new file mode 100644 index 00000000..637cdd9b --- /dev/null +++ b/src/QueryHandlerExample.cc @@ -0,0 +1,111 @@ +/** + * @file QueryHandler.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2023 Intel Corporation + * + * 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. + * + */ + +#include "QueryHandler.h" +#include +#include +#include + +#include "BlobCommand.h" +#include "BoundingBoxCommand.h" +#include "DescriptorsCommand.h" +#include "ImageCommand.h" +#include "VideoCommand.h" + +#include "ExceptionsCommand.h" + +#include "PMGDQuery.h" +#include "QueryMessage.h" +#include "pmgd.h" +#include "util.h" + +#include "APISchema.h" +#include +#include +#include +#include + +#include "QueryHandlerExample.h" + +using namespace VDMS; + +void QueryHandlerExample::init() { + // Load the string containing the schema (api_schema/APISchema.h) + Json::Reader reader; + Json::Value api_schema; + bool parseSuccess = reader.parse(schema_json.c_str(), api_schema); + if (!parseSuccess) { + std::cerr << "Failed to parse API reference schema." << std::endl; + std::cerr << "PANIC! Aborting." << std::endl; + exit(0); + } + + // Parse the json schema into an internal schema format + valijson::SchemaParser parser; + valijson::adapters::JsonCppAdapter schemaDocumentAdapter(api_schema); + try { + parser.populateSchema(schemaDocumentAdapter, *_schema); + } catch (std::exception &e) { + std::cerr << "Failed to load schema: " << e.what() << std::endl; + std::cerr << "PANIC! Aborting." << std::endl; + exit(0); + } +} + +QueryHandlerExample::QueryHandlerExample() {} + +void QueryHandlerExample::process_connection(comm::Connection *c) { + QueryMessage msgs(c); + + try { + while (true) { + protobufs::queryMessage response; + protobufs::queryMessage query = msgs.get_query(); + process_query(query, response); + msgs.send_response(response); + } + } catch (comm::ExceptionComm e) { + print_exception(e); + } +} + +void QueryHandlerExample::process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &proto_res) { + + Json::FastWriter fastWriter; + Json::Value hello_res; + Json::Value json_responses; + + hello_res["HiThere"] = "Hello, world!"; + json_responses.append(hello_res); + + proto_res.set_json(fastWriter.write(json_responses)); +} \ No newline at end of file diff --git a/src/QueryHandlerExample.h b/src/QueryHandlerExample.h new file mode 100644 index 00000000..d3cbc7db --- /dev/null +++ b/src/QueryHandlerExample.h @@ -0,0 +1,59 @@ +/** + * @file QueryHandler.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2023 Intel Corporation + * + * 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. + * + */ + +#pragma once +#include +#include +#include +#include + +#include "QueryHandlerBase.h" +#include "chrono/Chrono.h" +#include "comm/Connection.h" + +// Json parsing files +#include +#include +#include + +namespace VDMS { + +typedef ::google::protobuf::RepeatedPtrField BlobArray; + +class QueryHandlerExample : public QueryHandlerBase { +public: + static void init(); + QueryHandlerExample(); + void process_connection(comm::Connection *c); + void process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response); +}; +} // namespace VDMS diff --git a/src/QueryHandlerPMGD.cc b/src/QueryHandlerPMGD.cc new file mode 100644 index 00000000..1e35340f --- /dev/null +++ b/src/QueryHandlerPMGD.cc @@ -0,0 +1,536 @@ +/** + * @file QueryHandler.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2023 Intel Corporation + * + * 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. + * + */ + +#include "QueryHandlerPMGD.h" +#include +#include +#include + +#include "BlobCommand.h" +#include "BoundingBoxCommand.h" +#include "DescriptorsCommand.h" +#include "ImageCommand.h" +#include "VideoCommand.h" + +#include "ExceptionsCommand.h" + +#include "PMGDQuery.h" +#include "QueryMessage.h" +#include "pmgd.h" +#include "util.h" + +#include "APISchema.h" +#include +#include +#include +#include + +using namespace VDMS; + +std::unordered_map QueryHandlerPMGD::_rs_cmds; + +void QueryHandlerPMGD::init() { + DescriptorsManager::init(); + + _rs_cmds["AddEntity"] = new AddEntity(); + _rs_cmds["UpdateEntity"] = new UpdateEntity(); + _rs_cmds["FindEntity"] = new FindEntity(); + + _rs_cmds["AddConnection"] = new AddConnection(); + _rs_cmds["UpdateConnection"] = new UpdateConnection(); + _rs_cmds["FindConnection"] = new FindConnection(); + + _rs_cmds["AddImage"] = new AddImage(); + _rs_cmds["UpdateImage"] = new UpdateImage(); + _rs_cmds["FindImage"] = new FindImage(); + _rs_cmds["DeleteExpired"] = new DeleteExpired(); + + _rs_cmds["AddDescriptorSet"] = new AddDescriptorSet(); + _rs_cmds["AddDescriptor"] = new AddDescriptor(); + _rs_cmds["FindDescriptor"] = new FindDescriptor(); + _rs_cmds["ClassifyDescriptor"] = new ClassifyDescriptor(); + + _rs_cmds["AddBoundingBox"] = new AddBoundingBox(); + _rs_cmds["UpdateBoundingBox"] = new UpdateBoundingBox(); + _rs_cmds["FindBoundingBox"] = new FindBoundingBox(); + + _rs_cmds["AddVideo"] = new AddVideo(); + _rs_cmds["UpdateVideo"] = new UpdateVideo(); + _rs_cmds["FindVideo"] = new FindVideo(); + _rs_cmds["FindFrames"] = new FindFrames(); + + _rs_cmds["AddBlob"] = new AddBlob(); + _rs_cmds["UpdateBlob"] = new UpdateBlob(); + _rs_cmds["FindBlob"] = new FindBlob(); + + // Load the string containing the schema (api_schema/APISchema.h) + Json::Reader reader; + Json::Value api_schema; + bool parseSuccess = reader.parse(schema_json.c_str(), api_schema); + if (!parseSuccess) { + std::cerr << "Failed to parse API reference schema." << std::endl; + std::cerr << "PANIC! Aborting." << std::endl; + exit(0); + } + + // Parse the json schema into an internal schema format + valijson::SchemaParser parser; + valijson::adapters::JsonCppAdapter schemaDocumentAdapter(api_schema); + try { + parser.populateSchema(schemaDocumentAdapter, *_schema); + } catch (std::exception &e) { + std::cerr << "Failed to load schema: " << e.what() << std::endl; + std::cerr << "PANIC! Aborting." << std::endl; + exit(0); + } +} + +QueryHandlerPMGD::QueryHandlerPMGD() + : _pmgd_qh(), _autodelete_init(false), _autoreplicate_init(false) +#ifdef CHRONO_TIMING + , + ch_tx_total("ch_tx_total"), ch_tx_query("ch_tx_query"), + ch_tx_send("ch_tx_send") +#endif +{ +} + +bool QueryHandlerPMGD::syntax_checker(const Json::Value &root, + Json::Value &error) { + valijson::ValidationResults results; + valijson::adapters::JsonCppAdapter user_query(root); + if (!_validator.validate(*_schema, user_query, &results)) { + std::cerr << "API validation failed for:" << std::endl; + std::cerr << root.toStyledString() << std::endl; + + // Will attempt to find the simple error + // To avoid valijson dump + for (int j = 0; j < root.size(); j++) { + const Json::Value &query = root[j]; + if (query.getMemberNames().size() != 1) { + error["info"] = "Error: Only one command per element allowed"; + return false; + } + + const std::string cmd_str = query.getMemberNames()[0]; + auto it = _rs_cmds.find(cmd_str); + if (it == _rs_cmds.end()) { + error["info"] = cmd_str + ": Command not found!"; + return false; + } + } + + valijson::ValidationResults::Error va_error; + unsigned int errorNum = 1; + std::stringstream str_error; + while (results.popError(va_error)) { + std::string context; + std::vector::iterator itr = va_error.context.begin(); + for (; itr != va_error.context.end(); itr++) { + context += *itr; + } + + str_error << "Error #" << errorNum << std::endl + << " context: " << context << std::endl + << " desc: " << va_error.description << std::endl; + ++errorNum; + } + std::cerr << str_error.str(); + error["info"] = str_error.str(); + return false; + } + + for (auto &cmdTop : root) { + const std::string cmd_str = cmdTop.getMemberNames()[0]; + auto &cmd = cmdTop[cmd_str]; + if (cmd.isMember("constraints")) { + for (auto &member : cmd["constraints"].getMemberNames()) { + if (!cmd["constraints"][member].isArray()) { + error["info"] = + "Constraint for property '" + member + "' must be an array"; + return false; + } + auto size = cmd["constraints"][member].size(); + if (size != 2 && size != 4) { + error["info"] = "Constraint for property '" + member + + "' must be an array of size 2 or 4"; + return false; + } + } + } + } + + return true; +} + +int QueryHandlerPMGD::parse_commands(const protobufs::queryMessage &proto_query, + Json::Value &root) { + Json::Reader reader; + const std::string commands = proto_query.json(); + + try { + bool parseSuccess = reader.parse(commands.c_str(), root); + + if (!parseSuccess) { + root["info"] = "Error parsing the query, ill formed JSON"; + root["status"] = RSCommand::Error; + return -1; + } + + Json::Value error; + if (!syntax_checker(root, error)) { + root = error; + root["status"] = RSCommand::Error; + return -1; + } + + unsigned blob_counter = 0; + for (int j = 0; j < root.size(); j++) { + const Json::Value &query = root[j]; + assert(query.getMemberNames().size() == 1); + std::string cmd = query.getMemberNames()[0]; + + if (_rs_cmds[cmd]->need_blob(query)) { + blob_counter++; + } + } + + if (blob_counter != proto_query.blobs().size()) { + root = error; + root["info"] = std::string( + "Expected blobs: " + std::to_string(blob_counter) + + ". Received blobs: " + std::to_string(proto_query.blobs().size())); + root["status"] = RSCommand::Error; + std::cerr << "Not enough blobs!" << std::endl; + return -1; + } + + } catch (Json::Exception const &) { + root["info"] = "Json Exception at Parsing"; + root["status"] = RSCommand::Error; + return -1; + } + + return 0; +} + +void QueryHandlerPMGD::process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &proto_res) { + Json::FastWriter fastWriter; + + Json::Value root; + Json::Value exception_error; + std::stringstream error_msg; + + std::vector images_log; + std::vector videos_log; + + auto exception_handler = [&]() { + // When exception is catched, we return the message. + std::cerr << "Failed Query: " << std::endl; + std::cerr << root << std::endl; + std::cerr << error_msg.str(); + std::cerr << "End Failed Query: " << std::endl; + exception_error["info"] = error_msg.str(); + exception_error["status"] = RSCommand::Error; + Json::Value response; + response.append(exception_error); + proto_res.set_json(fastWriter.write(response)); + }; + + try { + Json::Value json_responses; + + Json::Value cmd_result; + Json::Value cmd_current; + + std::vector construct_results; + + auto error = [&](Json::Value &res, Json::Value &failed_command) { + cleanup_query(images_log, videos_log); + res["FailedCommand"] = failed_command; + json_responses.clear(); + json_responses.append(res); + proto_res.clear_blobs(); + proto_res.set_json(fastWriter.write(json_responses)); + Json::StyledWriter w; + std::cerr << w.write(json_responses); + }; + + if (parse_commands(proto_query, root) != 0) { + cmd_current = "Transaction"; + error(root, cmd_current); + return; + } + + PMGDQuery pmgd_query(_pmgd_qh); + int blob_count = 0; + + // iterate over the list of the queries + for (int j = 0; j < root.size(); j++) { + const Json::Value &query = root[j]; + std::string cmd = query.getMemberNames()[0]; + + int group_count = pmgd_query.add_group(); + + RSCommand *rscmd = _rs_cmds[cmd]; + + const std::string &blob = + rscmd->need_blob(query) ? proto_query.blobs(blob_count++) : ""; + + int ret_code = rscmd->construct_protobuf(pmgd_query, query, blob, + group_count, cmd_result); + + if (cmd_result.isMember("image_added")) { + images_log.push_back(cmd_result["image_added"].asString()); + } + if (cmd_result.isMember("video_added")) { + videos_log.push_back(cmd_result["video_added"].asString()); + } + + if (ret_code != 0) { + error(cmd_result, root[j]); + return; + } + + construct_results.push_back(cmd_result); + } + + Json::Value &tx_responses = pmgd_query.run(_autodelete_init); + + if (!tx_responses.isArray() || tx_responses.size() != root.size()) { + Json::StyledWriter writer; + std::cerr << "PMGD Response:" << std::endl; + std::cerr << writer.write(tx_responses) << std::endl; + + std::string tx_error_msg("Failed PMGD Transaction"); + if (!tx_responses.isArray() && tx_responses.isMember("info")) { + tx_error_msg += ": " + tx_responses["info"].asString(); + } + + cmd_result["status"] = RSCommand::Error; + cmd_result["info"] = tx_error_msg; + + cmd_current = "Transaction"; + error(cmd_result, cmd_current); + return; + } else { + blob_count = 0; + for (int j = 0; j < root.size(); j++) { + Json::Value &query = root[j]; + std::string cmd = query.getMemberNames()[0]; + + RSCommand *rscmd = _rs_cmds[cmd]; + + const std::string &blob = + rscmd->need_blob(query) ? proto_query.blobs(blob_count++) : ""; + + query["cp_result"] = construct_results[j]; + cmd_result = + rscmd->construct_responses(tx_responses[j], query, proto_res, blob); + + // This is for error handling + if (cmd_result.isMember("status")) { + int status = cmd_result["status"].asInt(); + if (status != RSCommand::Success || status != RSCommand::Empty || + status != RSCommand::Exists) { + error(cmd_result, root[j]); + return; + } + } + json_responses.append(cmd_result); + } + } + proto_res.set_json(fastWriter.write(json_responses)); + _pmgd_qh.cleanup_files(); + + } catch (VCL::Exception &e) { + print_exception(e); + error_msg << "Internal Server Error: VCL Exception at QH" << std::endl; + cleanup_query(images_log, videos_log); + exception_handler(); + } catch (PMGD::Exception &e) { + print_exception(e); + error_msg << "Internal Server Error: PMGD Exception at QH" << std::endl; + exception_handler(); + } catch (ExceptionCommand &e) { + print_exception(e); + error_msg << "Internal Server Error: Command Exception at QH" << std::endl; + exception_handler(); + } catch (Json::Exception const &e) { + // In case of error on the last fastWriter + error_msg << "Internal Server Error: Json Exception: " << e.what() + << std::endl; + exception_handler(); + // } catch (google::protobuf::FatalException &e) { + // // Need to be carefull with this, may lead to memory leak. + // // Protoubuf is not exception safe. + // error_msg << "Internal Server Error: Protobuf Exception: " << e.what() + // << std::endl; + // exception_handler(); + } catch (const std::invalid_argument &e) { + error_msg << "FATAL: Invalid argument: " << e.what() << std::endl; + exception_handler(); + } catch (const std::exception &e) { + error_msg << "std Exception: " << e.what() << std::endl; + exception_handler(); + } catch (...) { + error_msg << "Unknown Exception" << std::endl; + exception_handler(); + } +} + +void QueryHandlerPMGD::regular_run_autoreplicate( + ReplicationConfig &replicate_settings) { + std::string command = "bsdtar cvfz "; + std::string name; + std::ostringstream oss; + Json::Value config_file; + std::ofstream file_id; + name.clear(); + auto t = std::time(nullptr); + auto tm = *std::localtime(&t); + oss << asctime(&tm); + name = oss.str(); + name.erase(remove(name.begin(), name.end(), ' '), name.end()); + name.erase(std::remove(name.begin(), name.end(), '\n'), name.end()); + std::string full_name = replicate_settings.backup_path + "/" + name; + + command = command + " " + full_name + ".tar.gz " + + replicate_settings.db_path; // current_date_time + + system(command.c_str()); + + if (replicate_settings.server_port != 0) { + config_file["port"] = replicate_settings.server_port; + } + + if (!full_name.empty()) { + config_file["db_root_path"] = full_name; + } + + if (replicate_settings.autodelete_interval > 0) { + config_file["autodelete_interval"] = + replicate_settings + .autodelete_interval; // expired data removed daily (86400 secs) + } + + if (replicate_settings.expiration_time > 0) { + config_file["expiration_time"] = replicate_settings.expiration_time; + } + + config_file["more-info"] = "github.com/IntelLabs/vdms"; + + if (!replicate_settings.replication_time.empty()) { + config_file["autoreplicate_time"] = replicate_settings.replication_time; + } + + if (!replicate_settings.autoreplication_unit.empty()) { + config_file["unit"] = replicate_settings.autoreplication_unit; + } + + if (replicate_settings.autoreplicate_interval > 0) { + config_file["autoreplicate_interval"] = + replicate_settings.autoreplicate_interval; + } + + if (replicate_settings.max_simultaneous_clients > 0) { + config_file["max_simultaneous_clients"] = + replicate_settings.max_simultaneous_clients; + } + + if (!replicate_settings.backup_flag.empty()) { + config_file["backup_flag"] = replicate_settings.backup_flag; + } + if (!replicate_settings.backup_flag.empty()) { + config_file["backup_path"] = replicate_settings.backup_path; + } + if (!replicate_settings.backup_flag.empty()) { + config_file["images_path"] = replicate_settings.images_path; + } + if (!replicate_settings.backup_flag.empty()) { + config_file["blobs_path"] = replicate_settings.blobs_path; + } + if (!replicate_settings.backup_flag.empty()) { + config_file["descriptor_path"] = replicate_settings.descriptor_path; + } + if (!replicate_settings.backup_flag.empty()) { + config_file["pmgd_num_allocators"] = replicate_settings.pmgd_num_allocators; + } + std::string config_file_name = full_name + ".json"; + file_id.open(config_file_name.c_str(), std::ios::out); + file_id << config_file << std::endl; + file_id.close(); + + command = "bsdtar cvfz "; + oss.str(std::string()); + name.clear(); + config_file.clear(); +} +void QueryHandlerPMGD::reset_autoreplicate_init_flag() { + _autoreplicate_init = true; +} +void QueryHandlerPMGD::set_autoreplicate_init_flag() { + _autoreplicate_init = false; +} +void QueryHandlerPMGD::reset_autodelete_init_flag() { + _autodelete_init = false; +} + +void QueryHandlerPMGD::set_autodelete_init_flag() { _autodelete_init = true; } + +void QueryHandlerPMGD::regular_run_autodelete() { + std::string *json_string = new std::string( + "[{\"DeleteExpired\": {\"results\": {\"list\": [\"_expiration\"]}}}]"); + protobufs::queryMessage response; + protobufs::queryMessage query; + query.set_json(json_string->c_str()); + process_query(query, response); + delete json_string; +} + +void QueryHandlerPMGD::build_autodelete_queue() { + std::string *json_string = new std::string( + "[{\"FindImage\": {\"results\": {\"list\": [\"_expiration\"]}, " + "\"constraints\": {\"_expiration\": [\">\", 0]}}}, {\"FindVideo\": " + "{\"results\": {\"list\": [\"_expiration\"]}, \"constraints\": " + "{\"_expiration\": [\">\", 0]}}}], {\"FindFrames\": {\"results\": " + "{\"list\": [\"_expiration\"]}, \"constraints\": {\"_expiration\": " + "[\">\", 0]}}}], {\"FindDescriptor\": {\"results\": {\"list\": " + "[\"_expiration\"]}, \"constraints\": {\"_expiration\": [\">\", 0]}}}], " + "{\"FindEntity\": {\"results\": {\"list\": [\"_expiration\"]}, " + "\"constraints\": {\"_expiration\": [\">\", 0]}}}"); + protobufs::queryMessage response; + protobufs::queryMessage query; + query.set_json(json_string->c_str()); + process_query(query, response); + delete json_string; +} diff --git a/src/QueryHandlerPMGD.h b/src/QueryHandlerPMGD.h new file mode 100644 index 00000000..7d2571a3 --- /dev/null +++ b/src/QueryHandlerPMGD.h @@ -0,0 +1,70 @@ +/** + * @file QueryHandler.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2023 Intel Corporation + * + * 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. + * + */ +#pragma once + +#include "PMGDQueryHandler.h" // to provide the database connection +#include "QueryHandlerBase.h" +#include "RSCommand.h" +#include "Server.h" +#include "chrono/Chrono.h" + +namespace VDMS { + +class QueryHandlerPMGD : public QueryHandlerBase { + +protected: + friend class QueryHandlerTester; + + static std::unordered_map _rs_cmds; + PMGDQueryHandler _pmgd_qh; + bool _autodelete_init; + bool _autoreplicate_init; + + bool syntax_checker(const Json::Value &root, Json::Value &error); + int parse_commands(const protobufs::queryMessage &proto_query, + Json::Value &root); + +public: + static void init(); + QueryHandlerPMGD(); + + void process_query(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response); + void reset_autodelete_init_flag(); + void set_autodelete_init_flag(); + void regular_run_autodelete(); + void build_autodelete_queue(); + void set_autoreplicate_init_flag(); + void reset_autoreplicate_init_flag(); + void regular_run_autoreplicate(ReplicationConfig &); +}; + +} // namespace VDMS diff --git a/src/Server.cc b/src/Server.cc index 4ea79dc0..0b08ab33 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -42,7 +42,8 @@ #include "comm/Connection.h" #include "DescriptorsManager.h" -#include "QueryHandler.h" +#include "QueryHandlerExample.h" +#include "QueryHandlerPMGD.h" #include "VDMSConfig.h" #include "pmgdMessages.pb.h" // Protobuff implementation @@ -52,59 +53,83 @@ using namespace VDMS; bool Server::shutdown = false; Server::Server(std::string config_file) { + VDMSConfig::init(config_file); - _autoreplicate_settings.server_port = - VDMSConfig::instance()->get_int_value("port", DEFAULT_PORT); - - _autoreplicate_settings.max_simultaneous_clients = - VDMSConfig::instance()->get_int_value( - "max_simultaneous_clients", - 500); // Default from CommunicationManager.h - - _autoreplicate_settings.autodelete_interval = - VDMSConfig::instance()->get_int_value("autodelete_interval_s", - DEFAULT_AUTODELETE_INTERVAL); - _autoreplicate_settings.backup_flag = - VDMSConfig::instance()->get_string_value("backup_flag", - DEFAULT_AUTOREPLICATE_FLAG); - - _autoreplicate_settings.autoreplicate_interval = - VDMSConfig::instance()->get_int_value("autoreplicate_interval", - DEFAULT_AUTOREPLICATE_INTERVAL); - _autoreplicate_settings.autoreplication_unit = - VDMSConfig::instance()->get_string_value("unit", - DEFAULT_AUTOREPLICATE_UNIT); - - _autoreplicate_settings.replication_time = - VDMSConfig::instance()->get_string_value("replication_time", - DEFAULT_AUTOREPLICATE_UNIT); - _autoreplicate_settings.backup_path = - VDMSConfig::instance()->get_string_value("backup_path", - DEFAULT_BACKUP_PATH); - _autoreplicate_settings.db_path = - VDMSConfig::instance()->get_string_value("db_root_path", DEFAULT_DB_ROOT); - - PMGDQueryHandler::init(); - QueryHandler::init(); - - QueryHandler qh; - qh.set_autodelete_init_flag(); - qh.build_autodelete_queue(); // create priority queue of nodes with - // _expiration property - qh.regualar_run_autodelete(); // delete nodes that have expired since server - // previous closed - qh.reset_autodelete_init_flag(); // set flag to show autodelete queue has been - // initialized + + // pull out config into member variable for reference elsewhere + use in + // debugging + cfg = VDMSConfig::instance(); // Verify that the version of the library that we linked against is // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; - install_handler(); + // instantiate the right query handler. + this->setup_query_handler(); + + install_signal_handler(); _cm = new CommunicationManager(); } +void Server::setup_query_handler() { + + std::string qhandler_type; + qhandler_type = cfg->get_string_value("query_handler", DEFAULT_QUERY_HANDLER); + + // Select the correct logic for query handler instantiation + // This is pretty clunky ATM and wont scale beyond a few handlers, but should + // be okay as an on-ramp for the basic functionalty. + if (qhandler_type == "pmgd") { + printf("Setting up PMGD handler....\n"); + _autoreplicate_settings.server_port = + cfg->get_int_value("port", DEFAULT_PORT); + + _autoreplicate_settings.max_simultaneous_clients = + cfg->get_int_value("max_simultaneous_clients", + 500); // Default from CommunicationManager.h + + _autoreplicate_settings.autodelete_interval = cfg->get_int_value( + "autodelete_interval_s", DEFAULT_AUTODELETE_INTERVAL); + + _autoreplicate_settings.backup_flag = + cfg->get_string_value("backup_flag", DEFAULT_AUTOREPLICATE_FLAG); + + _autoreplicate_settings.autoreplicate_interval = cfg->get_int_value( + "autoreplicate_interval", DEFAULT_AUTOREPLICATE_INTERVAL); + + _autoreplicate_settings.autoreplication_unit = + cfg->get_string_value("unit", DEFAULT_AUTOREPLICATE_UNIT); + + _autoreplicate_settings.replication_time = + cfg->get_string_value("replication_time", DEFAULT_AUTOREPLICATE_UNIT); + + _autoreplicate_settings.backup_path = + cfg->get_string_value("backup_path", DEFAULT_BACKUP_PATH); + + _autoreplicate_settings.db_path = + cfg->get_string_value("db_root_path", DEFAULT_DB_ROOT); + + PMGDQueryHandler::init(); + QueryHandlerPMGD::init(); + + QueryHandlerPMGD qh; + qh.set_autodelete_init_flag(); + qh.build_autodelete_queue(); // create priority queue of nodes with + // _expiration property + qh.regular_run_autodelete(); // delete nodes that have expired since server + // previous closed + qh.reset_autodelete_init_flag(); // set flag to show autodelete queue has + // been + // initialized + } else if (qhandler_type == "example") { + QueryHandlerExample::init(); + } else { + printf("Unrecognized handler: \"%s\", exiting!\n", qhandler_type.c_str()); + exit(1); + } +} + void Server::process_requests() { comm::ConnServer *server; try { @@ -135,40 +160,54 @@ void Server::untar_data(std::string &name) { } void Server::auto_replicate_interval() { long replication_period = 0; - QueryHandler qh; + QueryHandlerPMGD qh; if (_autoreplicate_settings.backup_path.empty()) { _autoreplicate_settings.backup_path = _autoreplicate_settings.db_path; // set the default path to be db } - - if (_autoreplicate_settings.autoreplicate_interval > 0) { - if (_autoreplicate_settings.autoreplication_unit.compare("h") == 0) { + try { + if (_autoreplicate_settings.autoreplicate_interval == + Disable_Auto_Replicate) { replication_period = - _autoreplicate_settings.autoreplicate_interval * 60 * 60; - } else if (_autoreplicate_settings.autoreplication_unit.compare("m") == 0) { - replication_period = _autoreplicate_settings.autoreplicate_interval * 60; - } else { - replication_period = _autoreplicate_settings.autoreplicate_interval; + -1; // this is defualt value of disableing auto-replicate feature } - } - if (replication_period <= 0) { - std::cout << "Error: auto-replication interval must be a positive number." - << std::endl; - return; - } - while (!shutdown) { - // Sleep for the replication period - std::this_thread::sleep_for(std::chrono::seconds(replication_period)); + if (_autoreplicate_settings.autoreplicate_interval < + Disable_Auto_Replicate) { + replication_period = + Disable_Auto_Replicate; // this is defualt value of disableing + // auto-replicate feature + throw std::runtime_error( + "Error: auto-replication interval must be a positive number."); + } - // Execute the auto-replicate function - qh.regualar_run_autoreplicate(_autoreplicate_settings); + if (_autoreplicate_settings.autoreplicate_interval > 0) { + if (_autoreplicate_settings.autoreplication_unit.compare("h") == 0) { + replication_period = + _autoreplicate_settings.autoreplicate_interval * 60 * 60; + } else if (_autoreplicate_settings.autoreplication_unit.compare("m") == + 0) { + replication_period = + _autoreplicate_settings.autoreplicate_interval * 60; + } else { + replication_period = _autoreplicate_settings.autoreplicate_interval; + } + while (!shutdown) { + // Sleep for the replication period + std::this_thread::sleep_for(std::chrono::seconds(replication_period)); + + // Execute the auto-replicate function + qh.regular_run_autoreplicate(_autoreplicate_settings); + } + } + } catch (const std::runtime_error &e) { + std::cerr << e.what() << std::endl; } } void Server::auto_replicate_data_exact_time() { - QueryHandler qh; + QueryHandlerPMGD qh; std::istringstream iss(_autoreplicate_settings.replication_time); std::string time; @@ -207,7 +246,7 @@ void Server::auto_replicate_data_exact_time() { std::this_thread::sleep_for(duration); // Execute the auto-replicate function - qh.regualar_run_autoreplicate(_autoreplicate_settings); + qh.regular_run_autoreplicate(_autoreplicate_settings); } } @@ -215,15 +254,15 @@ void Server::autodelete_expired_data() { if (_autoreplicate_settings.autodelete_interval > 0) // check to ensure valid autodelete_interval { - QueryHandler qh; + QueryHandlerPMGD qh; while (!shutdown) { sleep(_autoreplicate_settings.autodelete_interval); - qh.regualar_run_autodelete(); // delete data expired since startup + qh.regular_run_autodelete(); // delete data expired since startup } } } -void Server::install_handler() { +void Server::install_signal_handler() { struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_handler = Server::sighandler; diff --git a/src/Server.h b/src/Server.h index 632353ec..1b1049a4 100644 --- a/src/Server.h +++ b/src/Server.h @@ -34,6 +34,7 @@ #include #include "CommunicationManager.h" +#include "VDMSConfig.h" #include "pmgd.h" #include @@ -66,6 +67,9 @@ struct ReplicationConfig { } }; class Server { + + // Defining constants/defaults within the class itself is a bit weird. + // Consider refactoring static const int DEFAULT_PORT = 55555; static const int DEFAULT_AUTODELETE_INTERVAL = -1; static const int DEFAULT_AUTOREPLICATE_INTERVAL = -1; @@ -73,21 +77,27 @@ class Server { std::string DEFAULT_BACKUP_PATH = "."; std::string DEFAULT_DB_ROOT = "db"; std::string DEFAULT_AUTOREPLICATE_FLAG = "false"; + std::string DEFAULT_QUERY_HANDLER = "pmgd"; + int Disable_Auto_Replicate = -1; CommunicationManager *_cm; ReplicationConfig _autoreplicate_settings; bool _untar; - // Handle ^c + // signal handling for crtl-c, static bool shutdown; - void install_handler(); + void install_signal_handler(); static void sighandler(int signo) { Server::shutdown = (signo == SIGINT) || (signo == SIGTERM) || (signo == SIGQUIT); } + // used to select as well as initialize any state for query handlers + void setup_query_handler(); + public: + VDMSConfig *cfg; Server(std::string config_file); void process_requests(); void autodelete_expired_data(); diff --git a/src/VideoCommand.cc b/src/VideoCommand.cc index 010ad307..291c3b4f 100644 --- a/src/VideoCommand.cc +++ b/src/VideoCommand.cc @@ -36,6 +36,7 @@ #include "ImageCommand.h" // for enqueue_operations of Image type #include "VDMSConfig.h" #include "VideoCommand.h" +#include "VideoLoop.h" #include "defines.h" using namespace VDMS; @@ -43,8 +44,8 @@ namespace fs = std::filesystem; VideoCommand::VideoCommand(const std::string &cmd_name) : RSCommand(cmd_name) {} -void VideoCommand::enqueue_operations(VCL::Video &video, - const Json::Value &ops) { +void VideoCommand::enqueue_operations(VCL::Video &video, const Json::Value &ops, + bool is_addition) { // Correct operation type and parameters are guaranteed at this point for (auto &op : ops) { const std::string &type = get_value(op, "type"); @@ -58,12 +59,37 @@ void VideoCommand::enqueue_operations(VCL::Video &video, get_value(op, "stop"), get_value(op, "step")); } else if (type == "resize") { - video.resize(get_value(op, "height"), get_value(op, "width")); + video.resize(get_value(op, "width"), get_value(op, "height")); } else if (type == "crop") { video.crop(VCL::Rectangle( get_value(op, "x"), get_value(op, "y"), get_value(op, "width"), get_value(op, "height"))); + } else if (type == "syncremoteOp") { + try { + video.syncremoteOperation(get_value(op, "url"), + get_value(op, "options")); + } catch (const std::exception &e) { + std::cerr << e.what() << '\n'; + } + } else if (type == "remoteOp") { + try { + if (is_addition) { + video.syncremoteOperation(get_value(op, "url"), + get_value(op, "options")); + } else { + video.remoteOperation(get_value(op, "url"), + get_value(op, "options")); + } + } catch (const std::exception &e) { + std::cerr << e.what() << '\n'; + } + } else if (type == "userOp") { + try { + video.userOperation(get_value(op, "options")); + } catch (const std::exception &e) { + std::cerr << e.what() << '\n'; + } } else { throw ExceptionCommand(ImageError, "Operation not defined"); } @@ -141,7 +167,7 @@ int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, frame_list = video.get_key_frame_list(); if (cmd.isMember("operations")) { - enqueue_operations(video, cmd["operations"]); + enqueue_operations(video, cmd["operations"], true); } // The container and codec are checked by the schema. @@ -165,6 +191,10 @@ int AddVideo::construct_protobuf(PMGDQuery &query, const Json::Value &jsoncmd, video.store(file_name, vcl_codec); + if (video.get_query_error_response() != video.NOERRORSTRING) { + throw VCLException(UndefinedException, video.get_query_error_response()); + } + if (_use_aws_storage) { video._remote->Write(file_name); std::remove(file_name.c_str()); // remove the local copy of the file @@ -278,6 +308,10 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, const Json::Value &cmd = json[_cmd_name]; Json::Value ret; + bool has_operations = false; + std::string no_op_def_video; + VCL::Video::Codec op_codec; + std::string op_container; auto error = [&](Json::Value &res) { ret[_cmd_name] = res; @@ -291,10 +325,18 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, Json::Value &FindVideo = responses[0]; - bool flag_empty = true; + if (FindVideo["entities"].size() == 0) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "No entities found"; + return error(return_error); + } + bool flag_empty = true; + VideoLoop videoLoop; for (auto &ent : FindVideo["entities"]) { + videoLoop.set_nrof_entities(FindVideo["entities"].size()); if (!ent.isMember(VDMS_VID_PATH_PROP)) { continue; } @@ -339,31 +381,43 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, if (cmd.isMember("operations")) { enqueue_operations(video, cmd["operations"]); + has_operations = true; } - const std::string &container = get_value(cmd, "container", "mp4"); - const std::string &file_name = - VCL::create_unique("/tmp/tmp/", container); + op_container = container; const std::string &codec = get_value(cmd, "codec", "h264"); VCL::Video::Codec vcl_codec = string_to_codec(codec); - video.store(file_name, vcl_codec); // to /tmp/ for encoding. - - auto video_enc = video.get_encoded(); - int size = video_enc.size(); + op_codec = vcl_codec; - if (size > 0) { - - std::string *video_str = query_res.add_blobs(); - video_str->resize(size); - std::memcpy((void *)video_str->data(), (void *)video_enc.data(), - size); - } else { + if (video.get_query_error_response() != video.NOERRORSTRING) { Json::Value return_error; return_error["status"] = RSCommand::Error; - return_error["info"] = "Video Data not found"; - error(return_error); + return_error["info"] = video.get_query_error_response(); + return error(return_error); + } + + if (has_operations) { + videoLoop.enqueue(video); + } else { + std::vector video_enc = + video.get_encoded(container, vcl_codec); + no_op_def_video = video.get_video_id(); + int size = video_enc.size(); + + if (size > 0) { + + std::string *video_str = query_res.add_blobs(); + video_str->resize(size); + std::memcpy((void *)video_str->data(), (void *)video_enc.data(), + size); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Video Data not found"; + error(return_error); + } } } } catch (VCL::Exception e) { @@ -375,6 +429,41 @@ Json::Value FindVideo::construct_responses(Json::Value &responses, } } + if (has_operations) { + while (videoLoop.is_loop_running()) { + continue; + } + std::map videoMap = videoLoop.get_video_map(); + std::map::iterator iter = videoMap.begin(); + + if (iter->second.get_query_error_response() != iter->second.NOERRORSTRING) { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = iter->second.get_query_error_response(); + return error(return_error); + } + + while (iter != videoMap.end()) { + auto video_enc = iter->second.get_encoded(op_container, op_codec); + int size = video_enc.size(); + + if (size > 0) { + + std::string *video_str = query_res.add_blobs(); + video_str->resize(size); + std::memcpy((void *)video_str->data(), (void *)video_enc.data(), size); + } else { + Json::Value return_error; + return_error["status"] = RSCommand::Error; + return_error["info"] = "Video Data not found"; + error(return_error); + } + iter++; + } + } else { + videoLoop.close_no_operation_loop(no_op_def_video); + } + if (flag_empty) { FindVideo.removeMember("entities"); } @@ -504,8 +593,6 @@ Json::Value FindFrames::construct_responses(Json::Value &responses, return error(return_error); } - VCL::Video video(video_path); - // grab the video from aws here if necessary if (_use_aws_storage) { VCL::RemoteConnection *connection = new VCL::RemoteConnection(); @@ -518,6 +605,8 @@ Json::Value FindFrames::construct_responses(Json::Value &responses, // local database location } + VCL::Video video(video_path); + // By default, return frames as PNGs VCL::Image::Format format = VCL::Image::Format::PNG; diff --git a/src/VideoCommand.h b/src/VideoCommand.h index becbb173..aeb94097 100644 --- a/src/VideoCommand.h +++ b/src/VideoCommand.h @@ -44,7 +44,8 @@ namespace VDMS { class VideoCommand : public RSCommand { protected: - void enqueue_operations(VCL::Video &video, const Json::Value &op); + void enqueue_operations(VCL::Video &video, const Json::Value &op, + bool is_addition = false); VCL::Video::Codec string_to_codec(const std::string &codec); diff --git a/src/VideoLoop.cc b/src/VideoLoop.cc new file mode 100644 index 00000000..9ce18a54 --- /dev/null +++ b/src/VideoLoop.cc @@ -0,0 +1,404 @@ +#include "VideoLoop.h" +#include "vcl/Exception.h" +#include + +VideoLoop::~VideoLoop() noexcept { + VCL::Video video(videoMap.begin()->first); + m_running = false; + r_running = false; + destroyed = true; + + enqueue(video); + m_thread.join(); + + r_enqueue(video); + r_thread.join(); +} + +bool VideoLoop::is_loop_running() { + if (m_running || r_running) { + return true; + } else { + return false; + } +} + +void VideoLoop::close_no_operation_loop(std::string videoid) { + VCL::Video video(videoid); + auto const result = + videoMap.insert(std::pair(videoid, video)); + if (not result.second) { + result.first->second = video; + } +} + +void VideoLoop::set_nrof_entities(int nrof_entities) { + _nrof_entities = nrof_entities; +} + +void VideoLoop::enqueue(VCL::Video video) noexcept { + { + std::lock_guard guard(m_mutex); + m_writeBuffer.push_back(video); + } + m_condVar.notify_one(); +} + +void VideoLoop::r_enqueue(VCL::Video video) noexcept { + { + std::lock_guard guard(r_mutex); + r_writeBuffer.push_back(video); + } + r_condVar.notify_one(); +} + +std::map VideoLoop::get_video_map() { + return videoMap; +} + +void VideoLoop::operationThread() noexcept { + std::vector readBuffer; + + while (m_running) { + { + std::unique_lock lock(m_mutex); + m_condVar.wait(lock, [this] { return !m_writeBuffer.empty(); }); + readBuffer.swap(m_writeBuffer); + } + int flag = 0; + for (VCL::Video video : readBuffer) { + // Execute operations on the video + int response = video.execute_operations(); + + if (response == -1) { + // An exception occured while executing the operations + // Terminate the eventloop + auto const result = videoMap.insert( + std::pair(video.get_video_id(), video)); + if (not result.second) { + result.first->second = video; + } + _remote_running = false; + flag = 0; + m_writeBuffer.clear(); + r_writeBuffer.clear(); + m_running = false; + r_running = false; + break; + } else { + if (video.get_enqueued_operation_count() > 0) { + // Remote operation encountered + response = video.execute_operations(true); + if (response == -1) { + // An exception occured while executing the operations + // Terminate the eventloop + auto const result = + videoMap.insert(std::pair( + video.get_video_id(), video)); + if (not result.second) { + result.first->second = video; + } + _remote_running = false; + flag = 0; + m_writeBuffer.clear(); + r_writeBuffer.clear(); + m_running = false; + r_running = false; + break; + } else { + // Enqueue the video onto the remote queue + r_enqueue(video); + flag = 1; + } + } else { + // All operations executed + // Finalize the videomap + auto const result = videoMap.insert( + std::pair(video.get_video_id(), video)); + if (not result.second) { + result.first->second = video; + } + } + } + } + readBuffer.clear(); + if (flag == 0 && _remote_running == false && m_writeBuffer.size() == 0 && + r_writeBuffer.size() == 0) { + // All eventloop tasks are completed + // setup terminating conditions + m_running = false; + r_running = false; + } + } +} + +/** + * Write the remote response to a local file + */ +static size_t videoCallback(void *ptr, size_t size, size_t nmemb, + void *stream) { + + size_t written = fwrite(ptr, size, nmemb, (FILE *)stream); + return written; +} + +CURL *VideoLoop::get_easy_handle(VCL::Video video, + std::string response_filepath) { + + // Get the remote operations parameters shared by the client + Json::Value rParams = video.get_remoteOp_params(); + std::string url = rParams["url"].toStyledString().data(); + url.erase(std::remove(url.begin(), url.end(), '\n'), url.end()); + url = url.substr(1, url.size() - 2); + Json::Value options = rParams["options"]; + + // Initialize curl + CURL *curl = NULL; + + CURLcode res; + struct curl_slist *headers = NULL; + curl_mime *form = NULL; + curl_mimepart *field = NULL; + + curl = curl_easy_init(); + + if (curl) { + + // Create the form to be sent to the remote operation + // We send the video file and the set of remote operation paramters + // as two form fields. + form = curl_mime_init(curl); + + field = curl_mime_addpart(form); + curl_mime_name(field, "videoData"); + if (curl_mime_filedata(field, video.get_operated_video_id().data()) != + CURLE_OK) { + throw VCLException(ObjectEmpty, + "Unable to retrieve local file for remoting"); + } + + field = curl_mime_addpart(form); + curl_mime_name(field, "jsonData"); + if (curl_mime_data(field, options.toStyledString().data(), + options.toStyledString().length()) != CURLE_OK) { + throw VCLException(ObjectEmpty, + "Unable to create curl mime data for client params"); + } + + // Post data + FILE *response_file = fopen(response_filepath.data(), "wb"); + url = url + "?id=" + video.get_video_id(); + + if (curl_easy_setopt(curl, CURLOPT_URL, url.data()) != CURLE_OK) { + throw VCLException(UndefinedException, "CURL setup error with URL"); + } + if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, videoCallback) != + CURLE_OK) { + throw VCLException(UndefinedException, "CURL setup error with callback"); + } + + if (response_file) { + if (curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_file) != + CURLE_OK) { + throw VCLException(UndefinedException, + "CURL setup error callback response file"); + } + if (curl_easy_setopt(curl, CURLOPT_MIMEPOST, form) != CURLE_OK) { + throw VCLException(UndefinedException, "CURL setup error with form"); + } + fclose(response_file); + return curl; + } + + return NULL; + } + + return NULL; +} + +void VideoLoop::execute_remote_operations(std::vector &readBuffer) { + int flag = 0; + int start_index = 0; + int step = 10; + int end_index = readBuffer.size() > step ? step : readBuffer.size(); + std::vector responseBuffer; + int rindex = 0; + std::map responseFileMaps; + try { + // Use multicurl to perform call to the remote API + // and receive response. We perform multiple amsll multicurl calls + // instead of a single large call to ensure that the remote server + // does not suspect an attack. + while (start_index != readBuffer.size()) { + CURLM *multi_handle; + CURLMsg *msg = NULL; + CURL *eh = NULL; + CURLcode return_code; + int still_running = 0, i = 0, msgs_left = 0; + int http_status_code; + char *szUrl; + + multi_handle = curl_multi_init(); + + auto start = readBuffer.begin() + start_index; + auto end = readBuffer.begin() + end_index; + + std::vector tempBuffer(start, end); + + for (VCL::Video video : tempBuffer) { + std::string video_id = video.get_operated_video_id(); + + Json::Value rParams = video.get_remoteOp_params(); + Json::Value options = rParams["options"]; + + std::string format = ""; + char *s = const_cast(video_id.data()); + std::string delimiter = "."; + char *p = std::strtok(s, delimiter.data()); + while (p != NULL) { + p = std::strtok(NULL, delimiter.data()); + if (p != NULL) { + format.assign(p, std::strlen(p)); + } + } + + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + std::string response_filepath = + "/tmp/rtempfile" + std::to_string(utc_time.count()) + "." + format; + + responseBuffer.push_back(response_filepath); + CURL *curl = get_easy_handle(video, responseBuffer[rindex]); + FILE *response_file = fopen(response_filepath.data(), "wb"); + responseFileMaps.insert( + std::pair(response_filepath, response_file)); + rindex++; + curl_multi_add_handle(multi_handle, curl); + } + + do { + CURLMcode mc = curl_multi_perform(multi_handle, &still_running); + if (still_running) + mc = curl_multi_wait(multi_handle, NULL, 0, 1000, NULL); + + if (mc) { + break; + } + } while (still_running); + + while ((msg = curl_multi_info_read(multi_handle, &msgs_left))) { + if (msg->msg == CURLMSG_DONE) { + eh = msg->easy_handle; + + return_code = msg->data.result; + + // Get HTTP status code + szUrl = NULL; + long rsize = 0; + + curl_easy_getinfo(eh, CURLINFO_RESPONSE_CODE, &http_status_code); + curl_easy_getinfo(eh, CURLINFO_EFFECTIVE_URL, &szUrl); + curl_easy_getinfo(eh, CURLINFO_REQUEST_SIZE, &rsize); + + if (http_status_code != 200) { + // Throw exceptions for different error codes received from the + // remote server + if (http_status_code == 0) { + throw VCLException(ObjectEmpty, "Remote server is not running."); + } + if (http_status_code == 400) { + throw VCLException(ObjectEmpty, + "Invalid Request to the Remote Server."); + } else if (http_status_code == 404) { + throw VCLException(ObjectEmpty, + "Invalid URL Request. Please check the URL."); + } else if (http_status_code == 500) { + throw VCLException(ObjectEmpty, + "Exception occurred at the remote server. " + "Please check your query."); + } else if (http_status_code == 503) { + throw VCLException(ObjectEmpty, "Unable to reach remote server"); + } else { + throw VCLException(ObjectEmpty, "Remote Server error."); + } + } + + curl_multi_remove_handle(multi_handle, eh); + curl_easy_cleanup(eh); + } else { + fprintf(stderr, "error: after curl_multi_info_read(), CURLMsg=%d\n", + msg->msg); + } + } + + tempBuffer.clear(); + start_index = end_index; + end_index = readBuffer.size() > (end_index + step) ? (end_index + step) + : readBuffer.size(); + } + rindex = -1; + // Finalize the remote operation and enqueue video on local queue + for (VCL::Video video : readBuffer) { + rindex++; + fclose(responseFileMaps[responseBuffer[rindex].data()]); + video.set_operated_video_id(responseBuffer[rindex]); + + auto const result = videoMap.insert( + std::pair(video.get_video_id(), video)); + if (not result.second) { + result.first->second = video; + } + if (rindex == readBuffer.size() - 1) { + _remote_running = false; + } + enqueue(video); + } + readBuffer.clear(); + } catch (VCL::Exception e) { + // Exception occured. Terminate the event loop. + VCL::Video video = readBuffer[0]; + video.set_query_error_response(e.msg); + + auto const result = videoMap.insert( + std::pair(video.get_video_id(), video)); + if (not result.second) { + result.first->second = video; + } + + readBuffer.clear(); + _remote_running = false; + m_writeBuffer.clear(); + r_writeBuffer.clear(); + m_running = false; + r_running = false; + + print_exception(e); + return; + } +} + +void VideoLoop::remoteOperationThread() noexcept { + std::vector readBuffer; + + while (r_running) { + // Swap the remote queue with a temporary vector on which operations can be + // performed + { + std::unique_lock rlock(r_mutex); + r_condVar.wait(rlock, [this] { return !r_writeBuffer.empty(); }); + if (r_writeBuffer.size() == _nrof_entities) { + std::swap(readBuffer, r_writeBuffer); + } + } + + if (readBuffer.size() == _nrof_entities && destroyed == false) { + // Set flag that remote operations are running and + // start the execution of remote operations on the temporary vector + _remote_running = true; + while (readBuffer.size() > 0) { + execute_remote_operations(readBuffer); + } + _remote_running = false; + } + } +} \ No newline at end of file diff --git a/src/VideoLoop.h b/src/VideoLoop.h new file mode 100644 index 00000000..76d58672 --- /dev/null +++ b/src/VideoLoop.h @@ -0,0 +1,140 @@ +/** + * @file VideoLoop.h + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2023 Intel Corporation + * + * 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. + * + */ + +#include "vcl/Image.h" +#include "vcl/Video.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +class VideoLoop { +public: + VideoLoop() = default; + VideoLoop(const VideoLoop &) = delete; + VideoLoop(VideoLoop &&) noexcept = delete; + ~VideoLoop() noexcept; + + VideoLoop &operator=(const VideoLoop &) = delete; + VideoLoop &operator=(VideoLoop &&) noexcept = delete; + + /** + * Sets the number of entities to be filled in the queue + * @param nrof_entities Number of entities in the query response + */ + void set_nrof_entities(int nrof_entities); + + /** + * Enqueue into the local queue + * @param video The video object to be enqueued + */ + void enqueue(VCL::Video video) noexcept; + + /** + * Enqueue into the remote queue + * @param video The video object to be enqueued + */ + void r_enqueue(VCL::Video video) noexcept; + + /** + * Get the map containing the operated video objects + */ + std::map get_video_map(); + + /** + * Check if the event loop is running + */ + bool is_loop_running(); + + /** + * If no operations are to be executed then create a dummy entry + * in the event loop and destroy it. + */ + void close_no_operation_loop(std::string videoId); + +private: + // Number of entities in the VDMS query response + int _nrof_entities = 0; + + // Is the event loop ready to be destroyed + bool destroyed = false; + + // Are any remote operations running + bool _remote_running = false; + + // Stores the operated videos. Key is the video id + std::map videoMap; + + /** + * The Local Queue parameters + */ + + std::vector m_writeBuffer; + std::mutex m_mutex; + std::condition_variable m_condVar; + bool m_running{true}; + std::thread m_thread{&VideoLoop::operationThread, this}; + // Local thread function + void operationThread() noexcept; + + /** + * The Remote Queue parameters + */ + std::vector r_writeBuffer; + std::mutex r_mutex; + std::condition_variable r_condVar; + bool r_running{true}; + std::thread r_thread{&VideoLoop::remoteOperationThread, this}; + // Local thread function + void remoteOperationThread() noexcept; + + /** + * Get the curl easy handles that will be used for multi-curl + * @param video The video object on which the remote operation will be + * performed + * @param response_filepath Path to the local file where the remote response + * file will be stored + */ + CURL *get_easy_handle(VCL::Video video, std::string response_filepath); + + /** + * Execute the remote operation using multi-curl + * @param readBuffer Stores all the videos on which the remote operation will + * be performed + */ + void execute_remote_operations(std::vector &readBuffer); +}; \ No newline at end of file diff --git a/src/defines.h b/src/defines.h index 5494e53d..7320afd9 100644 --- a/src/defines.h +++ b/src/defines.h @@ -61,6 +61,7 @@ #define VDMS_DESC_SET_PATH_PROP "VD:descSetPath" #define VDMS_DESC_SET_NAME_PROP "VD:name" #define VDMS_DESC_SET_DIM_PROP "VD:dimensions" +#define VDMS_DESC_SET_ENGIN_PROP "VD:engine" // Descriptor diff --git a/src/vcl/DescriptorParams.cc b/src/vcl/DescriptorParams.cc index e725ddbd..4e36bf4b 100644 --- a/src/vcl/DescriptorParams.cc +++ b/src/vcl/DescriptorParams.cc @@ -1,48 +1,48 @@ -/** - * - * @section LICENSE - * - * The MIT License - * - * @copyright Copyright (c) 2017 Intel Corporation - * - * 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. - * - * @section DESCRIPTION - * - */ -using namespace VCL; - -DescriptorParams::DescriptorParams(uint64_t numrows = 3, - uint64_t cellsperrow = (1 << 12), - uint64_t numhashtables = (1 << 9), - uint64_t hashespertable = 14, - uint64_t subhashbits = 2, - uint64_t cutoff = 6) { - this->num_rows = numrows; - this->cells_per_row = cellsperrow; - this->num_hash_tables = numhashtables; - this->hashes_per_table = hashespertable; - this->sub_hash_bits = - subhashbits; // sub_hash_bits * hashes_per_table must be less than 32, - // otherwise segfault will happen - this->cut_off = cutoff; -} +/** + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2017 Intel Corporation + * + * 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. + * + * @section DESCRIPTION + * + */ +using namespace VCL; + +DescriptorParams::DescriptorParams(uint64_t numrows = 3, + uint64_t cellsperrow = (1 << 12), + uint64_t numhashtables = (1 << 9), + uint64_t hashespertable = 14, + uint64_t subhashbits = 2, + uint64_t cutoff = 6) { + this->num_rows = numrows; + this->cells_per_row = cellsperrow; + this->num_hash_tables = numhashtables; + this->hashes_per_table = hashespertable; + this->sub_hash_bits = + subhashbits; // sub_hash_bits * hashes_per_table must be less than 32, + // otherwise segfault will happen + this->cut_off = cutoff; +} diff --git a/src/vcl/DescriptorParams.h b/src/vcl/DescriptorParams.h index 7282b2c7..c54ba2d7 100644 --- a/src/vcl/DescriptorParams.h +++ b/src/vcl/DescriptorParams.h @@ -1,77 +1,77 @@ -/** - * - * @section LICENSE - * - * The MIT License - * - * @copyright Copyright (c) 2017 Intel Corporation - * - * 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. - * - * @section DESCRIPTION - * - * This file declares the C++ Interface for the abstract DescriptorSetData - * object. - */ - -#pragma once - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "vcl/DescriptorSet.h" - -namespace VCL { - -class DescriptorParams { - -public: - /* Params needed for FLINNG */ - // constants for now until we derive them from N and dimensions - uint64_t num_rows; - uint64_t cells_per_row; - uint64_t num_hash_tables; - uint64_t hashes_per_table; - uint64_t sub_hash_bits; // sub_hash_bits * hashes_per_table must be less than - // 32, otherwise segfault will happen - uint64_t cut_off; - - DescriptorParams(uint64_t numrows = 3, uint64_t cellsperrow = (1 << 12), - uint64_t numhashtables = (1 << 9), - uint64_t hashespertable = 14, uint64_t subhashbits = 2, - uint64_t cutoff = 6) { - this->num_rows = numrows; - this->cells_per_row = cellsperrow; - this->num_hash_tables = numhashtables; - this->hashes_per_table = hashespertable; - this->sub_hash_bits = subhashbits; - this->cut_off = cutoff; - } -}; -}; // namespace VCL +/** + * + * @section LICENSE + * + * The MIT License + * + * @copyright Copyright (c) 2017 Intel Corporation + * + * 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. + * + * @section DESCRIPTION + * + * This file declares the C++ Interface for the abstract DescriptorSetData + * object. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "vcl/DescriptorSet.h" + +namespace VCL { + +class DescriptorParams { + +public: + /* Params needed for FLINNG */ + // constants for now until we derive them from N and dimensions + uint64_t num_rows; + uint64_t cells_per_row; + uint64_t num_hash_tables; + uint64_t hashes_per_table; + uint64_t sub_hash_bits; // sub_hash_bits * hashes_per_table must be less than + // 32, otherwise segfault will happen + uint64_t cut_off; + + DescriptorParams(uint64_t numrows = 3, uint64_t cellsperrow = (1 << 12), + uint64_t numhashtables = (1 << 9), + uint64_t hashespertable = 14, uint64_t subhashbits = 2, + uint64_t cutoff = 6) { + this->num_rows = numrows; + this->cells_per_row = cellsperrow; + this->num_hash_tables = numhashtables; + this->hashes_per_table = hashespertable; + this->sub_hash_bits = subhashbits; + this->cut_off = cutoff; + } +}; +}; // namespace VCL diff --git a/src/vcl/Image.cc b/src/vcl/Image.cc index 2bd64c9d..f996f6cb 100644 --- a/src/vcl/Image.cc +++ b/src/vcl/Image.cc @@ -162,20 +162,27 @@ void Image::Write::operator()(Image *img) { /* *********************** */ void Image::Resize::operator()(Image *img) { - if (_format == Image::Format::TDB) { - img->_tdb->resize(_rect); - img->_height = img->_tdb->get_image_height(); - img->_width = img->_tdb->get_image_width(); - img->_channels = img->_tdb->get_image_channels(); - } else { - if (!img->_cv_img.empty()) { - cv::Mat cv_resized; - cv::resize(img->_cv_img, cv_resized, cv::Size(_rect.width, _rect.height)); - img->shallow_copy_cv(cv_resized); - } else - throw VCLException(ObjectEmpty, "Image object is empty"); + try { + if (_format == Image::Format::TDB) { + img->_tdb->resize(_rect); + img->_height = img->_tdb->get_image_height(); + img->_width = img->_tdb->get_image_width(); + img->_channels = img->_tdb->get_image_channels(); + } else { + if (!img->_cv_img.empty()) { + cv::Mat cv_resized; + cv::resize(img->_cv_img, cv_resized, + cv::Size(_rect.width, _rect.height)); + img->shallow_copy_cv(cv_resized); + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; + } catch (VCL::Exception e) { + img->set_query_error_response(e.msg); + print_exception(e); + return; } - img->_op_completed++; } /* *********************** */ @@ -183,23 +190,29 @@ void Image::Resize::operator()(Image *img) { /* *********************** */ void Image::Crop::operator()(Image *img) { - if (_format == Image::Format::TDB) { - img->_tdb->read(_rect); - img->_height = img->_tdb->get_image_height(); - img->_width = img->_tdb->get_image_width(); - img->_channels = img->_tdb->get_image_channels(); - } else { - if (!img->_cv_img.empty()) { - if (img->_cv_img.rows < _rect.height + _rect.y || - img->_cv_img.cols < _rect.width + _rect.x) - throw VCLException(SizeMismatch, - "Requested area is not within the image"); - cv::Mat roi_img(img->_cv_img, _rect); - img->shallow_copy_cv(roi_img); - } else - throw VCLException(ObjectEmpty, "Image object is empty"); + try { + if (_format == Image::Format::TDB) { + img->_tdb->read(_rect); + img->_height = img->_tdb->get_image_height(); + img->_width = img->_tdb->get_image_width(); + img->_channels = img->_tdb->get_image_channels(); + } else { + if (!img->_cv_img.empty()) { + if (img->_cv_img.rows < _rect.height + _rect.y || + img->_cv_img.cols < _rect.width + _rect.x) + throw VCLException(SizeMismatch, + "Requested area is not within the image"); + cv::Mat roi_img(img->_cv_img, _rect); + img->shallow_copy_cv(roi_img); + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; + } catch (VCL::Exception e) { + img->set_query_error_response(e.msg); + print_exception(e); + return; } - img->_op_completed++; } /* *********************** */ @@ -207,16 +220,22 @@ void Image::Crop::operator()(Image *img) { /* *********************** */ void Image::Threshold::operator()(Image *img) { - if (_format == Image::Format::TDB) - img->_tdb->threshold(_threshold); - else { - if (!img->_cv_img.empty()) - cv::threshold(img->_cv_img, img->_cv_img, _threshold, _threshold, - cv::THRESH_TOZERO); - else - throw VCLException(ObjectEmpty, "Image object is empty"); + try { + if (_format == Image::Format::TDB) + img->_tdb->threshold(_threshold); + else { + if (!img->_cv_img.empty()) + cv::threshold(img->_cv_img, img->_cv_img, _threshold, _threshold, + cv::THRESH_TOZERO); + else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; + } catch (VCL::Exception e) { + img->set_query_error_response(e.msg); + print_exception(e); + return; } - img->_op_completed++; } /* *********************** */ @@ -224,20 +243,26 @@ void Image::Threshold::operator()(Image *img) { /* *********************** */ void Image::Flip::operator()(Image *img) { - if (_format == Image::Format::TDB) { - // Not implemented - throw VCLException(NotImplemented, - "Operation not supported for this format"); - } else { - if (!img->_cv_img.empty()) { - cv::Mat dst = - cv::Mat(img->_cv_img.rows, img->_cv_img.cols, img->_cv_img.type()); - cv::flip(img->_cv_img, dst, _code); - img->shallow_copy_cv(dst); - } else - throw VCLException(ObjectEmpty, "Image object is empty"); + try { + if (_format == Image::Format::TDB) { + // Not implemented + throw VCLException(NotImplemented, + "Operation not supported for this format"); + } else { + if (!img->_cv_img.empty()) { + cv::Mat dst = + cv::Mat(img->_cv_img.rows, img->_cv_img.cols, img->_cv_img.type()); + cv::flip(img->_cv_img, dst, _code); + img->shallow_copy_cv(dst); + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; + } catch (VCL::Exception e) { + img->set_query_error_response(e.msg); + print_exception(e); + return; } - img->_op_completed++; } /* *********************** */ @@ -245,43 +270,49 @@ void Image::Flip::operator()(Image *img) { /* *********************** */ void Image::Rotate::operator()(Image *img) { - if (_format == Image::Format::TDB) { - // Not implemented - throw VCLException(NotImplemented, - "Operation not supported for this format"); - } else { - if (!img->_cv_img.empty()) { + try { + if (_format == Image::Format::TDB) { + // Not implemented + throw VCLException(NotImplemented, + "Operation not supported for this format"); + } else { + if (!img->_cv_img.empty()) { - if (_keep_size) { - cv::Mat dst = - cv::Mat(img->_cv_img.rows, img->_cv_img.cols, img->_cv_img.type()); + if (_keep_size) { + cv::Mat dst = cv::Mat(img->_cv_img.rows, img->_cv_img.cols, + img->_cv_img.type()); - cv::Point2f im_c(img->_cv_img.cols / 2., img->_cv_img.rows / 2.); - cv::Mat r = cv::getRotationMatrix2D(im_c, _angle, 1.0); + cv::Point2f im_c(img->_cv_img.cols / 2., img->_cv_img.rows / 2.); + cv::Mat r = cv::getRotationMatrix2D(im_c, _angle, 1.0); - cv::warpAffine(img->_cv_img, dst, r, img->_cv_img.size()); - img->_cv_img = dst.clone(); - } else { + cv::warpAffine(img->_cv_img, dst, r, img->_cv_img.size()); + img->_cv_img = dst.clone(); + } else { - cv::Point2f im_c((img->_cv_img.cols - 1) / 2.0, - (img->_cv_img.rows - 1) / 2.0); - cv::Mat r = cv::getRotationMatrix2D(im_c, _angle, 1.0); - // Bbox rectangle - cv::Rect2f bbox = - cv::RotatedRect(cv::Point2f(), img->_cv_img.size(), _angle) - .boundingRect2f(); - // Transformation Matrix - r.at(0, 2) += bbox.width / 2.0 - img->_cv_img.cols / 2.0; - r.at(1, 2) += bbox.height / 2.0 - img->_cv_img.rows / 2.0; - - cv::Mat dst; - cv::warpAffine(img->_cv_img, dst, r, bbox.size()); - img->shallow_copy_cv(dst); - } - } else - throw VCLException(ObjectEmpty, "Image object is empty"); + cv::Point2f im_c((img->_cv_img.cols - 1) / 2.0, + (img->_cv_img.rows - 1) / 2.0); + cv::Mat r = cv::getRotationMatrix2D(im_c, _angle, 1.0); + // Bbox rectangle + cv::Rect2f bbox = + cv::RotatedRect(cv::Point2f(), img->_cv_img.size(), _angle) + .boundingRect2f(); + // Transformation Matrix + r.at(0, 2) += bbox.width / 2.0 - img->_cv_img.cols / 2.0; + r.at(1, 2) += bbox.height / 2.0 - img->_cv_img.rows / 2.0; + + cv::Mat dst; + cv::warpAffine(img->_cv_img, dst, r, bbox.size()); + img->shallow_copy_cv(dst); + } + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; + } catch (VCL::Exception e) { + img->set_query_error_response(e.msg); + print_exception(e); + return; } - img->_op_completed++; } /* *********************** */ @@ -289,15 +320,21 @@ void Image::Rotate::operator()(Image *img) { /* *********************** */ void Image::RemoteOperation::operator()(Image *img) { - if (_format == Image::Format::TDB) { - // Not implemented - throw VCLException(NotImplemented, - "Operation not supported for this format"); - } else { - if (!img->_cv_img.empty()) { - img->set_remoteOp_params(_options, _url); - } else - throw VCLException(ObjectEmpty, "Image object is empty"); + try { + if (_format == Image::Format::TDB) { + // Not implemented + throw VCLException(NotImplemented, + "Operation not supported for this format"); + } else { + if (!img->_cv_img.empty()) { + img->set_remoteOp_params(_options, _url); + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + } catch (VCL::Exception e) { + img->set_query_error_response(e.msg); + print_exception(e); + return; } } @@ -311,107 +348,144 @@ size_t writeCallback(char *ip, size_t size, size_t nmemb, void *op) { } void Image::SyncRemoteOperation::operator()(Image *img) { - if (_format == Image::Format::TDB) { - // Not implemented - throw VCLException(NotImplemented, - "Operation not supported for this format"); - } else { - if (!img->_cv_img.empty()) { + try { + if (_format == Image::Format::TDB) { + // Not implemented + throw VCLException(NotImplemented, + "Operation not supported for this format"); + } else { + if (!img->_cv_img.empty()) { - std::string readBuffer; + std::string readBuffer; - CURL *curl = NULL; + CURL *curl = NULL; - CURLcode res; - struct curl_slist *headers = NULL; - curl_mime *form = NULL; - curl_mimepart *field = NULL; + CURLcode res; + struct curl_slist *headers = NULL; + curl_mime *form = NULL; + curl_mimepart *field = NULL; - curl = curl_easy_init(); + curl = curl_easy_init(); - if (curl) { - auto time_now = std::chrono::system_clock::now(); - std::chrono::duration utc_time = time_now.time_since_epoch(); + if (curl) { + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); - VCL::Image::Format img_format = img->get_image_format(); - std::string format = img->format_to_string(img_format); + VCL::Image::Format img_format = img->get_image_format(); + std::string format = img->format_to_string(img_format); - if (format == "" && _options.isMember("format")) { - format = _options["format"].toStyledString().data(); - format.erase(std::remove(format.begin(), format.end(), '\n'), - format.end()); - format = format.substr(1, format.size() - 2); - } else { - format = "jpg"; - } + if (format == "" && _options.isMember("format")) { + format = _options["format"].toStyledString().data(); + format.erase(std::remove(format.begin(), format.end(), '\n'), + format.end()); + format = format.substr(1, format.size() - 2); + } else { + format = "jpg"; + } - std::string filePath = - "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; - cv::imwrite(filePath, img->_cv_img); + std::string filePath = + "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; + cv::imwrite(filePath, img->_cv_img); - std::ofstream tsfile; + std::ofstream tsfile; - auto opstart = std::chrono::system_clock::now(); + auto opstart = std::chrono::system_clock::now(); - form = curl_mime_init(curl); + form = curl_mime_init(curl); - field = curl_mime_addpart(form); - curl_mime_name(field, "imageData"); - if (curl_mime_filedata(field, filePath.data()) != CURLE_OK) { - if (std::remove(filePath.data()) != 0) { + field = curl_mime_addpart(form); + curl_mime_name(field, "imageData"); + if (curl_mime_filedata(field, filePath.data()) != CURLE_OK) { + if (std::remove(filePath.data()) != 0) { + } + throw VCLException(ObjectEmpty, + "Unable to create file for remoting"); } - throw VCLException(ObjectEmpty, "Unable to create file for remoting"); - } - field = curl_mime_addpart(form); - curl_mime_name(field, "jsonData"); - if (curl_mime_data(field, _options.toStyledString().data(), - _options.toStyledString().length()) != CURLE_OK) { - if (std::remove(filePath.data()) != 0) { + field = curl_mime_addpart(form); + curl_mime_name(field, "jsonData"); + if (curl_mime_data(field, _options.toStyledString().data(), + _options.toStyledString().length()) != CURLE_OK) { + if (std::remove(filePath.data()) != 0) { + } + throw VCLException(ObjectEmpty, "Unable to create curl mime data"); } - throw VCLException(ObjectEmpty, "Unable to create curl mime data"); - } - // Post data - if (curl_easy_setopt(curl, CURLOPT_URL, _url.data()) != CURLE_OK) { - throw VCLException(UndefinedException, "CURL setup error with URL"); - } - if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback) != - CURLE_OK) { - throw VCLException(UndefinedException, - "CURL setup error with callback"); - } - if (curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer) != - CURLE_OK) { - throw VCLException(UndefinedException, - "CURL setup error with read buffer"); - } - if (curl_easy_setopt(curl, CURLOPT_MIMEPOST, form) != CURLE_OK) { - throw VCLException(UndefinedException, "CURL setup error with form"); - } + // Post data + if (curl_easy_setopt(curl, CURLOPT_URL, _url.data()) != CURLE_OK) { + throw VCLException(UndefinedException, "CURL setup error with URL"); + } + if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeCallback) != + CURLE_OK) { + throw VCLException(UndefinedException, + "CURL setup error with callback"); + } + if (curl_easy_setopt(curl, CURLOPT_WRITEDATA, &readBuffer) != + CURLE_OK) { + throw VCLException(UndefinedException, + "CURL setup error with read buffer"); + } + if (curl_easy_setopt(curl, CURLOPT_MIMEPOST, form) != CURLE_OK) { + throw VCLException(UndefinedException, + "CURL setup error with form"); + } - res = curl_easy_perform(curl); + res = curl_easy_perform(curl); + + int http_status_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status_code); + + curl_easy_cleanup(curl); + curl_mime_free(form); + + if (http_status_code != 200) { + if (http_status_code == 0) { + throw VCLException(ObjectEmpty, "Remote server is not running."); + } + if (http_status_code == 400) { + throw VCLException(ObjectEmpty, + "Invalid Request to the Remote Server."); + } else if (http_status_code == 404) { + throw VCLException(ObjectEmpty, + "Invalid URL Request. Please check the URL."); + } else if (http_status_code == 500) { + throw VCLException(ObjectEmpty, + "Exception occurred at the remote server. " + "Please check your query."); + } else if (http_status_code == 503) { + throw VCLException(ObjectEmpty, "Unable to reach remote server"); + } else { + throw VCLException(ObjectEmpty, "Remote Server error."); + } + } - curl_easy_cleanup(curl); - curl_mime_free(form); + // Decode the response + std::vector vectordata(readBuffer.begin(), + readBuffer.end()); + cv::Mat data_mat(vectordata, true); - // Decode the response + if (data_mat.empty()) { + throw VCLException(ObjectEmpty, + "Empty response from remote server"); + } - std::vector vectordata(readBuffer.begin(), - readBuffer.end()); - cv::Mat data_mat(vectordata, true); - cv::Mat decoded_mat(cv::imdecode(data_mat, 1)); + cv::Mat decoded_mat(cv::imdecode(data_mat, 1)); - img->shallow_copy_cv(decoded_mat); + img->shallow_copy_cv(decoded_mat); - if (std::remove(filePath.data()) != 0) { + if (std::remove(filePath.data()) != 0) { + } } - } - } else - throw VCLException(ObjectEmpty, "Image object is empty"); + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; + } catch (VCL::Exception e) { + img->set_query_error_response(e.msg); + print_exception(e); + return; } - img->_op_completed++; } /* *********************** */ @@ -419,91 +493,91 @@ void Image::SyncRemoteOperation::operator()(Image *img) { /* *********************** */ void Image::UserOperation::operator()(Image *img) { - if (_format == Image::Format::TDB) { - // Not implemented - throw VCLException(NotImplemented, - "Operation not supported for this format"); - } else { - if (!img->_cv_img.empty()) { - - std::string opfile; + try { + if (_format == Image::Format::TDB) { + // Not implemented + throw VCLException(NotImplemented, + "Operation not supported for this format"); + } else { + if (!img->_cv_img.empty()) { - zmq::context_t context(1); - zmq::socket_t socket(context, zmq::socket_type::req); + std::string opfile; - std::string port = _options["port"].asString(); - std::string address = "tcp://127.0.0.1:" + port; + zmq::context_t context(1); + zmq::socket_t socket(context, zmq::socket_type::req); - socket.connect(address.data()); + std::string port = _options["port"].asString(); + std::string address = "tcp://127.0.0.1:" + port; - auto time_now = std::chrono::system_clock::now(); - std::chrono::duration utc_time = time_now.time_since_epoch(); + socket.connect(address.data()); - VCL::Image::Format img_format = img->get_image_format(); - std::string format = img->format_to_string(img_format); + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); - if (format == "" && _options.isMember("format")) { - format = _options["format"].toStyledString().data(); - format.erase(std::remove(format.begin(), format.end(), '\n'), - format.end()); - format = format.substr(1, format.size() - 2); - } else { - format = "jpg"; - } + VCL::Image::Format img_format = img->get_image_format(); + std::string format = img->format_to_string(img_format); - std::string filePath = - "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; - cv::imwrite(filePath, img->_cv_img); + if (format == "" && _options.isMember("format")) { + format = _options["format"].toStyledString().data(); + format.erase(std::remove(format.begin(), format.end(), '\n'), + format.end()); + format = format.substr(1, format.size() - 2); + } else { + format = "jpg"; + } - // std::string operation_id = _options["id"].toStyledString().data(); - // operation_id.erase(std::remove(operation_id.begin(), - // operation_id.end(), '\n'), operation_id.end()); operation_id = - // operation_id.substr(1, operation_id.size() - 2); + std::string filePath = + "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; + cv::imwrite(filePath, img->_cv_img); - _options["ipfile"] = filePath; + _options["ipfile"] = filePath; - // std::string message_to_send = filePath + "::" + operation_id; - std::string message_to_send = _options.toStyledString(); + std::string message_to_send = _options.toStyledString(); - int message_len = message_to_send.length(); - zmq::message_t ipfile(message_len); - memcpy(ipfile.data(), message_to_send.data(), message_len); + int message_len = message_to_send.length(); + zmq::message_t ipfile(message_len); + memcpy(ipfile.data(), message_to_send.data(), message_len); - socket.send(ipfile, 0); + socket.send(ipfile, 0); - while (true) { - char buffer[256]; - int size = socket.recv(buffer, 255, 0); + while (true) { + char buffer[256]; + int size = socket.recv(buffer, 255, 0); - buffer[size] = '\0'; - opfile = buffer; + buffer[size] = '\0'; + opfile = buffer; - break; - } + break; + } - std::ifstream rfile; - rfile.open(opfile); + std::ifstream rfile; + rfile.open(opfile); - if (rfile) { - rfile.close(); - } else { - if (std::remove(filePath.data()) != 0) { + if (rfile) { + rfile.close(); + } else { + if (std::remove(filePath.data()) != 0) { + } + throw VCLException(OpenFailed, "UDF Error"); } - throw VCLException(OpenFailed, "UDF Error"); - } - VCL::Image res_image(opfile); - img->shallow_copy_cv(res_image.get_cvmat(true)); + VCL::Image res_image(opfile); + img->shallow_copy_cv(res_image.get_cvmat(true)); - if (std::remove(filePath.data()) != 0) { - } + if (std::remove(filePath.data()) != 0) { + } - if (std::remove(opfile.data()) != 0) { - } - } else - throw VCLException(ObjectEmpty, "Image object is empty"); + if (std::remove(opfile.data()) != 0) { + } + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } + img->_op_completed++; + } catch (VCL::Exception e) { + img->set_query_error_response(e.msg); + print_exception(e); + return; } - img->_op_completed++; } /* *********************** */ @@ -871,6 +945,8 @@ int Image::get_op_completed() { return _op_completed; } Json::Value Image::get_remoteOp_params() { return remoteOp_params; } +std::string Image::get_query_error_response() { return _query_error_response; } + std::vector Image::get_encoded_image(Image::Format format, const std::vector ¶ms) { @@ -1049,6 +1125,10 @@ void Image::set_remoteOp_params(Json::Value options, std::string url) { remoteOp_params["url"] = url; } +void Image::set_query_error_response(std::string response_error) { + _query_error_response = response_error; +} + void Image::update_op_completed() { _op_completed++; } void Image::set_connection(RemoteConnection *remote) { @@ -1093,10 +1173,18 @@ int Image::execute_operation() { if ((*op).get_type() != VCL::Image::OperationType::REMOTEOPERATION) { (*op)(this); - return 0; + if (this->get_query_error_response() == "") { + return 0; + } else { + return -2; + } } else { (*op)(this); - return -1; + if (this->get_query_error_response() == "") { + return -1; + } else { + return -2; + } } } diff --git a/src/vcl/Video.cc b/src/vcl/Video.cc index 797bc82f..9d3eb788 100644 --- a/src/vcl/Video.cc +++ b/src/vcl/Video.cc @@ -5,7 +5,7 @@ * * The MIT License * - * @copyright Copyright (c) 2017 Intel Corporation + * @copyright Copyright (c) 2023 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -41,11 +41,12 @@ using namespace VCL; Video::Video() : _size({.width = 0, .height = 0, .frame_count = 0}), _fps(0), _video_id(""), _flag_stored(true), _codec(Video::Codec::NOCODEC), - _video_read(nullptr), _remote(nullptr) {} + _remote(nullptr) {} Video::Video(const std::string &video_id) : Video() { _video_id = video_id; _remote = nullptr; + initialize_video_attributes(_video_id); } Video::Video(void *buffer, long size) : Video() { @@ -60,6 +61,8 @@ Video::Video(void *buffer, long size) : Video() { throw VCLException(OpenFailed, "Cannot create temporary file"); _video_id = uname; + + initialize_video_attributes(_video_id); } Video::Video(const Video &video) { @@ -76,13 +79,13 @@ Video::Video(const Video &video) { _flag_stored = video._flag_stored; - //_frames = video._frames; _operations = video._operations; - _video_read = video._video_read; + _operated_video_id = video._operated_video_id; + + remoteOp_params = video.remoteOp_params; - for (const auto &op : video._operations) - _operations.push_back(op); + _query_error_response = video._query_error_response; } Video &Video::operator=(Video vid) { @@ -91,7 +94,6 @@ Video &Video::operator=(Video vid) { } Video::~Video() { - _video_read = nullptr; _operations.clear(); _key_frame_decoder.reset(); } @@ -104,43 +106,32 @@ std::string Video::get_video_id() const { return _video_id; } Video::Codec Video::get_codec() const { return _codec; } -Image *Video::read_frame(int index) { - if (_video_read == nullptr) { - throw VCLException(UnsupportedOperation, "Video file not opened"); - } - - Image *pframe = _video_read->read_frame(index); - if (pframe == nullptr) - _video_read = nullptr; // Reaching the end, close the input video - return pframe; -} - -// FIXME video read object is not released correctly. -cv::Mat Video::get_frame(unsigned frame_number) { +cv::Mat Video::get_frame(unsigned frame_number, bool performOp) { cv::Mat frame; if (_key_frame_decoder == nullptr) { - bool new_read = false; - std::shared_ptr video_read; - //_video_read not initialized, the current function is called directly - if (_video_read == nullptr) { - video_read = std::make_shared(this); - // open the video file - (*video_read)(0); - new_read = true; - } - // _video_read initialized, the current function is called by get_frames - else { - video_read = _video_read; - } - VCL::Image *pframe = video_read->read_frame(frame_number); - if (new_read) { - _video_read = nullptr; - } - if (pframe == nullptr) + if (performOp) + perform_operations(); + if (frame_number >= _size.frame_count) throw VCLException(OutOfBounds, "Frame requested is out of bounds"); - frame = pframe->get_cvmat(); + cv::VideoCapture inputVideo(_video_id); + int i = 0; + // Loop until the required frame is read + while (true) { + cv::Mat mat_frame; + inputVideo >> mat_frame; + + if (mat_frame.empty()) { + break; + } + if (i == frame_number) { + frame = mat_frame; + break; + } + i++; + } + inputVideo.release(); } else { std::vector frame_list = {frame_number}; @@ -154,7 +145,6 @@ cv::Mat Video::get_frame(unsigned frame_number) { return frame; } -// FIXME video read object is not released correctly. std::vector Video::get_frames(std::vector frame_list) { std::vector image_list; @@ -165,14 +155,8 @@ std::vector Video::get_frames(std::vector frame_list) { if (_key_frame_decoder == nullptr) { // Key frame information is not available: video will be decoded using // OpenCV. - _video_read = std::make_shared(this); - // open the video file - (*_video_read)(0); - for (const auto &f : frame_list) image_list.push_back(get_frame(f)); - - _video_read = nullptr; } else { // Key frame information is set, video will be partially decoded using // _key_frame_decoder object. @@ -188,29 +172,108 @@ std::vector Video::get_frames(std::vector frame_list) { return image_list; } -long Video::get_frame_count() { - perform_operations(); +long Video::get_frame_count(bool performOp) { + if (performOp) + perform_operations(); return _size.frame_count; } float Video::get_fps() { return _fps; } -cv::Size Video::get_frame_size() { - perform_operations(); +cv::Size Video::get_frame_size(bool performOp) { + if (performOp) + perform_operations(); cv::Size dims((int)_size.width, (int)_size.height); return dims; } -Video::VideoSize Video::get_size() { - perform_operations(); +Video::VideoSize Video::get_size(bool performOp) { + if (performOp) + perform_operations(); return _size; } -std::vector Video::get_encoded() { - if (_flag_stored == false) - throw VCLException(ObjectEmpty, "Object not written"); +int Video::get_enqueued_operation_count() { return _operations.size(); } + +std::vector Video::get_encoded(std::string container, + VCL::Video::Codec vcl_codec) { + + // Check if the video codec and container are same as the ones requested by + // the client If not then encode the video with the respective codec/container + if (_codec != vcl_codec) { + std::string id = _operated_video_id; + + // Retrieve container from file + char *s = const_cast(id.data()); + std::string format = ""; + if (std::strcmp(s, "") == 0) { + std::string delimiter = "."; + char *p = std::strtok(s, delimiter.data()); + while (p != NULL) { + p = std::strtok(NULL, delimiter.data()); + if (p != NULL) { + format.assign(p, std::strlen(p)); + } + } + } + + // Check if container (format) matches client container + if (format != "" && format != container) { + + cv::VideoCapture inputVideo(_operated_video_id); + + _fps = static_cast(inputVideo.get(cv::CAP_PROP_FPS)); + _size.frame_count = + static_cast(inputVideo.get(cv::CAP_PROP_FRAME_COUNT)); + _size.width = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_WIDTH)); + _size.height = + static_cast(inputVideo.get(cv::CAP_PROP_FRAME_HEIGHT)); + int fourcc = get_video_fourcc(vcl_codec); + + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + std::string fname = + "tmp/tempfile" + std::to_string(utc_time.count()) + container; + + // check sufficient memory + bool memory_avail = check_sufficient_memory(_size); + if (!memory_avail) { + throw VCLException(UnsupportedOperation, + "System out of memory, please retry later"); + } + + cv::VideoWriter outputVideo(fname, fourcc, _fps, + cv::Size(_size.width, _size.height)); + + // Write the video with the client codec and container + while (true) { + + cv::Mat mat_frame; + inputVideo >> mat_frame; // Read frame + + if (mat_frame.empty()) + break; + + outputVideo << mat_frame; + + mat_frame.release(); + } + + inputVideo.release(); + outputVideo.release(); - std::ifstream ifile(_video_id, std::ifstream::in); + if (std::remove(_operated_video_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + if (std::rename(fname.data(), _operated_video_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while renaming the file."); + } + } + } + + std::ifstream ifile(_operated_video_id, std::ifstream::in); ifile.seekg(0, std::ios::end); size_t encoded_size = (long)ifile.tellg(); ifile.seekg(0, std::ios::beg); @@ -220,6 +283,11 @@ std::vector Video::get_encoded() { ifile.read((char *)encoded.data(), encoded_size); ifile.close(); + if (std::remove(_operated_video_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + return encoded; } @@ -232,6 +300,33 @@ const KeyFrameList &Video::get_key_frame_list() { set_key_frame_list(_key_frame_list); return _key_frame_list; } +std::string Video::get_query_error_response() { return _query_error_response; } + +int Video::get_video_fourcc(VCL::Video::Codec _codec) { + switch (_codec) { + case VCL::Video::Codec::MJPG: + return cv::VideoWriter::fourcc('M', 'J', 'P', 'G'); + case VCL::Video::Codec::XVID: + return cv::VideoWriter::fourcc('X', 'V', 'I', 'D'); + case VCL::Video::Codec::H263: + return cv::VideoWriter::fourcc('U', '2', '6', '3'); + case VCL::Video::Codec::H264: + return cv::VideoWriter::fourcc('X', '2', '6', '4'); + case VCL::Video::Codec::AVC1: + return cv::VideoWriter::fourcc('A', 'V', 'C', '1'); + default: + throw VCLException(UnsupportedFormat, + std::to_string((int)_codec) + " is not a valid format"); + } +} + +Json::Value Video::get_remoteOp_params() { return remoteOp_params; } + +/* *********************** */ +/* SET FUNCTIONS */ +/* *********************** */ + +std::string Video::get_operated_video_id() { return _operated_video_id; } /* *********************** */ /* SET FUNCTIONS */ @@ -255,59 +350,286 @@ void Video::set_key_frame_list(KeyFrameList &key_frames) { _key_frame_decoder->set_key_frames(key_frames); } +void Video::set_remoteOp_params(Json::Value options, std::string url) { + remoteOp_params["options"] = options; + remoteOp_params["url"] = url; +} + +void Video::set_operated_video_id(std::string filename) { + _operated_video_id = filename; +} + /* *********************** */ /* UTILITIES */ /* *********************** */ -void Video::perform_operations() { - try { - // At this point, there are three different potential callees: - // - // - An object is instantiated through the default constructor with - // no name: an exception is thrown as no operations can be applied. - // - // - An object is instantiated through one-arg string constructor, - // but has no operations set explicitely (i.e. when calling - // get_frame_count()): a 'read' operation is pushed to the head of - // the queue. - // - // - An object is instantiated through any of the non-default - // constructors, and has pushed operations explicitely: a 'read' - // operation is pushed to the head of the queue. - if (_operations.empty() || _operations.front()->get_type() != READ) { - //&& !is_read()) { - if (_video_id.empty()) - throw VCLException(OpenFailed, "video_id is not initialized"); - _operations.push_front(std::make_shared(this)); +bool Video::is_read(void) { return (_size.frame_count > 0); } + +void Video::initialize_video_attributes(std::string vname) { + if (vname == "") { + return; + } + cv::VideoCapture inputVideo(vname); + + _fps = static_cast(inputVideo.get(cv::CAP_PROP_FPS)); + _size.frame_count = + static_cast(inputVideo.get(cv::CAP_PROP_FRAME_COUNT)); + _size.width = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_WIDTH)); + _size.height = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_HEIGHT)); + + inputVideo.release(); +} + +bool Video::check_sufficient_memory(const struct VideoSize &size) { + SystemStats systemStats; + + int frameSizeB = size.width * size.height * 3; // frame size in bytes + int videoSizeMb = + frameSizeB * size.frame_count / (1024 * 1024); // video size in MB + + return systemStats.query_sufficient_memory(videoSizeMb); +} + +std::string Video::get_video_format(char *video_id) { + std::string format = ""; + if (std::strcmp(video_id, "") == 0) { + std::string delimiter = "."; + char *p = std::strtok(video_id, delimiter.data()); + while (p != NULL) { + p = std::strtok(NULL, delimiter.data()); + if (p != NULL) { + format.assign(p, std::strlen(p)); + } + } + } else { + format = "mp4"; + } + + return format; +} + +void Video::set_video_writer_size(int op_count) { + for (int j = op_count; j < _operations.size(); j++) { + auto it = std::next(_operations.begin(), j); + std::shared_ptr op = *it; + + if ((*op).get_type() == VCL::Video::OperationType::RESIZE || + (*op).get_type() == VCL::Video::OperationType::CROP) { + cv::Size r_size = (*op).get_video_size(); + _size.width = r_size.width; + _size.height = r_size.height; + } else if ((*op).get_type() == VCL::Video::OperationType::INTERVAL || + (*op).get_type() == + VCL::Video::OperationType::SYNCREMOTEOPERATION || + (*op).get_type() == VCL::Video::OperationType::USEROPERATION || + (*op).get_type() == VCL::Video::OperationType::REMOTEOPERATION) { + break; + } + } +} + +void Video::store_video_no_operation(std::string id, std::string store_id, + std::string fname) { + cv::VideoCapture inputVideo(id); + + _fps = static_cast(inputVideo.get(cv::CAP_PROP_FPS)); + _size.frame_count = + static_cast(inputVideo.get(cv::CAP_PROP_FRAME_COUNT)); + _size.width = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_WIDTH)); + _size.height = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_HEIGHT)); + int fourcc = static_cast(inputVideo.get(cv::CAP_PROP_FOURCC)); + + if (_codec != NOCODEC) { + fourcc = get_video_fourcc(_codec); + } + + cv::VideoWriter outputVideo(fname, fourcc, _fps, + cv::Size(_size.width, _size.height)); + + // check sufficient memory + bool memory_avail = check_sufficient_memory(_size); + if (!memory_avail) { + throw VCLException(UnsupportedOperation, + "System out of memory, please retry later"); + } + + int fcount = 0; + while (true) { + fcount++; + cv::Mat mat_frame; + inputVideo >> mat_frame; + + if (mat_frame.empty()) { + break; } - if (_operations.size() == 1) { - // If only read operation exists, we should add another operation to - // avoid the useless loop. - _operations.push_back( - std::make_shared(this, Video::FRAMES, 0, 0, 1)); + outputVideo << mat_frame; + mat_frame.release(); + } + inputVideo.release(); + outputVideo.release(); + + if (std::remove(_video_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + _video_id = store_id; + if (std::rename(fname.data(), _video_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while renaming the file."); + } +} + +int Video::perform_single_frame_operations(std::string id, int op_count, + std::string fname) { + cv::VideoCapture inputVideo(id); + + _fps = static_cast(inputVideo.get(cv::CAP_PROP_FPS)); + _size.frame_count = + static_cast(inputVideo.get(cv::CAP_PROP_FRAME_COUNT)); + _size.width = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_WIDTH)); + _size.height = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_HEIGHT)); + int fourcc = static_cast(inputVideo.get(cv::CAP_PROP_FOURCC)); + + // Check if Crop or Resize operations are in the pipeline + // to set the height and width of the VideoWriter object + set_video_writer_size(op_count); + + // check sufficient memory + bool memory_avail = check_sufficient_memory(_size); + if (!memory_avail) { + throw VCLException(UnsupportedOperation, + "System out of memory, please retry later"); + } + + cv::VideoWriter outputVideo(fname, fourcc, _fps, + cv::Size(_size.width, _size.height)); + int i = 0; + while (true) { + cv::Mat mat_frame; + inputVideo >> mat_frame; // Read frame + + if (mat_frame.empty()) { + op_count = i; + break; } - for (const auto &op : _operations) { - if (op == NULL) - throw VCLException(ObjectEmpty, "Nothing to be done"); + // Perform operations frame by frame except the ones + // that work with the complete video + for (i = op_count; i < _operations.size(); i++) { + auto it = std::next(_operations.begin(), i); + std::shared_ptr op = *it; + + if ((*op).get_type() != VCL::Video::OperationType::SYNCREMOTEOPERATION && + (*op).get_type() != VCL::Video::OperationType::INTERVAL && + (*op).get_type() != VCL::Video::OperationType::USEROPERATION && + (*op).get_type() != VCL::Video::OperationType::REMOTEOPERATION) { + + (*op)(this, mat_frame); + if (i == _operations.size() - 1) { + outputVideo << mat_frame; + } + } else { + outputVideo << mat_frame; + break; + } } + mat_frame.release(); + } - Video::OperationResult res = PASS; - for (int index = 0; res != BREAK; index++) { - for (const auto &op : _operations) { - res = (*op)(index); - if (res != PASS) - break; + outputVideo.release(); + inputVideo.release(); + + return op_count; +} + +void Video::perform_operations(bool is_store, std::string store_id) { + try { + int op_count = 0; + std::string v_id = _video_id; + std::string s_id = store_id; + + // Get the video container format. + char *s; + if (is_store) { + s = const_cast(s_id.data()); + } else { + s = const_cast(v_id.data()); + } + std::string format = get_video_format(s); + + // Setup temporary files + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + std::string fname = + "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; + std::string id = + (_operated_video_id == "") ? _video_id : _operated_video_id; + + // Check for existence of the source video file + try { + std::ifstream file; + file.open(id); + if (file) { + file.close(); + } else { + throw VCLException(OpenFailed, "video_id could not be opened"); } + } catch (Exception e) { + throw VCLException(OpenFailed, "video_id could not be opened"); } - for (const auto &op : _operations) { - op->finalize(); + if (_operations.size() == 0) { + // If the call is made with not operations. + if (is_store) { + // If called to store a video into the data store + store_video_no_operation(id, store_id, fname); + } else { + _operated_video_id = _video_id; + } + } else { + // If the call is made with operations. + while (op_count < _operations.size()) { + time_now = std::chrono::system_clock::now(); + utc_time = time_now.time_since_epoch(); + fname = + "/tmp/tempfile" + std::to_string(utc_time.count()) + "." + format; + + op_count = perform_single_frame_operations(id, op_count, fname); + + // Perform the operations that run on the complete video + // Note: Async Remote Operation is performed by the event loop + // in the VideoLoop class. + if (op_count < _operations.size()) { + cv::Mat mat; + auto it = std::next(_operations.begin(), op_count); + std::shared_ptr op = *it; + if ((*op).get_type() != + VCL::Video::OperationType::SYNCREMOTEOPERATION) { + (*op)(this, mat, fname); + } else if ((*op).get_type() != VCL::Video::OperationType::INTERVAL) { + (*op)(this, mat, fname); + } else if ((*op).get_type() != + VCL::Video::OperationType::USEROPERATION) { + (*op)(this, mat, fname); + } + op_count++; + id = fname; + } + } + if (is_store) { + if (std::remove(_video_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + if (std::rename(fname.data(), store_id.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while renaming the file."); + } + } else { + _operated_video_id = fname; + } } - // FIXME Do we need to clear _operations when some exception happened? - // Right now, we assume that we should have another try and hence the - // vector _operations should be kept. } catch (cv::Exception &e) { throw VCLException(OpenCVError, e.what()); } @@ -315,17 +637,77 @@ void Video::perform_operations() { _operations.clear(); } +int Video::execute_operations(bool isRemote) { + if (isRemote) { + // Setup the remote operation to be run by the eventloop + auto it = std::next(_operations.begin(), 0); + std::shared_ptr op = *it; + cv::Mat mat; + std::string fname = + (_operated_video_id == "") ? _video_id : _operated_video_id; + if ((*op).get_type() == VCL::Video::OperationType::REMOTEOPERATION) { + try { + (*op)(this, mat, fname); + _operations.pop_front(); + if (_query_error_response != NOERRORSTRING) { + return -1; + } + return 0; + } catch (const std::exception &e) { + _query_error_response = + "Undefined exception occured while running remote operation"; + return -1; + } + } else { + _query_error_response = "Bad operation sent."; + return -1; + } + } else { + // Perform the operations till a remote operation is encountered. + // The _operations list is updated accordingly + try { + std::list> curr_operations; + std::list> rem_operations; + bool op_flag = false; + for (auto op : _operations) { + if ((*op).get_type() == VCL::Video::OperationType::REMOTEOPERATION) { + op_flag = true; + } + if (op_flag) { + rem_operations.push_back(op); + } else { + curr_operations.push_back(op); + } + } + std::swap(_operations, curr_operations); + if (_operations.size() > 0) { + perform_operations(); + } + if (_query_error_response != NOERRORSTRING) { + return -1; + } + std::swap(_operations, rem_operations); + return 0; + } catch (Exception e) { + _query_error_response = e.msg; + return -1; + } + } +} + void Video::swap(Video &rhs) noexcept { using std::swap; swap(_video_id, rhs._video_id); swap(_flag_stored, rhs._flag_stored); - // swap(_frames, rhs._frames); swap(_size, rhs._size); swap(_fps, rhs._fps); swap(_codec, rhs._codec); swap(_operations, rhs._operations); - swap(_video_read, rhs._video_read); +} + +void Video::set_query_error_response(std::string response_error) { + _query_error_response = response_error; } void Video::set_connection(RemoteConnection *remote) { @@ -346,30 +728,38 @@ void Video::set_connection(RemoteConnection *remote) { void Video::resize(int width, int height) { _flag_stored = false; - _operations.push_back( - std::make_shared(this, cv::Size(width, height))); + _operations.push_back(std::make_shared(cv::Size(width, height))); } void Video::interval(Video::Unit u, int start, int stop, int step) { _flag_stored = false; - _operations.push_back(std::make_shared(this, u, start, stop, step)); + _operations.push_back(std::make_shared(u, start, stop, step)); } void Video::crop(const Rectangle &rect) { _flag_stored = false; - _operations.push_back(std::make_shared(this, rect)); + _operations.push_back(std::make_shared(rect)); } void Video::threshold(int value) { _flag_stored = false; - _operations.push_back(std::make_shared(this, value)); + _operations.push_back(std::make_shared(value)); +} + +void Video::syncremoteOperation(std::string url, Json::Value options) { + _operations.push_back(std::make_shared(url, options)); +} + +void Video::remoteOperation(std::string url, Json::Value options) { + _operations.push_back(std::make_shared(url, options)); +} + +void Video::userOperation(Json::Value options) { + _operations.push_back(std::make_shared(options)); } void Video::store(const std::string &video_id, Video::Codec video_codec) { - // out_name cannot be assigned to _video_id here as the read operation - // may be pending and the input file name is needed for the read. - _operations.push_back(std::make_shared(this, video_id, video_codec)); - perform_operations(); + perform_operations(true, video_id); } void Video::store() { @@ -387,277 +777,379 @@ void Video::delete_video() { } /* *********************** */ -/* READ OPERATION */ +/* RESIZE OPERATION */ /* *********************** */ -Video::Read::~Read() { - if (_inputVideo.isOpened()) { - _inputVideo.release(); - _frames.clear(); - _frame_index_starting = 0; - _frame_index_ending = 0; - _video_id = ""; +void Video::Resize::operator()(Video *video, cv::Mat &frame, std::string args) { + try { + cv::resize(frame, frame, cv::Size(_size.width, _size.height), + cv::INTER_LINEAR); + + video->_size.width = _size.width; + video->_size.height = _size.height; + } catch (VCL::Exception e) { + video->set_query_error_response(e.msg); + print_exception(e); + return; } } -void Video::Read::finalize() { reset(); } +/* *********************** */ +/* CROP OPERATION */ +/* *********************** */ -void Video::Read::open() { - _video_id = _video->_video_id; - if (!_inputVideo.open(_video_id)) { - throw VCLException(OpenFailed, "Could not open the output video for read"); +void Video::Crop::operator()(Video *video, cv::Mat &frame, std::string args) { + try { + frame = frame(_rect); + + video->_size.width = _rect.width; + video->_size.height = _rect.height; + } catch (VCL::Exception e) { + video->set_query_error_response(e.msg); + print_exception(e); + return; } - - _video->_fps = static_cast(_inputVideo.get(cv::CAP_PROP_FPS)); - _video->_size.frame_count = - static_cast(_inputVideo.get(cv::CAP_PROP_FRAME_COUNT)); - _video->_size.width = - static_cast(_inputVideo.get(cv::CAP_PROP_FRAME_WIDTH)); - _video->_size.height = - static_cast(_inputVideo.get(cv::CAP_PROP_FRAME_HEIGHT)); - - // Get Codec Type- Int form - int ex = static_cast(_inputVideo.get(cv::CAP_PROP_FOURCC)); - char fourcc[] = {(char)((ex & 0XFF)), (char)((ex & 0XFF00) >> 8), - (char)((ex & 0XFF0000) >> 16), - (char)((ex & 0XFF000000) >> 24), 0}; - - _video->_codec = read_codec(fourcc); - - _video->_video_read = shared_from_this(); } -void Video::Read::reset() { - if (_inputVideo.isOpened()) { - _inputVideo.release(); - _frames.clear(); - _frame_index_starting = 0; - _frame_index_ending = 0; - _video_id = ""; +/* *********************** */ +/* THRESHOLD OPERATION */ +/* *********************** */ - if (_video->_video_read == shared_from_this()) { - _video->_video_read = nullptr; - } +void Video::Threshold::operator()(Video *video, cv::Mat &frame, + std::string args) { + try { + cv::threshold(frame, frame, _threshold, _threshold, cv::THRESH_TOZERO); + } catch (VCL::Exception e) { + video->set_query_error_response(e.msg); + print_exception(e); + return; } } -void Video::Read::reopen() { - reset(); - open(); -} - -VCL::Image *Video::Read::read_frame(int index) { - cv::Mat mat; +/* *********************** */ +/* INTERVAL Operation */ +/* *********************** */ - if (!_inputVideo.isOpened()) { - open(); - } +void Video::Interval::operator()(Video *video, cv::Mat &frame, + std::string args) { + try { + int nframes = video->get_frame_count(false); + + if (_start >= nframes) + throw VCLException(SizeMismatch, + "Start Frame cannot be greater than number of frames"); + + if (_stop >= nframes) + throw VCLException(SizeMismatch, + "End Frame cannot be greater than number of frames"); + + std::string fname = args; + char *s = const_cast(args.data()); + std::string format = ""; + if (fname != "") { + std::string delimiter = "."; + char *p = std::strtok(s, delimiter.data()); + while (p != NULL) { + p = std::strtok(NULL, delimiter.data()); + if (p != NULL) { + format.assign(p, std::strlen(p)); + } + } + } else { + throw VCLException(ObjectNotFound, "Video file not available"); + } + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + std::string tmp_fname = "/tmp/tempfile_interval" + + std::to_string(utc_time.count()) + "." + format; + + cv::VideoCapture inputVideo(fname); + + video->_fps /= _step; + video->_size.frame_count = + static_cast(inputVideo.get(cv::CAP_PROP_FRAME_COUNT)); + video->_size.width = + static_cast(inputVideo.get(cv::CAP_PROP_FRAME_WIDTH)); + video->_size.height = + static_cast(inputVideo.get(cv::CAP_PROP_FRAME_HEIGHT)); + int fourcc = static_cast(inputVideo.get(cv::CAP_PROP_FOURCC)); + + // check sufficient memory + bool memory_avail = video->check_sufficient_memory(video->_size); + if (!memory_avail) { + throw VCLException(UnsupportedOperation, + "System out of memory, please retry later"); + } + cv::VideoWriter outputVideo( + tmp_fname, fourcc, video->_fps, + cv::Size(video->_size.width, video->_size.height)); + + int frame_number = 0; + int last_frame_written = 0; + while (true) { + cv::Mat mat_frame; + inputVideo >> mat_frame; // Read frame + frame_number++; + + if (mat_frame.empty()) + break; + + if (frame_number >= _start && frame_number < _stop) { + if (last_frame_written == 0) { + outputVideo << mat_frame; + last_frame_written = frame_number; + } else { + if ((frame_number - last_frame_written) == _step) { + outputVideo << mat_frame; + last_frame_written = frame_number; + } + } + } - if (index < _frame_index_starting) { // Read the video file all over again - reopen(); // _frame_index_ending = 0; - _frame_index_starting = index; - } else if (index > _frame_index_starting + 30) { // The cached vector is full - _frames.clear(); - _frame_index_starting = index; - } + if (frame_number > _stop) { + break; + } + } - // Skip the frames that are too "old" - while (_frame_index_ending < _frame_index_starting) { - _inputVideo >> mat; - if (mat.empty()) - return nullptr; - _frame_index_ending++; - } + outputVideo.release(); + inputVideo.release(); - // Read the frames with indices up to - while (_frame_index_ending <= index) { - _inputVideo >> mat; - if (mat.empty()) - return nullptr; - _frames.push_back(VCL::Image(mat, false)); - _frame_index_ending++; + if (std::remove(fname.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + if (std::rename(tmp_fname.data(), fname.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while renaming the file."); + } + } catch (VCL::Exception e) { + video->set_query_error_response(e.msg); + print_exception(e); + return; } - - return &_frames[index - _frame_index_starting]; } -Video::Codec Video::Read::read_codec(char *fourcc) { - std::string codec(fourcc); - std::transform(codec.begin(), codec.end(), codec.begin(), ::tolower); +/* *********************** */ +/* SYNCREMOTE OPERATION */ +/* *********************** */ - if (codec == "mjpg") - return Codec::MJPG; - else if (codec == "xvid") - return Codec::XVID; - else if (codec == "u263") - return Codec::H263; - else if (codec == "avc1" || codec == "x264") - return Codec::H264; - else - throw VCLException(UnsupportedFormat, codec + " is not supported"); +// Reads the file sent from the remote server and saves locally +static size_t videoCallback(void *ptr, size_t size, size_t nmemb, + void *stream) { + size_t written = fwrite(ptr, size, nmemb, (FILE *)stream); + return written; } -Video::OperationResult Video::Read::operator()(int index) { - // The video object is changed, reset the InputCapture handler. - if (_video_id != _video->_video_id) { - _video_id = _video->_video_id; - reset(); - } - - if (!_inputVideo.isOpened()) { - open(); +void Video::SyncRemoteOperation::operator()(Video *video, cv::Mat &frame, + std::string args) { + try { + int frame_count = video->get_frame_count(false); + if (frame_count > 0) { + std::string fname = args; + + CURL *curl = NULL; + + CURLcode res; + struct curl_slist *headers = NULL; + curl_mime *form = NULL; + curl_mimepart *field = NULL; + + curl = curl_easy_init(); + + if (curl) { + + form = curl_mime_init(curl); + + field = curl_mime_addpart(form); + curl_mime_name(field, "videoData"); + if (curl_mime_filedata(field, fname.data()) != CURLE_OK) { + throw VCLException(ObjectEmpty, + "Unable to retrieve local file for remoting"); + } + + field = curl_mime_addpart(form); + curl_mime_name(field, "jsonData"); + if (curl_mime_data(field, _options.toStyledString().data(), + _options.toStyledString().length()) != CURLE_OK) { + throw VCLException( + ObjectEmpty, "Unable to create curl mime data for client params"); + } + + // Post data + std::string format = ""; + char *s = const_cast(args.data()); + if (fname != "") { + std::string delimiter = "."; + char *p = std::strtok(s, delimiter.data()); + while (p != NULL) { + p = std::strtok(NULL, delimiter.data()); + if (p != NULL) { + format.assign(p, std::strlen(p)); + } + } + } else { + throw VCLException(ObjectNotFound, "Video file not available"); + } + + auto time_now = std::chrono::system_clock::now(); + std::chrono::duration utc_time = time_now.time_since_epoch(); + std::string response_filepath = + "/tmp/rtempfile" + std::to_string(utc_time.count()) + "." + format; + FILE *response_file = fopen(response_filepath.data(), "wb"); + + if (curl_easy_setopt(curl, CURLOPT_URL, _url.data()) != CURLE_OK) { + throw VCLException(UndefinedException, "CURL setup error with URL"); + } + if (curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, videoCallback) != + CURLE_OK) { + throw VCLException(UndefinedException, + "CURL setup error with callback"); + } + + if (response_file) { + if (curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_file) != + CURLE_OK) { + throw VCLException(UndefinedException, + "CURL setup error callback response file"); + } + if (curl_easy_setopt(curl, CURLOPT_MIMEPOST, form) != CURLE_OK) { + throw VCLException(UndefinedException, + "CURL setup error with form"); + } + curl_easy_perform(curl); + fclose(response_file); + } + + int http_status_code; + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status_code); + + curl_easy_cleanup(curl); + curl_mime_free(form); + + // Throw exceptions for different error codes received from the remote + // server + if (http_status_code != 200) { + if (http_status_code == 0) { + throw VCLException(ObjectEmpty, "Remote server is not running."); + } + if (http_status_code == 400) { + throw VCLException(ObjectEmpty, + "Invalid Request to the Remote Server."); + } else if (http_status_code == 404) { + throw VCLException(ObjectEmpty, + "Invalid URL Request. Please check the URL."); + } else if (http_status_code == 500) { + throw VCLException(ObjectEmpty, + "Exception occurred at the remote server. " + "Please check your query."); + } else if (http_status_code == 503) { + throw VCLException(ObjectEmpty, "Unable to reach remote server"); + } else { + throw VCLException(ObjectEmpty, "Remote Server error."); + } + } + + if (std::remove(fname.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + if (std::rename(response_filepath.data(), fname.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while renaming the file."); + } + } + } else + throw VCLException(ObjectEmpty, "Video object is empty"); + } catch (VCL::Exception e) { + video->set_query_error_response(e.msg); + print_exception(e); + return; } - if (_video->_size.frame_count <= index) - return BREAK; - return PASS; } /* *********************** */ -/* WRITE OPERATION */ +/* REMOTE OPERATION */ /* *********************** */ - -int Video::Write::get_fourcc() { - switch (_codec) { - case Codec::MJPG: - return cv::VideoWriter::fourcc('M', 'J', 'P', 'G'); - case Codec::XVID: - return cv::VideoWriter::fourcc('X', 'V', 'I', 'D'); - case Codec::H263: - return cv::VideoWriter::fourcc('U', '2', '6', '3'); - case Codec::H264: - return cv::VideoWriter::fourcc('X', '2', '6', '4'); - case Codec::AVC1: - return cv::VideoWriter::fourcc('A', 'V', 'C', '1'); - default: - throw VCLException(UnsupportedFormat, - std::to_string((int)_codec) + " is not a valid format"); - } -} - -Video::OperationResult Video::Write::operator()(int index) { - VCL::Image *frame = _video->read_frame(index); - if (frame == NULL) - return BREAK; - - if (_last_write == index) - return PASS; - else if (_last_write > index) { - // Write the video file all over again. - // Probably some exceptions happened before. - _outputVideo.release(); - _last_write = -1; - } - - if (!_outputVideo.isOpened()) { - _outputVideo.open(_outname, get_fourcc(), _video->_fps, - cv::Size(_video->_size.width, _video->_size.height)); - - if (!_outputVideo.isOpened()) { - throw VCLException(OpenFailed, - "Could not open the output video for write"); +void Video::RemoteOperation::operator()(Video *video, cv::Mat &frame, + std::string args) { + try { + video->set_remoteOp_params(_options, _url); + if (video->get_operated_video_id() == "") { + video->set_operated_video_id(video->get_video_id()); } + } catch (VCL::Exception e) { + video->set_query_error_response(e.msg); + print_exception(e); + return; } - - _outputVideo << frame->get_cvmat(false); - _frame_count++; - _last_write = index; - return PASS; } -void Video::Write::finalize() { - if (_video->_storage == Storage::LOCAL) { - if (!_outputVideo.isOpened()) { - _outputVideo.release(); +/* ************************* */ +/* USER DEFINED OPERATION */ +/* ************************* */ +void Video::UserOperation::operator()(Video *video, cv::Mat &frame, + std::string args) { + try { + int frame_count = video->get_frame_count(false); + if (frame_count > 0) { - _video->_video_id = _outname; - _video->_codec = _codec; - _video->_flag_stored = true; - _video->_size.frame_count = _frame_count; - } - } -} + std::string fname = args; + std::string opfile; -Video::Write::~Write() { finalize(); } + zmq::context_t context(1); + zmq::socket_t socket(context, zmq::socket_type::req); -/* *********************** */ -/* RESIZE OPERATION */ -/* *********************** */ + std::string port = _options["port"].asString(); + std::string address = "tcp://127.0.0.1:" + port; -Video::OperationResult Video::Resize::operator()(int index) { - VCL::Image *frame = _video->read_frame(index); - if (frame == NULL) - return BREAK; - // VCL::Image expect the params (h,w) (contrary to openCV convention) - frame->resize(_size.height, _size.width); - _video->_size.width = _size.width; - _video->_size.height = _size.height; - return PASS; -} + socket.connect(address.data()); -/* *********************** */ -/* CROP OPERATION */ -/* *********************** */ + _options["ipfile"] = fname; -Video::OperationResult Video::Crop::operator()(int index) { - VCL::Image *frame = _video->read_frame(index); - if (frame == NULL) - return BREAK; - frame->crop(_rect); - _video->_size.width = _rect.width; - _video->_size.height = _rect.height; - return PASS; -} + std::string message_to_send = _options.toStyledString(); -/* *********************** */ -/* THRESHOLD OPERATION */ -/* *********************** */ + int message_len = message_to_send.length(); + zmq::message_t ipfile(message_len); + memcpy(ipfile.data(), message_to_send.data(), message_len); -Video::OperationResult Video::Threshold::operator()(int index) { - VCL::Image *frame = _video->read_frame(index); - if (frame == NULL) - return BREAK; - frame->threshold(_threshold); - return PASS; -} + socket.send(ipfile, 0); -/* *********************** */ -/* INTERVAL Operation */ -/* *********************** */ + // Wait for a response from the UDF process + while (true) { + char buffer[256]; + int size = socket.recv(buffer, 255, 0); -Video::OperationResult Video::Interval::operator()(int index) { - if (_u != Video::Unit::FRAMES) { - _fps_updated = false; - throw VCLException(UnsupportedOperation, - "Only Unit::FRAMES supported for interval operation"); - } + buffer[size] = '\0'; + opfile = buffer; - unsigned nframes = _video->_size.frame_count; + break; + } - if (_start >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "Start Frame cannot be greater than number of frames"); - } + std::ifstream rfile; + rfile.open(opfile); - if (_stop >= nframes) { - _fps_updated = false; - throw VCLException(SizeMismatch, - "End Frame cannot be greater than number of frames"); - } + if (rfile) { + rfile.close(); + } else { + if (std::remove(opfile.data()) != 0) { + } + throw VCLException(OpenFailed, "UDF Error"); + } - if (index < _start) - return CONTINUE; - if (index >= _stop) - return BREAK; - if ((index - _start) % _step != 0) - return CONTINUE; - update_fps(); - return PASS; -} + if (std::remove(fname.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while removing the file."); + } + if (std::rename(opfile.data(), fname.data()) != 0) { + throw VCLException(ObjectEmpty, + "Error encountered while renaming the file."); + } -void Video::Interval::update_fps() { - if (!_fps_updated) { - _video->_fps /= _step; - _fps_updated = true; + } else + throw VCLException(ObjectEmpty, "Image object is empty"); + } catch (VCL::Exception e) { + video->set_query_error_response(e.msg); + print_exception(e); + return; } } diff --git a/src/vdms.cc b/src/vdms.cc index 4692c159..e2056eaa 100644 --- a/src/vdms.cc +++ b/src/vdms.cc @@ -98,22 +98,38 @@ int main(int argc, char **argv) { } } - printf("Server will start processing requests... \n"); VDMS::Server server(config_file); + // Note: current default is PMGD + std::string qhandler_type; + qhandler_type = server.cfg->get_string_value("query_handler", "pmgd"); + // create a thread for processing request and a thread for the autodelete // timer request_thread_flag = pthread_create(&request_thread, NULL, start_request_thread, (void *)(&server)); - autodelete_thread_flag = pthread_create( - &autodelete_thread, NULL, start_autodelete_thread, (void *)(&server)); - auto_replcation_flag = - pthread_create(&auto_replicate_thread, NULL, start_replication_thread, - (void *)(&server)); + printf( + "Server instantiation complete, will start processing requests... \n"); + + // Kick off threads only if PMGD handler is used as its the only one with + // PMGD this functionality at the moment. May need refactor as more handlers + // are added. + if (qhandler_type == "pmgd") { + autodelete_thread_flag = pthread_create( + &autodelete_thread, NULL, start_autodelete_thread, (void *)(&server)); + auto_replcation_flag = + pthread_create(&auto_replicate_thread, NULL, start_replication_thread, + (void *)(&server)); + } + + // Only start threads if this is a PMGD handler as its logic is specific to it + // In the future we probably want a cleaner solution here pthread_join(request_thread, NULL); - pthread_join(autodelete_thread, NULL); - pthread_join(auto_replicate_thread, NULL); + if (qhandler_type == "pmgd") { + pthread_join(autodelete_thread, NULL); + pthread_join(auto_replicate_thread, NULL); + } printf("Server shutting down... \n"); diff --git a/tests/cleandbs.sh b/tests/cleandbs.sh index 99fea4e9..b8f1b227 100755 --- a/tests/cleandbs.sh +++ b/tests/cleandbs.sh @@ -1,6 +1,6 @@ rm -r jsongraph qhgraph simpleAdd_db simpleAddx10_db simpleUpdate_db rm -r entitycheck_db datatypecheck_db db_backup test_db_1 -rm -r tests_log.log tests_screen.log +rm tests_log.log tests_screen.log tests_remote_screen.log tests_remote_log.log tests_udf_screen.log tests_udf_log.log rm -r tdb rm -r db dbs test_db_client @@ -10,4 +10,5 @@ rm -r vdms rm test_images/tdb_to_jpg.jpg rm test_images/tdb_to_png.png rm test_images/test_image.jpg +rm remote_function_test/tmpfile* rm -r backups \ No newline at end of file diff --git a/tests/csv_samples/Descriptor.csv b/tests/csv_samples/Descriptor.csv index 2ef43646..025ca8ed 100644 --- a/tests/csv_samples/Descriptor.csv +++ b/tests/csv_samples/Descriptor.csv @@ -1,6 +1,6 @@ -DescriptorClass,label,prop_age,prop_gender,inputdata -Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt -Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt -Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt -Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt -Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt +DescriptorClass,label,prop_age,prop_gender,inputdata +Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt +Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt +Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt +Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt +Test_14096,Rocky,34,M,../tests/csv_samples/blob_1.txt diff --git a/tests/csv_samples/DescriptorSet.csv b/tests/csv_samples/DescriptorSet.csv index cf9a3f5b..8222799d 100644 --- a/tests/csv_samples/DescriptorSet.csv +++ b/tests/csv_samples/DescriptorSet.csv @@ -1,7 +1,7 @@ -DescriptorType,dimensions,distancemetric,searchengine -Test1024,1024,L2,FaissFlat -Test_14096,1024,L2,FaissFlat -Test1000,1000,L2,FaissFlat -Test100,100,L2,FaissFlat -Test128,128,IP,FaissIVFFlat -Test512,512,L2,TileDBDense +DescriptorType,dimensions,distancemetric,searchengine +Test1024,1024,L2,FaissFlat +Test_14096,1024,L2,FaissFlat +Test1000,1000,L2,FaissFlat +Test100,100,L2,FaissFlat +Test128,128,IP,FaissIVFFlat +Test512,512,L2,TileDBDense diff --git a/tests/csv_samples/Image.csv b/tests/csv_samples/Image.csv index 37723900..bd3c38b4 100644 --- a/tests/csv_samples/Image.csv +++ b/tests/csv_samples/Image.csv @@ -1,11 +1,11 @@ -ImagePath,ops_threshold,ops_crop,ops_resize,ops_flip,ops_rotate,prop_type,prop_part,format,cons_1 -../tests/test_images/large1.jpg,350,,,,,,image1,jpg,part==image1 -../tests/test_images/large1.jpg,350,"0,0,224,224",,,,,image2,jpg, -../tests/test_images/large1.jpg,350,,"224,224",,,,image3,jpg, -../tests/test_images/large1.jpg,350,,,1,,,image4,jpg, -../tests/test_images/large1.jpg,350,,,,"45,false",,image5,jpg, -../tests/test_images/large1.jpg,350,,,,"75.5,false",,image6,jpg, -../tests/test_images/large1.jpg,350,,,,,,image7,png, -../tests/test_images/large1.jpg,,,,,,,image8,bin, -../tests/test_images/large1.jpg,350,,,,,,image9,png, -../tests/test_images/large1.jpg,,,,,,,image10,bin, +ImagePath,ops_threshold,ops_crop,ops_resize,ops_flip,ops_rotate,prop_type,prop_part,format,cons_1 +../tests/test_images/large1.jpg,350,,,,,,image1,jpg,part==image1 +../tests/test_images/large1.jpg,350,"0,0,224,224",,,,,image2,jpg, +../tests/test_images/large1.jpg,350,,"224,224",,,,image3,jpg, +../tests/test_images/large1.jpg,350,,,1,,,image4,jpg, +../tests/test_images/large1.jpg,350,,,,"45,false",,image5,jpg, +../tests/test_images/large1.jpg,350,,,,"75.5,false",,image6,jpg, +../tests/test_images/large1.jpg,350,,,,,,image7,png, +../tests/test_images/large1.jpg,,,,,,,image8,bin, +../tests/test_images/large1.jpg,350,,,,,,image9,png, +../tests/test_images/large1.jpg,,,,,,,image10,bin, diff --git a/tests/csv_samples/Rectangle.csv b/tests/csv_samples/Rectangle.csv index 91236f69..b626f861 100644 --- a/tests/csv_samples/Rectangle.csv +++ b/tests/csv_samples/Rectangle.csv @@ -1,13 +1,13 @@ -RectangleBound,prop_name,cons_1 -"1,2,3,4",2,part==image1 -"1,2,3,4",2, -"1,2,3,4",2, -"1,2,3,4",2, -"1,2,3,4",2, -"1,2,3,4",2, -"1,2,3,4",2, -"1,2,3,4",2, -"1,2,3,4",2, -"1,2,3,4",2, -"1,2,3,4",2, +RectangleBound,prop_name,cons_1 +"1,2,3,4",2,part==image1 +"1,2,3,4",2, +"1,2,3,4",2, +"1,2,3,4",2, +"1,2,3,4",2, +"1,2,3,4",2, +"1,2,3,4",2, +"1,2,3,4",2, +"1,2,3,4",2, +"1,2,3,4",2, +"1,2,3,4",2, "1,2,3,4",2, \ No newline at end of file diff --git a/tests/csv_samples/Video.csv b/tests/csv_samples/Video.csv index a9a8f4f4..d07a0062 100644 --- a/tests/csv_samples/Video.csv +++ b/tests/csv_samples/Video.csv @@ -1,6 +1,6 @@ -VideoPath,format,compressto,prop_name,ops_resize,ops_interval -../tests/test_videos/Megamind.avi,avi,h264,Good,"200,175", -../tests/test_videos/Megamind.avi,avi,h264,Good,,"10,50,2" -../tests/test_videos/Megamind.avi,avi,h264,Good,, -../tests/test_videos/Megamind.avi,avi,h264,Good,, -../tests/test_videos/Megamind.avi,avi,h264,Good,, +VideoPath,format,compressto,prop_name,ops_resize,ops_interval +../tests/test_videos/Megamind.avi,avi,h264,Good,"200,175", +../tests/test_videos/Megamind.avi,avi,h264,Good,,"10,50,2" +../tests/test_videos/Megamind.avi,avi,h264,Good,, +../tests/test_videos/Megamind.avi,avi,h264,Good,, +../tests/test_videos/Megamind.avi,avi,h264,Good,, diff --git a/tests/csv_samples/connection.csv b/tests/csv_samples/connection.csv index 571d2210..59d44594 100644 --- a/tests/csv_samples/connection.csv +++ b/tests/csv_samples/connection.csv @@ -1,5 +1,5 @@ -ConnectionClass,Person@id,Person@id,prop_type -BloodRelation,1,2,brother -BloodRelation,14,16,sister -BloodRelation,14,15,mother +ConnectionClass,Person@id,Person@id,prop_type +BloodRelation,1,2,brother +BloodRelation,14,16,sister +BloodRelation,14,15,mother BloodRelation,14,13,father \ No newline at end of file diff --git a/tests/python/TestCommand.py b/tests/python/TestCommand.py index 1c9a4110..4127947b 100644 --- a/tests/python/TestCommand.py +++ b/tests/python/TestCommand.py @@ -36,6 +36,7 @@ def __init__(self, *args, **kwargs): # VDMS Server Info self.hostname = "localhost" self.port = 55565 + aws_port = 55564 db_up = False attempts = 0 @@ -47,10 +48,26 @@ def __init__(self, *args, **kwargs): db_up = True if attempts > 0: print("Connection to VDMS successful.") - except: - print("Attempt", attempts, "to connect to VDMS failed, retying...") - attempts += 1 - time.sleep(1) # sleeps 1 second + except Exception as e: + if e.strerror == "Connection refused": + try: + db = vdms.vdms() + db.connect(self.hostname, aws_port) + db.disconnect() + db_up = True + if attempts > 0: + print("Connection to VDMS successful.") + self.port = aws_port + except Exception as e: + print( + "Attempt", attempts, "to connect to VDMS failed, retying..." + ) + attempts += 1 + time.sleep(1) # sleeps 1 second + else: + print("Attempt", attempts, "to connect to VDMS failed, retying...") + attempts += 1 + time.sleep(1) # sleeps 1 second if attempts > 10: print("Failed to connect to VDMS after 10 attempts") diff --git a/tests/python/TestEngineDescriptors.py b/tests/python/TestEngineDescriptors.py index 15772ed3..b26e4b82 100644 --- a/tests/python/TestEngineDescriptors.py +++ b/tests/python/TestEngineDescriptors.py @@ -57,12 +57,16 @@ def test_addDifferentSets(self): self.addSet("128-IP-FaissIVFFlat", 128, "IP", "FaissIVFFlat") self.addSet("128-L2-TileDBDense", 128, "L2", "TileDBDense") self.addSet("128-L2-TileDBSparse", 128, "L2", "TileDBSparse") + self.addSet("128-L2-FLINNG", 128, "L2", "Flinng") + self.addSet("128-IP-FLINNG", 128, "IP", "Flinng") self.addSet("4075-L2-FaissFlat", 4075, "L2", "FaissFlat") self.addSet("4075-IP-FaissFlat", 4075, "IP", "FaissFlat") self.addSet("4075-L2-FaissIVFFlat", 4075, "L2", "FaissIVFFlat") self.addSet("4075-IP-FaissIVFFlat", 4075, "IP", "FaissIVFFlat") self.addSet("4075-L2-TileDBDense", 4075, "L2", "TileDBDense") + self.addSet("4075-L2-FLINNG", 4075, "L2", "Flinng") + self.addSet("4075-IP-FLINNG", 4075, "IP", "Flinng") def test_addDescriptorsx1000FaissIVFFlat(self): db = self.create_connection() diff --git a/tests/python/config-aws-tests.json b/tests/python/config-aws-tests.json index c0e48723..a623bdcb 100644 --- a/tests/python/config-aws-tests.json +++ b/tests/python/config-aws-tests.json @@ -3,8 +3,8 @@ // Sets database paths and other parameters { // Network - "port": 55565, - "db_root_path": "test_db", + "port": 55564, + "db_root_path": "test_db_aws", "storage_type": "aws", //local, aws, etc "bucket_name": "minio-bucket", "more-info": "github.com/IntelLabs/vdms" diff --git a/tests/remote_function_test/functions/caption.py b/tests/remote_function_test/functions/caption.py new file mode 100644 index 00000000..d086b1e1 --- /dev/null +++ b/tests/remote_function_test/functions/caption.py @@ -0,0 +1,33 @@ +import cv2 +import numpy as np +from datetime import datetime +from collections import deque +import skvideo.io +import imutils +import uuid + + +def run(ipfilename, format, options): + opfilename = "tmpfile" + uuid.uuid1().hex + "." + str(format) + print(opfilename) + vs = cv2.VideoCapture(ipfilename) + + video = skvideo.io.FFmpegWriter(opfilename, {"-pix_fmt": "bgr24"}) + print(options) + i = 0 + while True: + (grabbed, frame) = vs.read() + if not grabbed: + print("[INFO] no frame read from stream - exiting") + video.close() + # sys.exit(0) + break + + label = options["text"] + cv2.putText( + frame, label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2 + ) + + video.writeFrame(frame) + + return opfilename diff --git a/tests/remote_function_test/requirements.txt b/tests/remote_function_test/requirements.txt index 89b80f95..03c7d0e0 100644 --- a/tests/remote_function_test/requirements.txt +++ b/tests/remote_function_test/requirements.txt @@ -1,5 +1,5 @@ opencv-python==4.5.5.64 -flask -numpy -sk-video -imutils \ No newline at end of file +flask==2.3.3 +numpy==1.26.0 +sk-video==1.1.10 +imutils==0.5.4 \ No newline at end of file diff --git a/tests/remote_function_test/udf_server.py b/tests/remote_function_test/udf_server.py index 68d0006b..a476557f 100644 --- a/tests/remote_function_test/udf_server.py +++ b/tests/remote_function_test/udf_server.py @@ -8,6 +8,7 @@ from collections import defaultdict, deque import skvideo.io import imutils +import uuid for entry in os.scandir("functions"): if entry.is_file(): @@ -40,7 +41,7 @@ def image_api(): format = json_data["format"] if "format" in json_data else "jpg" - tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + tmpfile = "tmpfile" + uuid.uuid1().hex + "." + str(format) image_data.save(tmpfile) @@ -56,29 +57,26 @@ def image_api(): def video_api(): json_data = json.loads(request.form["jsonData"]) video_data = request.files["videoData"] + format = json_data["format"] - format = json_data["format"] if "format" in json_data else "mp4" - - tmpfile = "tmpfile" + str(datetime.now()) + "." + str(format) + tmpfile = "tmpfile" + uuid.uuid1().hex + "." + str(format) video_data.save(tmpfile) - udf = globals()[json_data["format"]] - activity_tagged_file = udf.run(tmpfile, format, json_data) + udf = globals()[json_data["id"]] + response_file = udf.run(tmpfile, format, json_data) os.remove(tmpfile) @after_this_request def remove_tempfile(response): try: - os.remove(activity_tagged_file) + os.remove(response_file) except Exception as e: print("File cannot be deleted or not present") return response try: - return send_file( - activity_tagged_file, as_attachment=True, download_name=activity_tagged_file - ) + return send_file(response_file, as_attachment=True, download_name=response_file) except Exception as e: print(str(e)) return "Error in file read" diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 41933ae7..5520b073 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -14,12 +14,12 @@ pkill -9 -f udf_local.py || true # Start remote server for test cd remote_function_test python3 -m pip install -r requirements.txt -python3 udf_server.py 5010 > ../tests_screen.log 2> ../tests_log.log & +python3 udf_server.py 5010 > ../tests_remote_screen.log 2> ../tests_remote_log.log & # Start UDF message queue for test cd ../udf_test python3 -m pip install -r requirements.txt -python3 udf_local.py > ../tests_screen.log 2> ../tests_log.log & +python3 udf_local.py > ../tests_udf_screen.log 2> ../tests_udf_log.log & cd .. @@ -34,7 +34,7 @@ echo 'not the vdms application - this file is needed for shared key' > vdms echo 'Running C++ tests...' ./../build/tests/unit_tests \ - --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* + --gtest_filter=-ImageTest.CreateNameTDB:ImageTest.NoMetadata:VideoTest.CreateUnique:VideoTest.SyncRemoteWrite:VideoTest.UDFWrite:Descriptors_Add.add_1by1_and_search_1k:RemoteConnectionTest.* pkill -9 -f udf_server.py pkill -9 -f udf_local.py diff --git a/tests/server/QueryHandlerTester.h b/tests/server/QueryHandlerTester.h index 4311a1d0..bd5c261f 100644 --- a/tests/server/QueryHandlerTester.h +++ b/tests/server/QueryHandlerTester.h @@ -3,7 +3,7 @@ * * The MIT License * - * @copyright Copyright (c) 2017 Intel Corporation + * @copyright Copyright (c) 2023 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), @@ -28,14 +28,27 @@ */ #pragma once -#include "QueryHandler.h" +#include "QueryHandlerExample.h" +#include "QueryHandlerPMGD.h" namespace VDMS { -class QueryHandlerTester { - QueryHandler &_qh; +class QueryHandlerPMGDTester { + QueryHandlerPMGD &_qh; public: - QueryHandlerTester(QueryHandler &qh) : _qh(qh) {} + QueryHandlerPMGDTester(QueryHandlerPMGD &qh) : _qh(qh) {} + + void pq(protobufs::queryMessage &proto_query, + protobufs::queryMessage &response) { + _qh.process_query(proto_query, response); + } +}; + +class QueryHandlerExampleTester { + QueryHandlerExample &_qh; + +public: + QueryHandlerExampleTester(QueryHandlerExample &qh) : _qh(qh) {} void pq(protobufs::queryMessage &proto_query, protobufs::queryMessage &response) { diff --git a/tests/server/json_queries.cc b/tests/server/json_queries.cc index 8dc6733d..ce534a61 100644 --- a/tests/server/json_queries.cc +++ b/tests/server/json_queries.cc @@ -37,6 +37,8 @@ #include "gtest/gtest.h" #include +#include "QueryHandlerExample.h" +#include "QueryHandlerPMGD.h" #include "QueryHandlerTester.h" #include "VDMSConfig.h" #include "pmgd.h" @@ -61,13 +63,15 @@ std::string singleAddImage(" \ } \ } \ "); + TEST(AutoReplicate, default_replicate) { std::string path = "server/config-auto-replicate-tests.json"; std::cout << path << std::endl; VDMSConfig::init(path); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); + ReplicationConfig replication_test; replication_test.backup_path = "backups"; replication_test.db_path = "db_backup"; @@ -75,21 +79,52 @@ TEST(AutoReplicate, default_replicate) { replication_test.autoreplication_unit = "s"; replication_test.server_port = 55557; - QueryHandler qh_base; - qh_base.regualar_run_autoreplicate(replication_test); + QueryHandlerPMGD qh_base; + qh_base.regular_run_autoreplicate( + replication_test); // set flag to show autodelete queue has been + // initialized +} + +TEST(ExampleHandler, simplePing) { + + // query contents don't actually matter here, as the example handler ignores + // them as long as they're in a valid format + // so we're just gonna copy the add image query from above + std::string addImg; + addImg += "[" + singleAddImage + "]"; + + VDMSConfig::init("server/example_handler_test.json"); + PMGDQueryHandler::init(); + QueryHandlerExample::init(); + + QueryHandlerExample qh_base; + QueryHandlerExampleTester query_handler(qh_base); + + VDMS::protobufs::queryMessage proto_query; + proto_query.set_json(addImg); + + VDMS::protobufs::queryMessage response; + query_handler.pq(proto_query, response); + + Json::Reader json_reader; + Json::Value json_response; + json_reader.parse(response.json(), json_response); + + EXPECT_EQ(json_response[0]["HiThere"].asString(), "Hello, world!"); } + TEST(AddImage, simpleAdd) { std::string addImg; addImg += "[" + singleAddImage + "]"; VDMSConfig::init("server/config-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query; proto_query.set_json(addImg); @@ -141,12 +176,12 @@ TEST(UpdateEntity, simpleAddUpdate) { VDMSConfig::init("server/config-update-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query; proto_query.set_json(json_query); @@ -189,12 +224,12 @@ TEST(AddImage, simpleAddx10) { VDMSConfig::init("server/config-add10-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query; proto_query.set_json(string_query); @@ -298,12 +333,12 @@ TEST(QueryHandler, AddAndFind) { VDMSConfig::init("server/config-addfind-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query; proto_query.set_json(json_query); @@ -403,12 +438,12 @@ TEST(QueryHandler, EmptyResultCheck) { VDMSConfig::init("server/config-emptyresult-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query; proto_query.set_json(json_query); @@ -458,12 +493,12 @@ TEST(QueryHandler, DataTypeChecks) { VDMSConfig::init("server/config-datatype-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query; proto_query.set_json(json_query); @@ -535,12 +570,12 @@ TEST(QueryHandler, AutoDeleteNode) { VDMSConfig::init("server/config-datatype-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query_init; proto_query_init.set_json(json_query_init); @@ -559,7 +594,7 @@ TEST(QueryHandler, AutoDeleteNode) { qh_base.set_autodelete_init_flag(); qh_base.build_autodelete_queue(); // create priority queue of nodes with // _expiration property - qh_base.regualar_run_autodelete(); // delete nodes that have expired since + qh_base.regular_run_autodelete(); // delete nodes that have expired since // server previous closed qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized @@ -611,12 +646,12 @@ TEST(QueryHandler, CustomFunctionNoProcess) { VDMSConfig::init("server/config-datatype-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query; proto_query.set_json(json_query); proto_query.add_blobs(image); @@ -655,12 +690,12 @@ TEST(QueryHandler, AddUpdateFind_Blob) { VDMSConfig::init("unit_tests/config-tests.json"); PMGDQueryHandler::init(); - QueryHandler::init(); + QueryHandlerPMGD::init(); - QueryHandler qh_base; + QueryHandlerPMGD qh_base; qh_base.reset_autodelete_init_flag(); // set flag to show autodelete queue has // been initialized - QueryHandlerTester query_handler(qh_base); + QueryHandlerPMGDTester query_handler(qh_base); VDMS::protobufs::queryMessage proto_query; proto_query.set_json(json_query); diff --git a/tests/test_images/large1.jpg b/tests/test_images/large1.jpg old mode 100644 new mode 100755 diff --git a/tests/udf_test/functions/caption.py b/tests/udf_test/functions/caption.py new file mode 100644 index 00000000..c40f1ba4 --- /dev/null +++ b/tests/udf_test/functions/caption.py @@ -0,0 +1,36 @@ +import cv2 +import numpy as np +from datetime import datetime +from collections import deque +import skvideo.io +import imutils +import time + + +def run(settings, message, input_params): + ipfilename = message + format = message.strip().split(".")[-1] + + t1 = time.time() + opfilename = settings["opfile"] + str(t1) + "." + format + print(opfilename) + vs = cv2.VideoCapture(ipfilename) + + video = skvideo.io.FFmpegWriter(opfilename, {"-pix_fmt": "bgr24"}) + i = 0 + while True: + (grabbed, frame) = vs.read() + if not grabbed: + print("[INFO] no frame read from stream - exiting") + video.close() + # sys.exit(0) + break + + label = input_params["text"] + cv2.putText( + frame, label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2 + ) + + video.writeFrame(frame) + + return (time.time() - t1), opfilename diff --git a/tests/udf_test/requirements.txt b/tests/udf_test/requirements.txt index 5ce1a8b4..23c96db1 100644 --- a/tests/udf_test/requirements.txt +++ b/tests/udf_test/requirements.txt @@ -1,2 +1,2 @@ opencv-python==4.5.5.64 -zmq \ No newline at end of file +zmq==0.0.0 \ No newline at end of file diff --git a/tests/udf_test/settings.json b/tests/udf_test/settings.json index 2f7c4a3a..00766372 100644 --- a/tests/udf_test/settings.json +++ b/tests/udf_test/settings.json @@ -4,7 +4,6 @@ "functions" : { "facedetect" : "facedetect", "flip": "flip", - "carcount": "carcount", - "activityrecognition": "activityrecognition" + "caption": "caption" } } \ No newline at end of file diff --git a/tests/unit_tests/Image_test.cc b/tests/unit_tests/Image_test.cc index 779c5fba..f7b0b1c0 100644 --- a/tests/unit_tests/Image_test.cc +++ b/tests/unit_tests/Image_test.cc @@ -555,8 +555,9 @@ TEST_F(ImageTest, ResizeTDB) { TEST_F(ImageTest, CropMatThrow) { VCL::Image img(img_); img.crop(bad_rect_); - - ASSERT_THROW(img.get_cvmat(), VCL::Exception); + img.get_cvmat(); + ASSERT_STREQ(img.get_query_error_response().data(), + "Requested area is not within the image"); } TEST_F(ImageTest, CropMat) { @@ -706,8 +707,9 @@ TEST_F(ImageTest, RotateResize) { TEST_F(ImageTest, TDBMatThrow) { VCL::Image img(tdb_img_); img.crop(bad_rect_); - - ASSERT_THROW(img.get_cvmat(), VCL::Exception); + img.get_cvmat(); + ASSERT_STREQ(img.get_query_error_response().data(), + "Requested area is not within the image"); } TEST_F(ImageTest, CropTDB) { @@ -860,4 +862,95 @@ TEST_F(ImageTest, ImageLoop) { ASSERT_TRUE(!img_enc.empty()); iter++; } +} + +TEST_F(ImageTest, ImageLoopURLError) { + VCL::Image img(img_); + ImageLoop imageLoop; + + std::string _url = "http://localhost:5010/imag"; + Json::Value _options; + _options["format"] = "jpg"; + _options["id"] = "flip"; + + img.flip(0); + img.remoteOperation(_url, _options); + + imageLoop.set_nrof_entities(1); + + imageLoop.enqueue(&img); + + while (imageLoop.is_loop_running()) { + continue; + } + + std::map imageMap = imageLoop.get_image_map(); + std::map::iterator iter = imageMap.begin(); + + ASSERT_TRUE(iter->second->get_query_error_response() != ""); +} + +TEST_F(ImageTest, ImageLoopRemoteFunctionError) { + VCL::Image img(img_); + ImageLoop imageLoop; + + std::string _url = "http://localhost:5010/image"; + Json::Value _options; + _options["format"] = "jpg"; + _options["id"] = "gray"; + + img.flip(0); + img.remoteOperation(_url, _options); + + imageLoop.set_nrof_entities(1); + + imageLoop.enqueue(&img); + + while (imageLoop.is_loop_running()) { + continue; + } + + std::map imageMap = imageLoop.get_image_map(); + std::map::iterator iter = imageMap.begin(); + + ASSERT_TRUE(iter->second->get_query_error_response() != ""); +} + +TEST_F(ImageTest, ImageLoopSyncRemoteFunctionError) { + VCL::Image img(img_); + ImageLoop imageLoop; + + std::string _url = "http://localhost:5010/imag"; + Json::Value _options; + _options["format"] = "jpg"; + _options["id"] = "gray"; + + img.flip(0); + img.syncremoteOperation(_url, _options); + + imageLoop.set_nrof_entities(1); + + imageLoop.enqueue(&img); + + while (imageLoop.is_loop_running()) { + continue; + } + + std::map imageMap = imageLoop.get_image_map(); + std::map::iterator iter = imageMap.begin(); + + ASSERT_TRUE(iter->second->get_query_error_response() != ""); +} + +TEST_F(ImageTest, PipelineException) { + VCL::Image img(img_); + + img.threshold(100); + img.flip(0); + img.resize(50, 80); + img.crop(bad_rect_); + + img.get_cvmat(); + ASSERT_STREQ(img.get_query_error_response().data(), + "Requested area is not within the image"); } \ No newline at end of file diff --git a/tests/unit_tests/Video_test.cc b/tests/unit_tests/Video_test.cc index 05fe9ad5..726f78d1 100644 --- a/tests/unit_tests/Video_test.cc +++ b/tests/unit_tests/Video_test.cc @@ -27,6 +27,7 @@ * */ +#include "VideoLoop.h" #include "vcl/Video.h" #include "gtest/gtest.h" @@ -102,11 +103,21 @@ class VideoTest : public Video { }; }; // namespace VCL +/** + * Create a Video object. + * Throw an exception as no video file is + * available to count number of frames + */ TEST_F(VideoTest, DefaultConstructor) { VCL::Video video_data; ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); } +/** + * Create a video object from a file. + * Should have the same number of frames as + * the OpenCV video + */ TEST_F(VideoTest, StringConstructor) { VCL::Video video_data(_video_path_avi_xvid); long input_frame_count = video_data.get_frame_count(); @@ -116,6 +127,10 @@ TEST_F(VideoTest, StringConstructor) { ASSERT_EQ(input_frame_count, test_frame_count); } +/** + * Create a video from a filename that has no extension. + * Should successfully create a video of 'mp4' extension. + */ TEST_F(VideoTest, StringConstructorNoFormat) { VCL::Video video_data("videos/megamind"); long input_frame_count = video_data.get_frame_count(); @@ -125,11 +140,19 @@ TEST_F(VideoTest, StringConstructorNoFormat) { ASSERT_EQ(input_frame_count, test_frame_count); } +/** + * Try create a video with an unavailable file location. + * Should throw an exception. + */ TEST_F(VideoTest, StringConstructorNoExists) { VCL::Video video_data("this/path/does/not/exist.wrongformat"); ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); } +/** + * Create a copy of a Video object. + * Both videos should have the same frames. + */ TEST_F(VideoTest, CopyConstructor) { VCL::Video testVideo4copy(_video_path_avi_xvid); @@ -147,6 +170,10 @@ TEST_F(VideoTest, CopyConstructor) { } } +/** + * Create a video object from a blob. + * Should have the same frames as an OpenCV video object. + */ TEST_F(VideoTest, BlobConstructor) { std::ifstream ifile; ifile.open(_video_path_avi_xvid); @@ -223,6 +250,10 @@ TEST_F(VideoTest, CreateUnique) { } } +/** + * Create a Video object using an AVI file. + * Should have the same frames as an OpenCV video object. + */ TEST_F(VideoTest, ReadAVI_XVID) { try { VCL::Video video_data(_video_path_avi_xvid); @@ -244,6 +275,10 @@ TEST_F(VideoTest, ReadAVI_XVID) { } } +/** + * Create a Video object using an MP4 file. + * Should have the same frames as an OpenCV video object. + */ TEST_F(VideoTest, ReadMP4_H264) { try { VCL::Video video_data(_video_path_mp4_h264); @@ -265,28 +300,27 @@ TEST_F(VideoTest, ReadMP4_H264) { } } +/** + * Create a Video object of MP4 format using an AVI file and write to the data + * store. Imitates the VDMS read then store capability. Should have the same + * frames as an OpenCV video object. + */ TEST_F(VideoTest, WriteMP4_H264) { try { + std::string temp_video_input("/tmp/video_test_WriteMP4_H264_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + std::string temp_video_test("/tmp/video_test_WriteMP4_H264_test.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_test, get_fourcc()); + std::string write_output_vcl("videos_tests/write_test_vcl.mp4"); { - VCL::Video video_data(_video_path_avi_xvid); + VCL::Video video_data(temp_video_input); video_data.store(write_output_vcl, VCL::Video::Codec::H264); } // OpenCV writing the video H264 std::string write_output_ocv("videos_tests/write_test_ocv.mp4"); - { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - - cv::VideoWriter testResultVideo( - write_output_ocv, get_fourcc(), testWriteVideo.get(cv::CAP_PROP_FPS), - cv::Size(testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), - testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT))); - - for (auto &frame : _frames_xvid) { - testResultVideo << frame; - } - } + { copy_video_to_temp(temp_video_test, write_output_ocv, get_fourcc()); } VCL::Video video_data(write_output_vcl); long input_frame_count = video_data.get_frame_count(); @@ -307,34 +341,40 @@ TEST_F(VideoTest, WriteMP4_H264) { compare_mat_mat(input_frame, test_frame); } + std::remove(temp_video_input.data()); + std::remove(temp_video_test.data()); + } catch (VCL::Exception &e) { print_exception(e); ASSERT_TRUE(false); } } +/** + * Create a Video object using an AVI file and write to the data store. + * Imitates the VDMS read then store capability. + * Should have the same frames as an OpenCV video object. + */ TEST_F(VideoTest, WriteAVI_XVID) { try { + std::string temp_video_input("/tmp/video_test_WriteAVI_XVID_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, + cv::VideoWriter::fourcc('X', 'V', 'I', 'D')); + std::string temp_video_test("/tmp/video_test_WriteAVI_XVID_test.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_test, + cv::VideoWriter::fourcc('X', 'V', 'I', 'D')); + std::string write_output_vcl("videos_tests/write_test_vcl.avi"); { - VCL::Video video_data(_video_path_avi_xvid); + VCL::Video video_data(temp_video_input); video_data.store(write_output_vcl, VCL::Video::Codec::XVID); } // OpenCV writing the video H264 std::string write_output_ocv("videos_tests/write_test_ocv.avi"); { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); - - cv::VideoWriter testResultVideo( - write_output_ocv, cv::VideoWriter::fourcc('X', 'V', 'I', 'D'), - testWriteVideo.get(cv::CAP_PROP_FPS), - cv::Size(testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), - testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT))); - - for (auto &frame : _frames_xvid) { - testResultVideo << frame; - } + copy_video_to_temp(temp_video_test, write_output_ocv, + cv::VideoWriter::fourcc('X', 'V', 'I', 'D')); } VCL::Video video_data(write_output_vcl); @@ -355,6 +395,8 @@ TEST_F(VideoTest, WriteAVI_XVID) { compare_mat_mat(input_frame, test_frame); } + std::remove(temp_video_input.data()); + std::remove(temp_video_test.data()); } catch (VCL::Exception &e) { print_exception(e); @@ -362,15 +404,25 @@ TEST_F(VideoTest, WriteAVI_XVID) { } } +/** + * Imitates the resize and store operation of VDMS. + * Should have the same frames as an OpenCV video object + * that undergoes a resize operation. + */ TEST_F(VideoTest, ResizeWrite) { int new_w = 160; int new_h = 90; try { + std::string temp_video_input("/tmp/video_test_ResizeWrite_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + std::string temp_video_test("/tmp/video_test_ResizeWrite_test.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_test, get_fourcc()); + std::string resize_name_vcl("videos_tests/resize_vcl.mp4"); { - VCL::Video video_data(_video_path_avi_xvid); // + VCL::Video video_data(temp_video_input); // video_data.resize(new_w, new_h); video_data.store(resize_name_vcl, VCL::Video::Codec::H264); } @@ -378,19 +430,25 @@ TEST_F(VideoTest, ResizeWrite) { // OpenCV writing the video H264 std::string resize_name_ocv("videos_tests/resize_ocv.mp4"); { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + cv::VideoCapture testWriteVideo(temp_video_test); cv::VideoWriter testResultVideo(resize_name_ocv, get_fourcc(), testWriteVideo.get(cv::CAP_PROP_FPS), cv::Size(new_w, new_h)); - for (auto &ff : _frames_xvid) { + while (true) { + cv::Mat mat_frame; + testWriteVideo >> mat_frame; + + if (mat_frame.empty()) { + break; + } cv::Mat cv_resized; - cv::resize(ff, cv_resized, cv::Size(new_w, new_h)); + cv::resize(mat_frame, cv_resized, cv::Size(new_w, new_h)); + testResultVideo << cv_resized; + mat_frame.release(); } - - testWriteVideo.release(); } VCL::Video video_data(resize_name_vcl); @@ -411,6 +469,8 @@ TEST_F(VideoTest, ResizeWrite) { compare_mat_mat(input_frame, test_frame); } + std::remove(temp_video_input.data()); + std::remove(temp_video_test.data()); } catch (VCL::Exception &e) { print_exception(e); @@ -418,6 +478,11 @@ TEST_F(VideoTest, ResizeWrite) { } } +/** + * Imitates the trim and store operation of VDMS. + * Should have the same frames as an OpenCV video object + * that undergoes a trim operation. + */ TEST_F(VideoTest, IntervalWrite) { int init = 10; int end = 100; @@ -425,9 +490,14 @@ TEST_F(VideoTest, IntervalWrite) { try { + std::string temp_video_input("/tmp/video_test_IntervalWrite_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + std::string temp_video_test("/tmp/video_test_IntervalWrite_test.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_test, get_fourcc()); + std::string interval_name_vcl("videos_tests/interval_vcl.mp4"); { - VCL::Video video_data(_video_path_avi_xvid); // + VCL::Video video_data(temp_video_input); // video_data.interval(VCL::Video::FRAMES, init, end, step); video_data.store(interval_name_vcl, VCL::Video::Codec::H264); } @@ -446,8 +516,31 @@ TEST_F(VideoTest, IntervalWrite) { if (end >= _frames_xvid.size()) ASSERT_TRUE(false); - for (int i = init; i < end; i += step) { - testResultVideo << _frames_xvid.at(i); + int frame_number = 0; + int last_frame_written = 0; + while (true) { + cv::Mat mat_frame; + testWriteVideo >> mat_frame; // Read frame + frame_number++; + + if (mat_frame.empty()) + break; + + if (frame_number >= init && frame_number < end) { + if (last_frame_written == 0) { + testResultVideo << mat_frame; + last_frame_written = frame_number; + } else { + if ((frame_number - last_frame_written) == step) { + testResultVideo << mat_frame; + last_frame_written = frame_number; + } + } + } + + if (frame_number > end) { + break; + } } testWriteVideo.release(); @@ -469,8 +562,10 @@ TEST_F(VideoTest, IntervalWrite) { if (test_frame.empty()) break; // should not happen - compare_mat_mat(input_frame, test_frame); + compare_image_image(input_frame, test_frame); } + std::remove(temp_video_input.data()); + std::remove(temp_video_test.data()); } catch (VCL::Exception &e) { print_exception(e); @@ -478,6 +573,10 @@ TEST_F(VideoTest, IntervalWrite) { } } +/** + * Try to trim a video with out of bounds parameters. + * Should throw an exception. + */ TEST_F(VideoTest, IntervalOutOfBounds) { // Video has 270 frames, we test out of bounds here. @@ -488,7 +587,9 @@ TEST_F(VideoTest, IntervalOutOfBounds) { VCL::Video video_data(_video_path_avi_xvid); // video_data.interval(VCL::Video::FRAMES, init, end, step); // It will only throw when the operations are performed - ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); + video_data.get_frame_count(); + ASSERT_STREQ(video_data.get_query_error_response().data(), + "End Frame cannot be greater than number of frames"); } catch (VCL::Exception &e) { print_exception(e); ASSERT_TRUE(false); @@ -500,21 +601,33 @@ TEST_F(VideoTest, IntervalOutOfBounds) { VCL::Video video_data(_video_path_avi_xvid); // video_data.interval(VCL::Video::FRAMES, init, end, step); // It will only throw when the operations are performed - ASSERT_THROW(video_data.get_frame_count(), VCL::Exception); + video_data.get_frame_count(); + ASSERT_STREQ(video_data.get_query_error_response().data(), + "Start Frame cannot be greater than number of frames"); } catch (VCL::Exception &e) { print_exception(e); ASSERT_TRUE(false); } } +/** + * Imitates the threshold and store operation of VDMS. + * Should have the same frames as an OpenCV video object + * that undergoes a threshold operation. + */ TEST_F(VideoTest, ThresholdWrite) { int ths = 100; try { + std::string temp_video_input("/tmp/video_test_ThresholdWrite_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + std::string temp_video_test("/tmp/video_test_ThresholdWrite_test.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_test, get_fourcc()); + std::string threshold_name_vcl("videos_tests/threshold_vcl.mp4"); { - VCL::Video video_data(_video_path_avi_xvid); // + VCL::Video video_data(temp_video_input); // video_data.threshold(ths); video_data.store(threshold_name_vcl, VCL::Video::Codec::H264); } @@ -522,7 +635,7 @@ TEST_F(VideoTest, ThresholdWrite) { // OpenCV writing the video H264 std::string threshold_name_ocv("videos_tests/threshold_ocv.mp4"); { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + cv::VideoCapture testWriteVideo(temp_video_test); cv::VideoWriter testResultVideo( threshold_name_ocv, get_fourcc(), @@ -530,10 +643,18 @@ TEST_F(VideoTest, ThresholdWrite) { cv::Size(testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT))); - for (auto &ff : _frames_xvid) { + while (true) { + cv::Mat mat_frame; + testWriteVideo >> mat_frame; + + if (mat_frame.empty()) { + break; + } cv::Mat cv_ths; - cv::threshold(ff, cv_ths, ths, ths, cv::THRESH_TOZERO); + cv::threshold(mat_frame, cv_ths, ths, ths, cv::THRESH_TOZERO); + testResultVideo << cv_ths; + mat_frame.release(); } testWriteVideo.release(); @@ -557,6 +678,8 @@ TEST_F(VideoTest, ThresholdWrite) { compare_mat_mat(input_frame, test_frame); } + std::remove(temp_video_input.data()); + std::remove(temp_video_test.data()); } catch (VCL::Exception &e) { print_exception(e); @@ -564,6 +687,11 @@ TEST_F(VideoTest, ThresholdWrite) { } } +/** + * Imitates the crop and store operation of VDMS. + * Should have the same frames as an OpenCV video object + * that undergoes a crop operation. + */ TEST_F(VideoTest, CropWrite) { int new_w = 160; int new_h = 90; @@ -573,9 +701,14 @@ TEST_F(VideoTest, CropWrite) { try { + std::string temp_video_input("/tmp/video_test_CropWrite_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + std::string temp_video_test("/tmp/video_test_CropWrite_test.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_test, get_fourcc()); + std::string crop_name_vcl("videos_tests/crop_vcl.mp4"); { - VCL::Video video_data(_video_path_avi_xvid); // + VCL::Video video_data(temp_video_input); // video_data.crop(rect); video_data.store(crop_name_vcl, VCL::Video::Codec::H264); } @@ -583,15 +716,24 @@ TEST_F(VideoTest, CropWrite) { // OpenCV writing the video H264 std::string crop_name_ocv("videos_tests/crop_ocv.mp4"); { - cv::VideoCapture testWriteVideo(_video_path_avi_xvid); + cv::VideoCapture testWriteVideo(temp_video_test); cv::VideoWriter testResultVideo(crop_name_ocv, get_fourcc(), testWriteVideo.get(cv::CAP_PROP_FPS), cv::Size(new_w, new_h)); - for (auto &ff : _frames_xvid) { - cv::Mat roi_frame(ff, ocv_rect); + while (true) { + cv::Mat mat_frame; + testWriteVideo >> mat_frame; + + if (mat_frame.empty()) { + break; + } + + cv::Mat roi_frame(mat_frame, ocv_rect); + testResultVideo << roi_frame; + mat_frame.release(); } testWriteVideo.release(); @@ -615,6 +757,166 @@ TEST_F(VideoTest, CropWrite) { compare_mat_mat(input_frame, test_frame); } + std::remove(temp_video_input.data()); + std::remove(temp_video_test.data()); + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} + +/** + * Imitates performing a remote operation (Adding a caption here) + * and then storing the video in VDMS. + * Should have the same frames as an OpenCV video object + * that undergoes a captioning operation. + */ +TEST_F(VideoTest, SyncRemoteWrite) { + std::string _url = "http://localhost:5010/video"; + Json::Value _options; + _options["format"] = "mp4"; + _options["text"] = "Video"; + _options["id"] = "caption"; + + try { + + std::string temp_video_input("/tmp/video_test_SyncRemoteWrite_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + std::string temp_video_test("/tmp/video_test_SyncRemoteWrite_test.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_test, get_fourcc()); + + std::string syncremote_name_vcl("videos_tests/syncremote_vcl.mp4"); + { + VCL::Video video_data(temp_video_input); // + video_data.syncremoteOperation(_url, _options); + video_data.store(syncremote_name_vcl, VCL::Video::Codec::H264); + } + + // OpenCV writing the video H264 + std::string syncremote_name_ocv("videos_tests/syncremote_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(temp_video_test); + + cv::VideoWriter testResultVideo( + syncremote_name_ocv, get_fourcc(), + testWriteVideo.get(cv::CAP_PROP_FPS), + cv::Size(testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), + testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT))); + + while (true) { + cv::Mat mat_frame; + testWriteVideo >> mat_frame; + + if (mat_frame.empty()) { + break; + } + cv::putText(mat_frame, _options["text"].asCString(), cv::Point(10, 25), + cv::FONT_HERSHEY_SIMPLEX, 0.8, CV_RGB(255, 255, 255), 2); + + testResultVideo << mat_frame; + mat_frame.release(); + } + } + + VCL::Video video_data(syncremote_name_vcl); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(syncremote_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + + ASSERT_EQ(input_frame_count, test_frame_count); + + for (int i = 0; i < input_frame_count; ++i) { + cv::Mat input_frame = video_data.get_frame(i); + cv::Mat test_frame; + testVideo >> test_frame; + + if (test_frame.empty()) + break; // should not happen + + compare_image_image(input_frame, test_frame); + } + std::remove(temp_video_input.data()); + std::remove(temp_video_test.data()); + + } catch (VCL::Exception &e) { + print_exception(e); + ASSERT_TRUE(false); + } +} + +/** + * Imitates performing a user defined operation (Adding a caption here) + * and then storing the video in VDMS. + * Should have the same frames as an OpenCV video object + * that undergoes a captioning operation. + */ +TEST_F(VideoTest, UDFWrite) { + Json::Value _options; + _options["port"] = 5555; + _options["text"] = "Video"; + _options["id"] = "caption"; + + try { + + std::string temp_video_input("/tmp/video_test_UDFWrite_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + std::string temp_video_test("/tmp/video_test_UDFemoteWrite_test.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_test, get_fourcc()); + + std::string udf_name_vcl("videos_tests/udf_vcl.mp4"); + { + VCL::Video video_data(temp_video_input); // + video_data.userOperation(_options); + video_data.store(udf_name_vcl, VCL::Video::Codec::H264); + } + + // OpenCV writing the video H264 + std::string udf_name_ocv("videos_tests/udf_ocv.mp4"); + { + cv::VideoCapture testWriteVideo(temp_video_test); + + cv::VideoWriter testResultVideo( + udf_name_ocv, get_fourcc(), testWriteVideo.get(cv::CAP_PROP_FPS), + cv::Size(testWriteVideo.get(cv::CAP_PROP_FRAME_WIDTH), + testWriteVideo.get(cv::CAP_PROP_FRAME_HEIGHT))); + + while (true) { + cv::Mat mat_frame; + testWriteVideo >> mat_frame; + + if (mat_frame.empty()) { + break; + } + cv::putText(mat_frame, _options["text"].asCString(), cv::Point(10, 25), + cv::FONT_HERSHEY_SIMPLEX, 0.8, CV_RGB(255, 255, 255), 2); + + testResultVideo << mat_frame; + mat_frame.release(); + } + } + + VCL::Video video_data(udf_name_vcl); + long input_frame_count = video_data.get_frame_count(); + + cv::VideoCapture testVideo(udf_name_ocv); + long test_frame_count = testVideo.get(cv::CAP_PROP_FRAME_COUNT); + + ASSERT_EQ(input_frame_count, test_frame_count); + + for (int i = 0; i < input_frame_count; ++i) { + cv::Mat input_frame = video_data.get_frame(i); + cv::Mat test_frame; + testVideo >> test_frame; + + if (test_frame.empty()) + break; // should not happen + + compare_image_image(input_frame, test_frame); + } + std::remove(temp_video_input.data()); + std::remove(temp_video_test.data()); } catch (VCL::Exception &e) { print_exception(e); @@ -622,6 +924,194 @@ TEST_F(VideoTest, CropWrite) { } } +/** + * Tests the working of the VideoLoop class + * when a single remote operation is executed. + * The resulting video being encoded should not be null. + */ +TEST_F(VideoTest, VideoLoopTest) { + std::string _url = "http://localhost:5010/video"; + Json::Value _options; + _options["format"] = "mp4"; + _options["text"] = "Video"; + _options["id"] = "caption"; + + std::string temp_video_input("/tmp/video_test_VideoLoopTest_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + + std::string vloop_name_vcl("videos_tests/vloop_vcl.mp4"); + { + VCL::Video video_data(temp_video_input); + video_data.store(vloop_name_vcl, VCL::Video::Codec::H264); + } + + VideoLoop videoLoop; + VCL::Video video_data(vloop_name_vcl); + + video_data.remoteOperation(_url, _options); + + videoLoop.set_nrof_entities(1); + + videoLoop.enqueue(video_data); + + while (videoLoop.is_loop_running()) { + continue; + } + + std::map videoMap = videoLoop.get_video_map(); + std::map::iterator iter = videoMap.begin(); + + VCL::Video::Codec vcl_codec = VCL::Video::Codec::H264; + const std::string vcl_container = "mp4"; + + while (iter != videoMap.end()) { + auto video_enc = iter->second.get_encoded(vcl_container, vcl_codec); + int size = video_enc.size(); + + ASSERT_TRUE(!video_enc.empty()); + iter++; + } +} + +/** + * Tests the working of the VideoLoop class + * when a an operation pipeline is executed. + * The resulting video being encoded should not be null. + */ +TEST_F(VideoTest, VideoLoopPipelineTest) { + std::string _url = "http://localhost:5010/video"; + Json::Value _options; + _options["format"] = "mp4"; + _options["text"] = "Video"; + _options["id"] = "caption"; + + int ths = 100; + + int init = 10; + int end = 100; + int step = 5; + + std::string temp_video_input( + "/tmp/video_test_VideoLoopPipelineTest_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + + std::string vloop_name_vcl("videos_tests/vloop_vcl.mp4"); + { + VCL::Video video_data(temp_video_input); + video_data.store(vloop_name_vcl, VCL::Video::Codec::H264); + } + + VideoLoop videoLoop; + VCL::Video video_data(vloop_name_vcl); + + video_data.threshold(ths); + video_data.interval(VCL::Video::FRAMES, init, end, step); + video_data.remoteOperation(_url, _options); + + videoLoop.set_nrof_entities(1); + + videoLoop.enqueue(video_data); + + while (videoLoop.is_loop_running()) { + continue; + } + + std::map videoMap = videoLoop.get_video_map(); + std::map::iterator iter = videoMap.begin(); + + VCL::Video::Codec vcl_codec = VCL::Video::Codec::H264; + const std::string vcl_container = "mp4"; + + while (iter != videoMap.end()) { + auto video_enc = iter->second.get_encoded(vcl_container, vcl_codec); + int size = video_enc.size(); + + ASSERT_TRUE(!video_enc.empty()); + iter++; + } +} + +/** + * Tests the working of the VideoLoop class + * when a wrong url is provided for a remote operation. + * The resulting video object should have an error message. + */ +TEST_F(VideoTest, VideoLoopTestError) { + std::string _url = "http://localhost:5010/vide"; + Json::Value _options; + _options["format"] = "mp4"; + _options["text"] = "Video"; + _options["id"] = "caption"; + + std::string temp_video_input("/tmp/video_test_VideoLoopTestError_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + + std::string vloop_name_vcl("videos_tests/vloop_vcl.mp4"); + { + VCL::Video video_data(temp_video_input); + video_data.store(vloop_name_vcl, VCL::Video::Codec::H264); + } + + VideoLoop videoLoop; + VCL::Video video_data(vloop_name_vcl); + + video_data.remoteOperation(_url, _options); + + videoLoop.set_nrof_entities(1); + + videoLoop.enqueue(video_data); + + while (videoLoop.is_loop_running()) { + continue; + } + + std::map videoMap = videoLoop.get_video_map(); + std::map::iterator iter = videoMap.begin(); + + ASSERT_TRUE(iter->second.get_query_error_response() != ""); +} + +/** + * Tests the working of the VideoLoop class + * when a wrong url is provided for a synchronous remote operation. + * The resulting video object should have an error message. + */ +TEST_F(VideoTest, VideoLoopSyncRemoteTestError) { + std::string _url = "http://localhost:5010/vide"; + Json::Value _options; + _options["format"] = "mp4"; + _options["text"] = "Video"; + _options["id"] = "caption"; + + std::string temp_video_input( + "/tmp/video_test_VideoLoopSyncRemoteTestError_input.avi"); + copy_video_to_temp(_video_path_avi_xvid, temp_video_input, get_fourcc()); + + std::string vloop_name_vcl("videos_tests/vloop_vcl.mp4"); + { + VCL::Video video_data(temp_video_input); + video_data.store(vloop_name_vcl, VCL::Video::Codec::H264); + } + + VideoLoop videoLoop; + VCL::Video video_data(vloop_name_vcl); + + video_data.syncremoteOperation(_url, _options); + + videoLoop.set_nrof_entities(1); + + videoLoop.enqueue(video_data); + + while (videoLoop.is_loop_running()) { + continue; + } + + std::map videoMap = videoLoop.get_video_map(); + std::map::iterator iter = videoMap.begin(); + + ASSERT_TRUE(iter->second.get_query_error_response() != ""); +} + TEST_F(VideoTest, KeyFrameExtractionSuccess) { try { VCL::VideoTest video_data(_video_path_mp4_h264); diff --git a/tests/unit_tests/client_add_entity.cc b/tests/unit_tests/client_add_entity.cc index 9a7d7c04..24b773e9 100644 --- a/tests/unit_tests/client_add_entity.cc +++ b/tests/unit_tests/client_add_entity.cc @@ -1,203 +1,203 @@ - -#include "meta_data_helper.h" - -TEST(CLIENT_CPP, add_two_CLIENT_CPP_with_connection) { - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple; - tuple.append(meta_obj->construct_add_query(1, false, false)); - - tuple.append(meta_obj->construct_add_area(2, false)); - tuple.append(meta_obj->construct_add_connection(1, 2, false)); - - VDMS::Response response = - meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - int status1 = result[0]["AddEntity"]["status"].asInt(); - int status2 = result[1]["AddEntity"]["status"].asInt(); - int status3 = result[1]["AddConnection"]["status"].asInt(); - - EXPECT_EQ(status1, 0); - EXPECT_EQ(status2, 0); - EXPECT_EQ(status3, 0); -} - -TEST(CLIENT_CPP, add_single_entity) { - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple; - tuple.append(meta_obj->construct_add_query(1, false, false)); - VDMS::Response response = - meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - int status1 = result[0]["AddEntity"]["status"].asInt(); - - EXPECT_EQ(status1, 0); -} - -TEST(CLIENT_CPP, add_single_entity_expiration) { - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple; - tuple.append(meta_obj->construct_add_query(1, false, true)); - VDMS::Response response = - meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - int status1 = result[0]["AddEntity"]["status"].asInt(); - - EXPECT_EQ(status1, 0); -} - -TEST(CLIENT_CPP, add_single_entity_constraints) { - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple; - tuple.append(meta_obj->construct_add_query(1, true, false)); - VDMS::Response response = - meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - int status1 = result[0]["AddEntity"]["status"].asInt(); - - EXPECT_EQ(status1, 0); -} - -TEST(CLIENT_CPP, add_multiple_CLIENT_CPP) { - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple; - int num_queries = 4; - for (int i = 1; i <= num_queries; i++) { - tuple.append(meta_obj->construct_add_query(i, false, false)); - } - - VDMS::Response response = - meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - for (int i = 0; i < result.size(); i++) { - int status = result[i]["AddEntity"]["status"].asInt(); - - EXPECT_EQ(status, 0); - } -} -TEST(CLIENT_CPP, add_multiple_from_file) { - - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - - std::ifstream ifile; - int fsize; - char *inBuf; - ifile.open("../tests/unit_tests/queries.json", std::ifstream::in); - ifile.seekg(0, std::ios::end); - fsize = (int)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - inBuf = new char[fsize]; - ifile.read(inBuf, fsize); - std::string json_query = std::string(inBuf); - ifile.close(); - delete[] inBuf; - - VDMS::Response response = meta_obj->_aclient->query(json_query); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - for (int i = 0; i < result.size(); i++) { - int status = result[i]["AddEntity"]["status"].asInt(); - EXPECT_EQ(status, 0); - } -} - -TEST(CLIENT_CPP, add_two_from_file) { - - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - - std::ifstream ifile; - int fsize; - char *inBuf; - ifile.open("../tests/unit_tests/two_entities.json", std::ifstream::in); - ifile.seekg(0, std::ios::end); - fsize = (int)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - inBuf = new char[fsize]; - ifile.read(inBuf, fsize); - std::string json_query = std::string(inBuf); - ifile.close(); - delete[] inBuf; - - VDMS::Response response = meta_obj->_aclient->query(json_query); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - for (int i = 0; i < result.size(); i++) { - int status = result[i]["AddEntity"]["status"].asInt(); - EXPECT_EQ(status, 0); - } -} - -TEST(CLIENT_CPP, add_connection_from_file) { - - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - - std::ifstream ifile; - int fsize; - char *inBuf; - ifile.open("../tests/unit_tests/connection.json", std::ifstream::in); - ifile.seekg(0, std::ios::end); - fsize = (int)ifile.tellg(); - ifile.seekg(0, std::ios::beg); - inBuf = new char[fsize]; - ifile.read(inBuf, fsize); - std::string json_query = std::string(inBuf); - ifile.close(); - delete[] inBuf; - - VDMS::Response response = meta_obj->_aclient->query(json_query); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - for (int i = 0; i < result.size() - 1; i++) { - int status = result[i]["FindEntity"]["status"].asInt(); - EXPECT_EQ(status, 0); - } -} - -TEST(CLIENT_CPP, add_multiple_CLIENT_CPP_constraints) { - Meta_Data *meta_obj = new Meta_Data(); - meta_obj->_aclient.reset( - new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); - Json::Value tuple; - int num_queries = 4; - for (int i = 1; i <= num_queries; i++) { - tuple.append(meta_obj->construct_add_query(i, true, false)); - } - - VDMS::Response response = - meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); - Json::Value result; - meta_obj->_reader.parse(response.json.c_str(), result); - - for (int i = 0; i < result.size(); i++) { - int status = result[i]["AddEntity"]["status"].asInt(); - EXPECT_EQ(status, 0); - } -} + +#include "meta_data_helper.h" + +TEST(CLIENT_CPP, add_two_CLIENT_CPP_with_connection) { + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple.append(meta_obj->construct_add_query(1, false, false)); + + tuple.append(meta_obj->construct_add_area(2, false)); + tuple.append(meta_obj->construct_add_connection(1, 2, false)); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + int status1 = result[0]["AddEntity"]["status"].asInt(); + int status2 = result[1]["AddEntity"]["status"].asInt(); + int status3 = result[1]["AddConnection"]["status"].asInt(); + + EXPECT_EQ(status1, 0); + EXPECT_EQ(status2, 0); + EXPECT_EQ(status3, 0); +} + +TEST(CLIENT_CPP, add_single_entity) { + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple.append(meta_obj->construct_add_query(1, false, false)); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + int status1 = result[0]["AddEntity"]["status"].asInt(); + + EXPECT_EQ(status1, 0); +} + +TEST(CLIENT_CPP, add_single_entity_expiration) { + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple.append(meta_obj->construct_add_query(1, false, true)); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + int status1 = result[0]["AddEntity"]["status"].asInt(); + + EXPECT_EQ(status1, 0); +} + +TEST(CLIENT_CPP, add_single_entity_constraints) { + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple.append(meta_obj->construct_add_query(1, true, false)); + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + int status1 = result[0]["AddEntity"]["status"].asInt(); + + EXPECT_EQ(status1, 0); +} + +TEST(CLIENT_CPP, add_multiple_CLIENT_CPP) { + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + int num_queries = 4; + for (int i = 1; i <= num_queries; i++) { + tuple.append(meta_obj->construct_add_query(i, false, false)); + } + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + for (int i = 0; i < result.size(); i++) { + int status = result[i]["AddEntity"]["status"].asInt(); + + EXPECT_EQ(status, 0); + } +} +TEST(CLIENT_CPP, add_multiple_from_file) { + + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("../tests/unit_tests/queries.json", std::ifstream::in); + ifile.seekg(0, std::ios::end); + fsize = (int)ifile.tellg(); + ifile.seekg(0, std::ios::beg); + inBuf = new char[fsize]; + ifile.read(inBuf, fsize); + std::string json_query = std::string(inBuf); + ifile.close(); + delete[] inBuf; + + VDMS::Response response = meta_obj->_aclient->query(json_query); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + for (int i = 0; i < result.size(); i++) { + int status = result[i]["AddEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); + } +} + +TEST(CLIENT_CPP, add_two_from_file) { + + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("../tests/unit_tests/two_entities.json", std::ifstream::in); + ifile.seekg(0, std::ios::end); + fsize = (int)ifile.tellg(); + ifile.seekg(0, std::ios::beg); + inBuf = new char[fsize]; + ifile.read(inBuf, fsize); + std::string json_query = std::string(inBuf); + ifile.close(); + delete[] inBuf; + + VDMS::Response response = meta_obj->_aclient->query(json_query); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + for (int i = 0; i < result.size(); i++) { + int status = result[i]["AddEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); + } +} + +TEST(CLIENT_CPP, add_connection_from_file) { + + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + + std::ifstream ifile; + int fsize; + char *inBuf; + ifile.open("../tests/unit_tests/connection.json", std::ifstream::in); + ifile.seekg(0, std::ios::end); + fsize = (int)ifile.tellg(); + ifile.seekg(0, std::ios::beg); + inBuf = new char[fsize]; + ifile.read(inBuf, fsize); + std::string json_query = std::string(inBuf); + ifile.close(); + delete[] inBuf; + + VDMS::Response response = meta_obj->_aclient->query(json_query); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + for (int i = 0; i < result.size() - 1; i++) { + int status = result[i]["FindEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); + } +} + +TEST(CLIENT_CPP, add_multiple_CLIENT_CPP_constraints) { + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + int num_queries = 4; + for (int i = 1; i <= num_queries; i++) { + tuple.append(meta_obj->construct_add_query(i, true, false)); + } + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + for (int i = 0; i < result.size(); i++) { + int status = result[i]["AddEntity"]["status"].asInt(); + EXPECT_EQ(status, 0); + } +} diff --git a/tests/unit_tests/client_image.cc b/tests/unit_tests/client_image.cc index 970e70bc..68f0e5c8 100644 --- a/tests/unit_tests/client_image.cc +++ b/tests/unit_tests/client_image.cc @@ -76,6 +76,24 @@ TEST(CLIENT_CPP, find_image) { EXPECT_EQ(status1, 0); } +TEST(CLIENT_CPP, find_image_noentity) { + + Meta_Data *meta_obj = new Meta_Data(); + meta_obj->_aclient.reset( + new VDMS::VDMSClient(meta_obj->get_server(), meta_obj->get_port())); + Json::Value tuple; + tuple = meta_obj->construct_find_image_no_entity(); + + VDMS::Response response = + meta_obj->_aclient->query(meta_obj->_fastwriter.write(tuple)); + Json::Value result; + meta_obj->_reader.parse(response.json.c_str(), result); + + std::string info1 = result[0]["FindImage"]["info"].asString(); + delete meta_obj; + EXPECT_STREQ(info1.data(), "No entities found"); +} + TEST(CLIENT_CPP, find_image_remote) { Meta_Data *meta_obj = new Meta_Data(); diff --git a/tests/unit_tests/helpers.cc b/tests/unit_tests/helpers.cc index ad9f1846..76bc8707 100644 --- a/tests/unit_tests/helpers.cc +++ b/tests/unit_tests/helpers.cc @@ -45,6 +45,26 @@ // Image / Video Helpers +// source: +// https://github.com/MasteringOpenCV/code/blob/master/Chapter8_FaceRecognition/recognition.cpp +// Compare two images by getting the L2 error (square-root of sum of squared +// error). +// this is useful for jpeg images with small differences due to encoding +void compare_image_image(cv::Mat &A, cv::Mat &B, float error) { + if (A.rows > 0 && A.rows == B.rows && A.cols > 0 && A.cols == B.cols) { + // Calculate the L2 relative error between images. + double errorL2 = norm(A, B, cv::NORM_L2); + // Convert to a reasonable scale, since L2 error is summed across all pixels + // of the image. + double similarity = errorL2 / (double)(A.rows * A.cols); + // std::cout << "Similarity: " << similarity << std::endl; + ASSERT_LT(similarity, error); + } else { + // Images have a different size + ASSERT_TRUE(false); + } +} + void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error) { bool exact_comparison = (error == 0.0); @@ -116,6 +136,32 @@ void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2) { } } +void copy_video_to_temp(std::string source_path, std::string dest_path, + int fourcc) { + cv::VideoCapture inputVideo(source_path); + + float _fps = static_cast(inputVideo.get(cv::CAP_PROP_FPS)); + int frame_count = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_COUNT)); + int width = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_WIDTH)); + int height = static_cast(inputVideo.get(cv::CAP_PROP_FRAME_HEIGHT)); + + cv::VideoWriter outputVideo(dest_path, fourcc, _fps, cv::Size(width, height)); + + while (true) { + cv::Mat mat_frame; + inputVideo >> mat_frame; + + if (mat_frame.empty()) { + break; + } + + outputVideo << mat_frame; + mat_frame.release(); + } + inputVideo.release(); + outputVideo.release(); +} + // Descriptors Helpers // This function return nb descriptors of dimension d as follows: diff --git a/tests/unit_tests/helpers.h b/tests/unit_tests/helpers.h index f106aa9c..6a70925d 100644 --- a/tests/unit_tests/helpers.h +++ b/tests/unit_tests/helpers.h @@ -45,10 +45,15 @@ // Image / Video Helpers +void compare_image_image(cv::Mat &A, cv::Mat &B, float error = 0.02); + void compare_mat_mat(cv::Mat &cv_img, cv::Mat &img, float error = 0.0); void compare_cvcapture_cvcapture(cv::VideoCapture v1, cv::VideoCapture v2); +void copy_video_to_temp(std::string source_path, std::string dest_path, + int fourcc); + // Descriptors Helpers void generate_desc_linear_increase(int d, int nb, float *xb, float init = 0); diff --git a/tests/unit_tests/meta_data.cc b/tests/unit_tests/meta_data.cc index 7896d4f3..35adeb9e 100644 --- a/tests/unit_tests/meta_data.cc +++ b/tests/unit_tests/meta_data.cc @@ -167,6 +167,23 @@ Json::Value Meta_Data::construct_find_image() { return tuple; } +Json::Value Meta_Data::construct_find_image_no_entity() { + Json::Value tuple; + + Json::Value cons; + cons["Name"][0] = "=="; + cons["Name"][1] = "sample"; + + Json::Value image; + image["constraints"] = cons; + + Json::Value find_image; + find_image["FindImage"] = image; + + tuple.append(find_image); + return tuple; +} + Json::Value Meta_Data::construct_find_image_withop(Json::Value operations) { Json::Value tuple; diff --git a/tests/unit_tests/meta_data_helper.h b/tests/unit_tests/meta_data_helper.h index d6679223..c3115804 100644 --- a/tests/unit_tests/meta_data_helper.h +++ b/tests/unit_tests/meta_data_helper.h @@ -41,6 +41,7 @@ class Meta_Data { Json::Value constuct_image(bool = false, Json::Value operations = {}); Json::Value constuct_video(bool = false); Json::Value construct_find_image(); + Json::Value construct_find_image_no_entity(); Json::Value construct_find_image_withop(Json::Value operations); Json::Value construct_descriptor(); Json::Value construct_find_descriptor(); diff --git a/user_defined_operations/README.md b/user_defined_operations/README.md index ec17527c..974a2f06 100644 --- a/user_defined_operations/README.md +++ b/user_defined_operations/README.md @@ -1,5 +1,5 @@ # User Defined Operations in VDMS -This submodule is required to execute user defined operations (UDF) in VDMS using message queues (Support only available for images). Although shipped with VDMS, this submodule can be run independently and interacts with VDMS using message queues. +This submodule is required to execute user defined operations (UDF) in VDMS using message queues. Although shipped with VDMS, this submodule can be run independently and interacts with VDMS using message queues. ## Requirements - Python 3 or higher diff --git a/user_defined_operations/functions/caption.py b/user_defined_operations/functions/caption.py new file mode 100644 index 00000000..c40f1ba4 --- /dev/null +++ b/user_defined_operations/functions/caption.py @@ -0,0 +1,36 @@ +import cv2 +import numpy as np +from datetime import datetime +from collections import deque +import skvideo.io +import imutils +import time + + +def run(settings, message, input_params): + ipfilename = message + format = message.strip().split(".")[-1] + + t1 = time.time() + opfilename = settings["opfile"] + str(t1) + "." + format + print(opfilename) + vs = cv2.VideoCapture(ipfilename) + + video = skvideo.io.FFmpegWriter(opfilename, {"-pix_fmt": "bgr24"}) + i = 0 + while True: + (grabbed, frame) = vs.read() + if not grabbed: + print("[INFO] no frame read from stream - exiting") + video.close() + # sys.exit(0) + break + + label = input_params["text"] + cv2.putText( + frame, label, (10, 25), cv2.FONT_HERSHEY_SIMPLEX, 0.8, (255, 255, 255), 2 + ) + + video.writeFrame(frame) + + return (time.time() - t1), opfilename diff --git a/user_defined_operations/requirements.txt b/user_defined_operations/requirements.txt index 5ce1a8b4..23c96db1 100644 --- a/user_defined_operations/requirements.txt +++ b/user_defined_operations/requirements.txt @@ -1,2 +1,2 @@ opencv-python==4.5.5.64 -zmq \ No newline at end of file +zmq==0.0.0 \ No newline at end of file diff --git a/user_defined_operations/settings.json b/user_defined_operations/settings.json index ac75f78f..00766372 100644 --- a/user_defined_operations/settings.json +++ b/user_defined_operations/settings.json @@ -3,6 +3,7 @@ "port": 5555, "functions" : { "facedetect" : "facedetect", - "flip": "flip" + "flip": "flip", + "caption": "caption" } } \ No newline at end of file diff --git a/utils/include/stats/SystemStats.h b/utils/include/stats/SystemStats.h index 902a727d..cab27655 100644 --- a/utils/include/stats/SystemStats.h +++ b/utils/include/stats/SystemStats.h @@ -74,4 +74,5 @@ class SystemStats { void get_process_cpu_utilization(); void log_stats(std::string pName); -}; \ No newline at end of file + bool query_sufficient_memory(int size_requested); +}; diff --git a/utils/src/stats/SystemStats.cc b/utils/src/stats/SystemStats.cc index 33fc4ea0..45353bb9 100644 --- a/utils/src/stats/SystemStats.cc +++ b/utils/src/stats/SystemStats.cc @@ -303,4 +303,30 @@ void SystemStats::log_stats(std::string pname) { statsFile << "\n"; statsFile.close(); -} \ No newline at end of file +} + +bool SystemStats::query_sufficient_memory(int size_requested) { + get_system_virtual_memory(); + + long conversion_B_MB = 1024 * 1024; + long ttlVirtMemMB = memoryStats.total_virtual_memory / conversion_B_MB; + long usedVirtMemMB = memoryStats.virtual_memory_used / conversion_B_MB; + long availVirtMemMB = ttlVirtMemMB - usedVirtMemMB; + + float memPercent = + (static_cast(usedVirtMemMB) / static_cast(ttlVirtMemMB)) * + 100; + + // cout << "TTL: " << ttlVirtMemMB << ", used: " << usedVirtMemMB << ", avail: + // " << availVirtMemMB << ", requested: " << size_requested << endl; cout << + // "Used: " << memPercent << "%" << endl; + + printf("MEMORY: %0.1f%% used, %ldMB of %ldMB\n", memPercent, usedVirtMemMB, + ttlVirtMemMB); + + if (size_requested < availVirtMemMB) { + return true; + } + + return false; +}